Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Watson2012-06-07 17:14:24 +0000
committerThomas Watson2012-06-07 17:14:24 +0000
commit883f1251263debbff303100af87e8b5e5ce872ac (patch)
treecdf46f9a380638008904aa02422b6af19ab2d98d /bundles/org.eclipse.osgi
parent499ac70cc96fd37279f31bb8b700f9c6b7778391 (diff)
downloadrt.equinox.framework-883f1251263debbff303100af87e8b5e5ce872ac.tar.gz
rt.equinox.framework-883f1251263debbff303100af87e8b5e5ce872ac.tar.xz
rt.equinox.framework-883f1251263debbff303100af87e8b5e5ce872ac.zip
Move the container impl to org.eclipse.osgi
Diffstat (limited to 'bundles/org.eclipse.osgi')
-rw-r--r--bundles/org.eclipse.osgi/.classpath3
-rw-r--r--bundles/org.eclipse.osgi/.settings/.api_filters26
-rw-r--r--bundles/org.eclipse.osgi/.settings/org.eclipse.jdt.core.prefs7
-rw-r--r--bundles/org.eclipse.osgi/META-INF/MANIFEST.MF8
-rw-r--r--bundles/org.eclipse.osgi/about.html17
-rw-r--r--bundles/org.eclipse.osgi/build.properties5
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/Module.java645
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleCapability.java61
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleClassLoader.java42
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleCollisionHook.java59
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleContainer.java1156
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleContainerAdaptor.java76
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleDataBase.java1523
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleRequirement.java140
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleResolver.java663
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleRevision.java199
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleRevisionBuilder.java142
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleRevisions.java108
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleWire.java79
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleWiring.java255
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/SystemModule.java113
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/builders/OSGiManifestBuilderFactory.java557
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/namespaces/EclipsePlatformNamespace.java41
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/namespaces/EquinoxModuleDataNamespace.java106
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/container/Capabilities.java236
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/container/Converters.java107
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/container/LockSet.java86
-rw-r--r--bundles/org.eclipse.osgi/resolver/src/org/apache/felix/resolver/Activator.java52
-rw-r--r--bundles/org.eclipse.osgi/resolver/src/org/apache/felix/resolver/Candidates.java1051
-rw-r--r--bundles/org.eclipse.osgi/resolver/src/org/apache/felix/resolver/Logger.java122
-rw-r--r--bundles/org.eclipse.osgi/resolver/src/org/apache/felix/resolver/ResolverImpl.java1883
-rw-r--r--bundles/org.eclipse.osgi/resolver/src/org/apache/felix/resolver/ShadowList.java157
-rw-r--r--bundles/org.eclipse.osgi/resolver/src/org/apache/felix/resolver/SimpleHostedCapability.java61
-rw-r--r--bundles/org.eclipse.osgi/resolver/src/org/apache/felix/resolver/Util.java99
-rw-r--r--bundles/org.eclipse.osgi/resolver/src/org/apache/felix/resolver/WireImpl.java116
-rw-r--r--bundles/org.eclipse.osgi/resolver/src/org/apache/felix/resolver/WrappedCapability.java114
-rw-r--r--bundles/org.eclipse.osgi/resolver/src/org/apache/felix/resolver/WrappedRequirement.java102
-rw-r--r--bundles/org.eclipse.osgi/resolver/src/org/apache/felix/resolver/WrappedResource.java127
38 files changed, 10334 insertions, 10 deletions
diff --git a/bundles/org.eclipse.osgi/.classpath b/bundles/org.eclipse.osgi/.classpath
index 5e737b490..61f80c36c 100644
--- a/bundles/org.eclipse.osgi/.classpath
+++ b/bundles/org.eclipse.osgi/.classpath
@@ -1,10 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
- <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="osgi/src"/>
<classpathentry kind="src" path="supplement/src"/>
<classpathentry kind="src" path="core/adaptor"/>
+ <classpathentry kind="src" path="container/src"/>
<classpathentry kind="src" path="core/framework"/>
<classpathentry kind="src" path="core/composite"/>
<classpathentry kind="src" path="resolver/src"/>
diff --git a/bundles/org.eclipse.osgi/.settings/.api_filters b/bundles/org.eclipse.osgi/.settings/.api_filters
index 22e7fc6fa..05077ceed 100644
--- a/bundles/org.eclipse.osgi/.settings/.api_filters
+++ b/bundles/org.eclipse.osgi/.settings/.api_filters
@@ -1,5 +1,31 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<component id="org.eclipse.osgi" version="2">
+ <resource path="container/src/org/eclipse/osgi/container/builders/OSGiManifestBuilderFactory.java" type="org.eclipse.osgi.container.builders.OSGiManifestBuilderFactory">
+ <filter id="647004193">
+ <message_arguments>
+ <message_argument value="org.eclipse.osgi.container.builders.OSGiManifestBuilderFactory.addPackageImports(ModuleRevisionBuilder, ManifestElement[], Collection&lt;String&gt;, boolean)"/>
+ <message_argument value="StringBuilder"/>
+ <message_argument value="length()"/>
+ <message_argument value="JavaSE-1.6"/>
+ </message_arguments>
+ </filter>
+ <filter id="647004193">
+ <message_arguments>
+ <message_argument value="org.eclipse.osgi.container.builders.OSGiManifestBuilderFactory.getFragmentHost(ModuleRevisionBuilder, ManifestElement[])"/>
+ <message_argument value="StringBuilder"/>
+ <message_argument value="length()"/>
+ <message_argument value="JavaSE-1.6"/>
+ </message_arguments>
+ </filter>
+ <filter id="647004193">
+ <message_arguments>
+ <message_argument value="org.eclipse.osgi.container.builders.OSGiManifestBuilderFactory.getRequireBundle(ModuleRevisionBuilder, ManifestElement[])"/>
+ <message_argument value="StringBuilder"/>
+ <message_argument value="length()"/>
+ <message_argument value="JavaSE-1.6"/>
+ </message_arguments>
+ </filter>
+ </resource>
<resource path="core/framework/org/eclipse/osgi/framework/internal/protocol/URLStreamHandlerFactoryProxyFor15.java" type="org.eclipse.osgi.framework.internal.protocol.URLStreamHandlerFactoryProxyFor15">
<filter comment="Code is protected when running on a limited environment" id="646971428">
<message_arguments>
diff --git a/bundles/org.eclipse.osgi/.settings/org.eclipse.jdt.core.prefs b/bundles/org.eclipse.osgi/.settings/org.eclipse.jdt.core.prefs
index 4f32b45f6..df8c0b940 100644
--- a/bundles/org.eclipse.osgi/.settings/org.eclipse.jdt.core.prefs
+++ b/bundles/org.eclipse.osgi/.settings/org.eclipse.jdt.core.prefs
@@ -1,4 +1,3 @@
-#Thu Feb 17 07:54:09 CST 2011
eclipse.preferences.version=1
org.eclipse.jdt.core.builder.cleanOutputFolder=clean
org.eclipse.jdt.core.builder.duplicateResourceTask=warning
@@ -8,9 +7,9 @@ org.eclipse.jdt.core.circularClasspath=error
org.eclipse.jdt.core.classpath.exclusionPatterns=enabled
org.eclipse.jdt.core.classpath.multipleOutputLocations=enabled
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=jsr14
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.5
+org.eclipse.jdt.core.compiler.compliance=1.6
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
@@ -100,7 +99,7 @@ org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=enab
org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=error
-org.eclipse.jdt.core.compiler.source=1.5
+org.eclipse.jdt.core.compiler.source=1.6
org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
diff --git a/bundles/org.eclipse.osgi/META-INF/MANIFEST.MF b/bundles/org.eclipse.osgi/META-INF/MANIFEST.MF
index e24d90a9a..33b5d8e0d 100644
--- a/bundles/org.eclipse.osgi/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.osgi/META-INF/MANIFEST.MF
@@ -1,6 +1,9 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
-Export-Package: org.eclipse.osgi.event;version="1.0",
+Export-Package: org.eclipse.osgi.container;version="1.0",
+ org.eclipse.osgi.container.builders;version="1.0",
+ org.eclipse.osgi.container.namespaces;version="1.0",
+ org.eclipse.osgi.event;version="1.0",
org.eclipse.osgi.framework.console;version="1.1",
org.eclipse.osgi.framework.eventmgr;version="1.2",
org.eclipse.osgi.framework.log;version="1.1",
@@ -85,6 +88,5 @@ Bundle-DocUrl: http://www.eclipse.org
Eclipse-ExtensibleAPI: true
Eclipse-SystemBundle: true
Main-Class: org.eclipse.core.runtime.adaptor.EclipseStarter
-Bundle-RequiredExecutionEnvironment: J2SE-1.5,
- OSGi/Minimum-1.2
+Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Eclipse-BundleShape: jar
diff --git a/bundles/org.eclipse.osgi/about.html b/bundles/org.eclipse.osgi/about.html
index 60a8dda5f..7aa669046 100644
--- a/bundles/org.eclipse.osgi/about.html
+++ b/bundles/org.eclipse.osgi/about.html
@@ -67,6 +67,23 @@ in the file <a href="about_files/LICENSE-2.0.txt" target="_blank">LICENSE-2.0.tx
or may not be a member of the OSGi Alliance). The OSGi Alliance and its members are not responsible and shall not be held responsible in any manner for identifying or failing to identify any or all such third party
intellectual property rights.</p>
+<h4>Apache Felix Resolver</h4>
+
+<p>This bundle includes software developed by The Apache Software Foundation as part of the Felix project.
+this includes all files in the following sub-directories (and their sub-directories):</p>
+<ul>
+ <li>org/apache/felix/resolver</li>
+</ul>
+
+<p>Your use of the Resolver code is subject to the terms and conditions of the Apache Software License 2.0. A copy of the license is contained
+ in the file <a href="about_files/LICENSE-2.0.txt">LICENSE-2.0.txt</a> and is also available at <a href="http://www.apache.org/licenses/LICENSE-2.0.html">http://www.apache.org/licenses/LICENSE-2.0.html</a>.
+
+<p>The names &quot;Felix Resolver&quot; and &quot;Apache Software Foundation&quot; must not be used to endorse or promote products derived from this
+ software without prior written permission. For written permission, please contact <a href="mailto:apache@apache.org">apache@apache.org</a>.</p>
+
+<p>
+ Original binaries and source are available from the <a href="http://felix.apache.org/">Apache Felix website</a>.</p>
+
<small>OSGi&trade; is a trademark, registered trademark, or service mark of The OSGi Alliance in the US and other countries. Java is a trademark,
registered trademark, or service mark of Sun Microsystems, Inc. in the US and other countries. All other trademarks, registered trademarks, or
service marks used in the Content are the property of their respective owners and are hereby recognized.</small>
diff --git a/bundles/org.eclipse.osgi/build.properties b/bundles/org.eclipse.osgi/build.properties
index deea927bc..fdcd7b508 100644
--- a/bundles/org.eclipse.osgi/build.properties
+++ b/bundles/org.eclipse.osgi/build.properties
@@ -28,7 +28,8 @@ source.. = osgi/src,\
eclipseAdaptor/src/,\
console/src/,\
supplement/src/,\
- security/src/
+ security/src/,\
+ container/src/
output.. = bin/
jre.compilation.profile = J2SE-1.5
@@ -44,4 +45,4 @@ javacTarget=jsr14
# and use the .classpath.osgisource as the .classpath file.
# customBuildCallbacks=customBuildCallbacks.xml
-javacWarnings..=-deadCode,-raw,-unchecked \ No newline at end of file
+javacWarnings..=-deadCode,-raw,-unchecked
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/Module.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/Module.java
new file mode 100644
index 000000000..b950a49a0
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/Module.java
@@ -0,0 +1,645 @@
+/*******************************************************************************
+ * Copyright (c) 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.container;
+
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReentrantLock;
+import org.eclipse.osgi.container.namespaces.EquinoxModuleDataNamespace;
+import org.osgi.framework.*;
+import org.osgi.framework.startlevel.BundleStartLevel;
+import org.osgi.resource.Capability;
+import org.osgi.service.resolver.ResolutionException;
+
+/**
+ * A module represents a set of revisions installed in a
+ * module {@link ModuleContainer container}.
+ */
+public abstract class Module implements BundleReference, BundleStartLevel, Comparable<Module> {
+ /**
+ * The possible start options for a module
+ */
+ public static enum StartOptions {
+ /**
+ * The module start operation is transient and the persistent
+ * autostart or activation policy setting of the module is not modified.
+ */
+ TRANSIENT,
+ /**
+ * The module start operation must activate the module according to the module's declared
+ * activation policy.
+ */
+ USE_ACTIVATION_POLICY,
+ /**
+ * The module start operation is transient and the persistent activation policy
+ * setting will be used.
+ */
+ TRANSIENT_RESUME,
+ /**
+ * The module start operation is transient and will only happen if {@link Settings#AUTO_START auto start}
+ * setting is persistent.
+ */
+ TRANSIENT_IF_AUTO_START,
+ /**
+ * The module start operation that indicates the module is being started because of a
+ * lazy start trigger class load. This option must be used with the
+ * {@link StartOptions#TRANSIENT transient} options.
+ */
+ LAZY_TRIGGER;
+
+ /**
+ * Tests if this option is contained in the specified options
+ */
+ public boolean isContained(StartOptions... options) {
+ for (StartOptions option : options) {
+ if (equals(option)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ /**
+ * The possible start options for a module
+ */
+ public static enum StopOptions {
+ /**
+ * The module stop operation is transient and the persistent
+ * autostart setting of the module is not modified.
+ */
+ TRANSIENT;
+
+ /**
+ * Tests if this option is contained in the specified options
+ */
+ public boolean isContained(StopOptions... options) {
+ for (StopOptions option : options) {
+ if (equals(option)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ /**
+ * An enumeration of the possible {@link Module#getState() states} a module may be in.
+ */
+ public static enum State {
+ /**
+ * The module is installed but not yet resolved.
+ */
+ INSTALLED,
+ /**
+ * The module is resolved and able to be started.
+ */
+ RESOLVED,
+ /**
+ * The module is waiting for a {@link StartOptions#LAZY_TRIGGER trigger}
+ * class load to proceed with starting.
+ */
+ LAZY_STARTING,
+ /**
+ * The module is in the process of starting.
+ */
+ STARTING,
+ /**
+ * The module is now running.
+ */
+ ACTIVE,
+ /**
+ * The module is in the process of stopping
+ */
+ STOPPING,
+ /**
+ * The module is uninstalled and may not be used.
+ */
+ UNINSTALLED
+ }
+
+ /**
+ * Event types that may be {@link Module#publishEvent(Event) published} for a module
+ * indicating a {@link Module#getState() state} change has occurred for a module.
+ */
+ public static enum Event {
+ /**
+ * The module has been installed
+ */
+ INSTALLED,
+ /**
+ * The module has been activated with the lazy activation policy and
+ * is waiting a {@link StartOptions#LAZY_TRIGGER trigger} class load.
+ */
+ LAZY_ACTIVATION,
+ /**
+ * The module has been resolved.
+ */
+ RESOLVED,
+ /**
+ * The module has beens started.
+ */
+ STARTED,
+ /**
+ * The module is about to be activated.
+ */
+ STARTING,
+ /**
+ * The module has been stopped.
+ */
+ STOPPED,
+ /**
+ * The module is about to be deactivated.
+ */
+ STOPPING,
+ /**
+ * The module has been uninstalled.
+ */
+ UNINSTALLED,
+ /**
+ * The module has been unresolved.
+ */
+ UNRESOLVED,
+ /**
+ * The module has been updated.
+ */
+ UPDATED
+ }
+
+ /**
+ * An enumeration of persistent settings for a module
+ */
+ public static enum Settings {
+ /**
+ * The module has been set to auto start.
+ */
+ AUTO_START,
+ /**
+ * The module has been set to use its activation policy
+ */
+ USE_ACTIVATION_POLICY,
+ }
+
+ /**
+ * A set of {@link State states} that indicate a module is active.
+ */
+ public static final EnumSet<State> ACTIVE_SET = EnumSet.of(State.STARTING, State.LAZY_STARTING, State.ACTIVE, State.STOPPING);
+ /**
+ * A set of {@link State states} that indicate a module is resolved.
+ */
+ public static final EnumSet<State> RESOLVED_SET = EnumSet.of(State.RESOLVED, State.STARTING, State.LAZY_STARTING, State.ACTIVE, State.STOPPING);
+
+ private final Long id;
+ private final String location;
+ private final ModuleRevisions revisions;
+ private final ReentrantLock stateChangeLock = new ReentrantLock();
+ private final EnumSet<Event> stateTransitionEvents = EnumSet.noneOf(Event.class);
+ private final EnumSet<Settings> settings;
+ private volatile State state = State.INSTALLED;
+ private volatile int startlevel;
+ private volatile long lastModified;
+
+ /**
+ * Constructs a new module with the specified id, location and
+ * container.
+ * @param id the new module id
+ * @param location the new module location
+ * @param container the container for the new module
+ * @param settings the persisted settings. May be {@code null} if there are no settings.
+ * @param startlevel the persisted start level or initial start level.
+ */
+ public Module(Long id, String location, ModuleContainer container, EnumSet<Settings> settings, int startlevel) {
+ this.id = id;
+ this.location = location;
+ this.revisions = new ModuleRevisions(this, container);
+ this.settings = settings == null ? EnumSet.noneOf(Settings.class) : settings;
+ this.startlevel = startlevel;
+ }
+
+ /**
+ * Returns the module id.
+ * @return the module id.
+ */
+ public Long getId() {
+ return id;
+ }
+
+ /** Returns the module location
+ * @return the module location
+ */
+ public String getLocation() {
+ return location;
+ }
+
+ /**
+ * Returns the {@link ModuleRevisions} associated with this module.
+ * @return the {@link ModuleRevisions} associated with this module
+ */
+ public final ModuleRevisions getRevisions() {
+ return revisions;
+ }
+
+ /**
+ * Returns the current {@link ModuleRevision revision} associated with this module.
+ * If the module is uninstalled then {@code null} is returned.
+ * @return the current {@link ModuleRevision revision} associated with this module.
+ */
+ public final ModuleRevision getCurrentRevision() {
+ return revisions.getCurrentRevision();
+ }
+
+ /**
+ * Returns the current {@link State state} of this module.
+ * @return the current state of this module.
+ */
+ public State getState() {
+ return state;
+ }
+
+ void setState(State state) {
+ this.state = state;
+ }
+
+ @Override
+ public int getStartLevel() {
+ return this.startlevel;
+ }
+
+ @Override
+ public void setStartLevel(int startLevel) {
+ revisions.getContainer().setStartLevel(this, startLevel);
+ }
+
+ @Override
+ public boolean isPersistentlyStarted() {
+ return settings.contains(Settings.AUTO_START);
+ }
+
+ @Override
+ public boolean isActivationPolicyUsed() {
+ return settings.contains(Settings.USE_ACTIVATION_POLICY);
+ }
+
+ void storeStartLevel(int newStartLevel) {
+ this.startlevel = newStartLevel;
+ }
+
+ /**
+ * Returns the time when this module was last modified. A module is considered
+ * to be modified when it is installed, updated or uninstalled.
+ * <p>
+ * The time value is a the number of milliseconds since January 1, 1970, 00:00:00 UTC.
+ * @return the time when this bundle was last modified.
+ */
+ public long getLastModified() {
+ return this.lastModified;
+ }
+
+ void setlastModified(long lastModified) {
+ this.lastModified = lastModified;
+ }
+
+ private static final EnumSet<Event> VALID_RESOLVED_TRANSITION = EnumSet.of(Event.STARTED);
+ private static final EnumSet<Event> VALID_STOPPED_TRANSITION = EnumSet.of(Event.UPDATED, Event.UNRESOLVED, Event.UNINSTALLED);
+
+ /**
+ * Acquires the module lock for state changes by the current thread for the specified
+ * transition event. Certain transition events locks may be nested within other
+ * transition event locks. For example, a resolved transition event lock may be
+ * nested within a started transition event lock. A stopped transition lock
+ * may be nested within an updated, unresolved or uninstalled transition lock.
+ * @param transitionEvent the transition event to acquire the lock for.
+ * @throws BundleException
+ */
+ protected void lockStateChange(Event transitionEvent) throws BundleException {
+ try {
+ boolean acquired = stateChangeLock.tryLock(5, TimeUnit.SECONDS);
+ if (acquired) {
+ boolean isValidTransition = true;
+ switch (transitionEvent) {
+ case STARTED :
+ case UPDATED :
+ case UNINSTALLED :
+ case UNRESOLVED :
+ // These states must be initiating transition states
+ // no other transition state is allowed when these are kicked off
+ isValidTransition = stateTransitionEvents.isEmpty();
+ break;
+ case RESOLVED :
+ isValidTransition = VALID_RESOLVED_TRANSITION.containsAll(stateTransitionEvents);
+ break;
+ case STOPPED :
+ isValidTransition = VALID_STOPPED_TRANSITION.containsAll(stateTransitionEvents);
+ break;
+ default :
+ isValidTransition = false;
+ break;
+ }
+ if (!isValidTransition) {
+ stateChangeLock.unlock();
+ } else {
+ stateTransitionEvents.add(transitionEvent);
+ return;
+ }
+ }
+ throw new BundleException("Unable to acquire the state change lock for the module: " + transitionEvent, BundleException.STATECHANGE_ERROR);
+ } catch (InterruptedException e) {
+ throw new BundleException("Unable to acquire the state change lock for the module.", BundleException.STATECHANGE_ERROR, e);
+ }
+ }
+
+ /**
+ * Releases the lock for state changes for the specified transition event.
+ * @param transitionEvent
+ */
+ protected void unlockStateChange(Event transitionEvent) {
+ if (stateChangeLock.getHoldCount() == 0 || !stateTransitionEvents.contains(transitionEvent))
+ throw new IllegalMonitorStateException("Current thread does not hold the state change lock for: " + transitionEvent);
+ stateTransitionEvents.remove(transitionEvent);
+ stateChangeLock.unlock();
+ }
+
+ /**
+ * Returns true if the current thread holds the state change lock for the specified transition event.
+ * @param transitionEvent
+ * @return true if the current thread holds the state change lock for the specified transition event.
+ */
+ protected boolean holdsTransitionEventLock(Event transitionEvent) {
+ return stateChangeLock.getHoldCount() > 0 && stateTransitionEvents.contains(transitionEvent);
+ }
+
+ /**
+ * Starts this module
+ * @param options the options for starting
+ * @throws BundleException if an errors occurs while starting
+ */
+ public void start(StartOptions... options) throws BundleException {
+ revisions.getContainer().checkAdminPermission(getBundle(), AdminPermission.EXECUTE);
+ if (options == null) {
+ options = new StartOptions[0];
+ }
+ Event event;
+ if (StartOptions.LAZY_TRIGGER.isContained(options)) {
+ if (stateChangeLock.getHoldCount() > 0 && stateTransitionEvents.contains(Event.STARTED)) {
+ // nothing to do here; the current thread is activating the bundle.
+ }
+ }
+ BundleException startError = null;
+ lockStateChange(Event.STARTED);
+ try {
+ checkValid();
+ if (StartOptions.TRANSIENT_IF_AUTO_START.isContained(options) && !settings.contains(Settings.AUTO_START)) {
+ // Do nothing
+ return;
+ }
+ persistStartOptions(options);
+ if (getStartLevel() > getRevisions().getContainer().getStartLevel()) {
+ if (StartOptions.TRANSIENT.isContained(options)) {
+ throw new BundleException("Cannot transiently start a module whose start level is not met.", BundleException.START_TRANSIENT_ERROR);
+ }
+ // DO nothing
+ return;
+ }
+ // TODO need a check to see if the current revision is valid for start (e.g. is fragment).
+ if (State.ACTIVE.equals(getState()))
+ return;
+ if (getState().equals(State.INSTALLED)) {
+ try {
+ getRevisions().getContainer().resolve(Arrays.asList(this), true);
+ } catch (ResolutionException e) {
+ throw new BundleException("Could not resolve module.", BundleException.RESOLVE_ERROR, e);
+ }
+ }
+ if (getState().equals(State.INSTALLED)) {
+ throw new BundleException("Could not resolve module.", BundleException.RESOLVE_ERROR);
+ }
+ try {
+ event = doStart(options);
+ } catch (BundleException e) {
+ // must return state to resolved
+ setState(State.RESOLVED);
+ startError = e;
+ // must always publish the STOPPED event on error
+ event = Event.STOPPED;
+ }
+ } finally {
+ unlockStateChange(Event.STARTED);
+ }
+
+ if (event != null) {
+ if (!EnumSet.of(Event.STARTED, Event.LAZY_ACTIVATION, Event.STOPPED).contains(event))
+ throw new IllegalStateException("Wrong event type: " + event);
+ publishEvent(event);
+ }
+
+ if (startError != null) {
+ throw startError;
+ }
+ }
+
+ /**
+ * Stops this module.
+ * @param options options for stopping
+ * @throws BundleException if an error occurs while stopping
+ */
+ public void stop(StopOptions... options) throws BundleException {
+ revisions.getContainer().checkAdminPermission(getBundle(), AdminPermission.EXECUTE);
+ if (options == null)
+ options = new StopOptions[0];
+ Event event;
+ BundleException stopError = null;
+ lockStateChange(Event.STOPPED);
+ try {
+ checkValid();
+ persistStopOptions(options);
+ if (!Module.ACTIVE_SET.contains(getState()))
+ return;
+ try {
+ event = doStop();
+ } catch (BundleException e) {
+ stopError = e;
+ // must always publish the STOPPED event
+ event = Event.STOPPED;
+ }
+ } finally {
+ unlockStateChange(Event.STOPPED);
+ }
+
+ if (event != null) {
+ if (!Event.STOPPED.equals(event))
+ throw new IllegalStateException("Wrong event type: " + event);
+ publishEvent(event);
+ }
+ if (stopError != null)
+ throw stopError;
+ }
+
+ @Override
+ public int compareTo(Module o) {
+ int slcomp = getStartLevel() - o.getStartLevel();
+ if (slcomp != 0) {
+ return slcomp;
+ }
+ long idcomp = getId() - o.getId();
+ return (idcomp < 0L) ? -1 : ((idcomp > 0L) ? 1 : 0);
+ }
+
+ void checkValid() {
+ if (getState().equals(State.UNINSTALLED))
+ throw new IllegalStateException("Module has been uninstalled.");
+ }
+
+ private Event doStart(StartOptions... options) throws BundleException {
+ boolean isLazyTrigger = StartOptions.LAZY_TRIGGER.isContained(options);
+ if (isLazyTrigger) {
+ if (!State.LAZY_STARTING.equals(getState())) {
+ // need to make sure we transition through the lazy starting state
+ setState(State.LAZY_STARTING);
+ // need to publish the lazy event
+ unlockStateChange(Event.STARTED);
+ try {
+ publishEvent(Event.LAZY_ACTIVATION);
+ } finally {
+ lockStateChange(Event.STARTED);
+ }
+ if (State.ACTIVE.equals(getState())) {
+ // A sync listener must have caused the bundle to activate
+ return null;
+ }
+ // continue on to normal starting
+ }
+ } else {
+ if (settings.contains(Settings.USE_ACTIVATION_POLICY) && isLazyActivate()) {
+ if (State.LAZY_STARTING.equals(getState())) {
+ // a sync listener must have tried to start this module again with the lazy option
+ return null; // no event to publish; nothing to do
+ }
+ // set the lazy starting state and return lazy activation event for firing
+ setState(State.LAZY_STARTING);
+ return Event.LAZY_ACTIVATION;
+ }
+ }
+
+ // time to actual start the module
+ if (!State.STARTING.equals(getState())) {
+ // TODO this starting state check should not be needed
+ // but we do it because of the way the system module init works
+ setState(State.STARTING);
+ publishEvent(Event.STARTING);
+ }
+ try {
+ startWorker();
+ setState(State.ACTIVE);
+ return Event.STARTED;
+ } catch (Throwable t) {
+ // must fire stopping event
+ setState(State.STOPPING);
+ publishEvent(Event.STOPPING);
+ if (t instanceof BundleException)
+ throw (BundleException) t;
+ throw new BundleException("Error starting module.", BundleException.ACTIVATOR_ERROR, t);
+ }
+ }
+
+ /**
+ * Performs any work associated with starting a module. For example,
+ * loading and calling start on an activator.
+ * @throws BundleException
+ */
+ protected void startWorker() throws BundleException {
+ // Do nothing
+ }
+
+ private Event doStop() throws BundleException {
+ setState(State.STOPPING);
+ publishEvent(Event.STOPPING);
+ try {
+ stopWorker();
+ return Event.STOPPED;
+ } catch (Throwable t) {
+ if (t instanceof BundleException)
+ throw (BundleException) t;
+ throw new BundleException("Error stopping module.", BundleException.ACTIVATOR_ERROR, t);
+ } finally {
+ // must always set the state to stopped
+ setState(State.RESOLVED);
+ }
+ }
+
+ /**
+ * @throws BundleException
+ */
+ protected void stopWorker() throws BundleException {
+ // Do nothing
+ }
+
+ /**
+ * @throws BundleException
+ */
+ protected void updateWorker(ModuleRevisionBuilder builder) throws BundleException {
+ // do nothing
+ }
+
+ @Override
+ public String toString() {
+ return "[id=" + id + "]";
+ }
+
+ /**
+ * Publishes the specified event for this module.
+ * @param event the event type to publish
+ */
+ abstract protected void publishEvent(Event event);
+
+ private void persistStartOptions(StartOptions... options) {
+ if (StartOptions.TRANSIENT_RESUME.isContained(options) || StartOptions.LAZY_TRIGGER.isContained(options)) {
+ return;
+ }
+
+ // Always set the use acivation policy setting
+ if (StartOptions.USE_ACTIVATION_POLICY.isContained(options)) {
+ settings.add(Settings.USE_ACTIVATION_POLICY);
+ } else {
+ settings.remove(Settings.USE_ACTIVATION_POLICY);
+ }
+
+ if (StartOptions.TRANSIENT.isContained(options)) {
+ return;
+ }
+ settings.add(Settings.AUTO_START);
+ revisions.getContainer().moduleDataBase.persistSettings(settings, this);
+ }
+
+ private void persistStopOptions(StopOptions... options) {
+ if (StopOptions.TRANSIENT.isContained(options))
+ return;
+ settings.clear();
+ revisions.getContainer().moduleDataBase.persistSettings(settings, this);
+ }
+
+ /**
+ * The container is done with the revision and it has been complete removed.
+ * This method allows the resources behind the revision to be cleaned up.
+ * @param revision the revision to clean up
+ */
+ abstract protected void cleanup(ModuleRevision revision);
+
+ boolean isLazyActivate() {
+ ModuleRevision current = getCurrentRevision();
+ if (current == null)
+ return false;
+ List<Capability> capabilities = current.getCapabilities(EquinoxModuleDataNamespace.MODULE_DATA_NAMESPACE);
+ if (capabilities.isEmpty())
+ return false;
+ Capability moduleData = capabilities.get(0);
+ return EquinoxModuleDataNamespace.CAPABILITY_ACTIVATION_POLICY_LAZY.equals(moduleData.getAttributes().get(EquinoxModuleDataNamespace.CAPABILITY_ACTIVATION_POLICY));
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleCapability.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleCapability.java
new file mode 100644
index 000000000..83e0f67c8
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleCapability.java
@@ -0,0 +1,61 @@
+/*******************************************************************************
+ * Copyright (c) 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.container;
+
+import java.util.Map;
+import org.osgi.framework.wiring.BundleCapability;
+
+/**
+ * An implementation of {@link BundleCapability}.
+ */
+public class ModuleCapability implements BundleCapability {
+ private final String namespace;
+ private final Map<String, String> directives;
+ private final Map<String, Object> attributes;
+ private final ModuleRevision revision;
+
+ ModuleCapability(String namespace, Map<String, String> directives, Map<String, Object> attributes, ModuleRevision revision) {
+ this.namespace = namespace;
+ this.directives = directives;
+ this.attributes = attributes;
+ this.revision = revision;
+ }
+
+ @Override
+ public ModuleRevision getRevision() {
+ return revision;
+ }
+
+ @Override
+ public String getNamespace() {
+ return namespace;
+ }
+
+ @Override
+ public Map<String, String> getDirectives() {
+ return directives;
+ }
+
+ @Override
+ public Map<String, Object> getAttributes() {
+ return attributes;
+ }
+
+ @Override
+ public ModuleRevision getResource() {
+ return revision;
+ }
+
+ @Override
+ public String toString() {
+ return namespace + ModuleRevision.toString(attributes, false) + ModuleRevision.toString(directives, true);
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleClassLoader.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleClassLoader.java
new file mode 100644
index 000000000..475eb40ab
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleClassLoader.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright (c) 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.container;
+
+import java.net.URL;
+import java.util.Collection;
+import java.util.List;
+
+public interface ModuleClassLoader {
+ /**
+ *
+ * @param path
+ * @param filePattern
+ * @param options
+ * @return TODO
+ * @see ModuleWiring#findEntries(String, String, int)
+ */
+ public List<URL> findEntries(String path, String filePattern, int options);
+
+ /**
+ *
+ * @param path
+ * @param filePattern
+ * @param options
+ * @return TODO
+ * @see ModuleWiring#listResources(String, String, int)
+ */
+ public Collection<String> listResources(String path, String filePattern, int options);
+
+ /**
+ * Closes this module class loader and all of its associated resources
+ */
+ public void close();
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleCollisionHook.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleCollisionHook.java
new file mode 100644
index 000000000..94b562c9f
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleCollisionHook.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * Copyright (c) 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.container;
+
+import java.util.Collection;
+
+/**
+ * Hook used to determine if a module revision being installed or updated will cause a collision
+
+ */
+public interface ModuleCollisionHook {
+
+ /**
+ * Specifies a module install operation is being performed.
+ */
+ int INSTALLING = 1;
+
+ /**
+ * Specifies a module update operation is being performed.
+ */
+ int UPDATING = 2;
+
+ /**
+ * Filter bundle collisions hook method. This method is called during the
+ * install or update operation. The operation type will be
+ * {@link #INSTALLING installing} or {@link #UPDATING updating}. Depending
+ * on the operation type the target module and the collision candidate
+ * collection are the following:
+ * <ul>
+ * <li> {@link #INSTALLING installing} - The target is the module associated
+ * which is performing the install operation. The
+ * collision candidate collection contains the existing modules installed
+ * which have a current revision with the same symbolic name and version as the
+ * module being installed.
+ * <li> {@link #UPDATING updating} - The target is the module being updated.
+ * The collision candidate collection contains the existing modules installed which have
+ * a current revision with the same symbolic name and version as the content the target
+ * module is being updated to.
+ * </ul>
+ * This method can filter the collection of collision candidates by removing
+ * potential collisions. For the specified operation to succeed, the
+ * collection of collision candidates must be empty when this method returns.
+ *
+ * @param operationType The operation type. Must be the value of
+ * {@link #INSTALLING installing} or {@link #UPDATING updating}.
+ * @param target The target module used to determine what collision
+ * candidates to filter.
+ * @param collisionCandidates The collection of collision candidates.
+ */
+ void filterCollisions(int operationType, Module target, Collection<Module> collisionCandidates);
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleContainer.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleContainer.java
new file mode 100644
index 000000000..c7e0abc25
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleContainer.java
@@ -0,0 +1,1156 @@
+/*******************************************************************************
+ * Copyright (c) 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.container;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.eclipse.osgi.container.Module.Event;
+import org.eclipse.osgi.container.Module.StartOptions;
+import org.eclipse.osgi.container.Module.State;
+import org.eclipse.osgi.container.Module.StopOptions;
+import org.eclipse.osgi.container.ModuleContainerAdaptor.ContainerEvent;
+import org.eclipse.osgi.container.ModuleDataBase.Sort;
+import org.eclipse.osgi.container.ModuleRequirement.DynamicModuleRequirement;
+import org.eclipse.osgi.framework.eventmgr.*;
+import org.eclipse.osgi.internal.container.LockSet;
+import org.osgi.framework.*;
+import org.osgi.framework.namespace.HostNamespace;
+import org.osgi.framework.namespace.PackageNamespace;
+import org.osgi.framework.startlevel.FrameworkStartLevel;
+import org.osgi.framework.wiring.BundleRevision;
+import org.osgi.framework.wiring.FrameworkWiring;
+import org.osgi.service.resolver.ResolutionException;
+
+/**
+ * A container for installing, updating, uninstalling and resolve modules.
+ *
+ */
+public final class ModuleContainer {
+
+ /**
+ * Used by install operations to establish a write lock on an install location
+ */
+ private final LockSet<String> locationLocks = new LockSet<String>(false);
+
+ /**
+ * Used by install and update operations to establish a write lock for a name
+ */
+ private final LockSet<String> nameLocks = new LockSet<String>(false);
+
+ /**
+ * An implementation of FrameworkWiring for this container
+ */
+ private final ContainerWiring frameworkWiring;
+
+ /**
+ * An implementation of FrameworkStartLevel for this container
+ */
+ private final ContainerStartLevel frameworkStartLevel;
+
+ /**
+ * The module database for this container. All access to this database MUST
+ * be guarded by the database lock
+ */
+ /* @GuardedBy("moduleDataBase") */
+ final ModuleDataBase moduleDataBase;
+
+ final ModuleContainerAdaptor adaptor;
+
+ /**
+ * The module resolver which implements the ResolverContext and handles calling the
+ * resolver service.
+ */
+ private final ModuleResolver moduleResolver;
+
+ /**
+ * Constructs a new container with the specified collision hook, resolver hook, resolver and module database.
+ * @param adaptor the adaptor for the container
+ * @param moduleDataBase the module database
+ */
+ public ModuleContainer(ModuleContainerAdaptor adaptor, ModuleDataBase moduleDataBase) {
+ this.adaptor = adaptor;
+ this.moduleResolver = new ModuleResolver(adaptor);
+ this.moduleDataBase = moduleDataBase;
+ this.frameworkWiring = new ContainerWiring();
+ this.frameworkStartLevel = new ContainerStartLevel();
+ }
+
+ /**
+ * Returns the list of currently installed modules sorted by module id.
+ * @return the list of currently installed modules sorted by module id.
+ */
+ public List<Module> getModules() {
+ return moduleDataBase.getModules();
+ }
+
+ /**
+ * Returns the module installed with the specified id, or null if no
+ * such module is installed.
+ * @param id the id of the module
+ * @return the module with the specified id, or null of no such module is installed.
+ */
+ public Module getModule(long id) {
+ return moduleDataBase.getModule(id);
+ }
+
+ /**
+ * Returns the module installed with the specified location, or null if no
+ * such module is installed.
+ * @param location the location of the module
+ * @return the module with the specified location, or null of no such module is installed.
+ */
+ public Module getModule(String location) {
+ return moduleDataBase.getModule(location);
+ }
+
+ /**
+ * Installs a new module using the specified location. The specified
+ * builder is used to create a new {@link ModuleRevision revision}
+ * which will become the {@link Module#getCurrentRevision() current}
+ * revision of the new module.
+ * <p>
+ * If a module already exists with the specified location then the
+ * existing module is returned and the builder is not used.
+ * @param origin the module performing the install, may be {@code null}.
+ * @param location The location identifier of the module to install.
+ * @param builder the builder used to create the revision to install.
+ * @return a new module or a existing module if one exists at the
+ * specified location.
+ * @throws BundleException if some error occurs installing the module
+ */
+ public Module install(Module origin, String location, ModuleRevisionBuilder builder) throws BundleException {
+ String name = builder.getSymbolicName();
+ boolean locationLocked = false;
+ boolean nameLocked = false;
+ try {
+ // Attempt to lock the location and name
+ try {
+ locationLocked = locationLocks.tryLock(location, 5, TimeUnit.SECONDS);
+ nameLocked = name != null && nameLocks.tryLock(name, 5, TimeUnit.SECONDS);
+ if (!locationLocked) {
+ throw new BundleException("Failed to obtain location lock for installation: " + location, BundleException.STATECHANGE_ERROR);
+ }
+ if (name != null && !nameLocked) {
+ throw new BundleException("Failed to obtain symbolic name lock for installation: " + name, BundleException.STATECHANGE_ERROR);
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new BundleException("Failed to obtain id locks for installation.", BundleException.STATECHANGE_ERROR, e);
+ }
+
+ Module existingLocation = null;
+ Collection<Module> collisionCandidates = Collections.emptyList();
+ moduleDataBase.lockRead();
+ try {
+ existingLocation = moduleDataBase.getModule(location);
+ if (existingLocation == null) {
+ // Collect existing current revisions with the same name and version as the revision we want to install
+ // This is to perform the collision check below
+ Collection<ModuleRevision> existingRevisionNames = moduleDataBase.getRevisions(name, builder.getVersion());
+ if (!existingRevisionNames.isEmpty()) {
+ collisionCandidates = new ArrayList<Module>(1);
+ for (ModuleRevision equinoxRevision : existingRevisionNames) {
+ if (!equinoxRevision.isCurrent())
+ continue; // only pay attention to current revisions
+ // need to prevent duplicates here; this is in case a revisions object contains multiple revision objects.
+ if (!collisionCandidates.contains(equinoxRevision.getRevisions().getModule()))
+ collisionCandidates.add(equinoxRevision.getRevisions().getModule());
+ }
+ }
+ }
+ } finally {
+ moduleDataBase.unlockRead();
+ }
+ // Check that the existing location is visible from the origin module
+ if (existingLocation != null) {
+ if (origin != null) {
+ Bundle bundle = origin.getBundle();
+ BundleContext context = bundle == null ? null : bundle.getBundleContext();
+ if (context != null && context.getBundle(existingLocation.getId()) == null) {
+ Bundle b = existingLocation.getBundle();
+ throw new BundleException("Bundle \"" + b.getSymbolicName() + "\" version \"" + b.getVersion() + "\" is already installed at location: " + location, BundleException.REJECTED_BY_HOOK);
+ }
+ }
+ return existingLocation;
+ }
+ // Check that the bundle does not collide with other bundles with the same name and version
+ // This is from the perspective of the origin bundle
+ if (origin != null && !collisionCandidates.isEmpty()) {
+ adaptor.getModuleCollisionHook().filterCollisions(ModuleCollisionHook.INSTALLING, origin, collisionCandidates);
+ }
+ if (!collisionCandidates.isEmpty()) {
+ throw new BundleException("A bundle is already installed with name \"" + name + "\" and version \"" + builder.getVersion(), BundleException.DUPLICATE_BUNDLE_ERROR);
+ }
+
+ Module result = moduleDataBase.install(location, builder);
+
+ result.publishEvent(Event.INSTALLED);
+
+ return result;
+ } finally {
+ if (locationLocked)
+ locationLocks.unlock(location);
+ if (nameLocked)
+ nameLocks.unlock(name);
+ }
+ }
+
+ /**
+ * Updates the specified module with a new revision. The specified
+ * builder is used to create a new {@link ModuleRevision revision}
+ * which will become the {@link Module#getCurrentRevision() current}
+ * revision of the new module.
+ * @param module the module to update
+ * @param builder the builder used to create the revision for the update.
+ * @throws BundleException if some error occurs updating the module
+ */
+ public void update(Module module, ModuleRevisionBuilder builder) throws BundleException {
+ String name = builder.getSymbolicName();
+ boolean nameLocked = false;
+ try {
+ // Attempt to lock the name
+ try {
+ nameLocked = name != null && nameLocks.tryLock(name, 5, TimeUnit.SECONDS);
+ if (!nameLocked) {
+ throw new BundleException("Failed to obtain id locks for installation.", BundleException.STATECHANGE_ERROR);
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new BundleException("Failed to obtain id locks for installation.", BundleException.STATECHANGE_ERROR, e);
+ }
+
+ Collection<Module> collisionCandidates = Collections.emptyList();
+ moduleDataBase.lockRead();
+ try {
+ // Collect existing bundles with the same name and version as the bundle we want to install
+ // This is to perform the collision check below
+ Collection<ModuleRevision> existingRevisionNames = moduleDataBase.getRevisions(name, builder.getVersion());
+ if (!existingRevisionNames.isEmpty()) {
+ collisionCandidates = new ArrayList<Module>(1);
+ for (ModuleRevision equinoxRevision : existingRevisionNames) {
+ if (!equinoxRevision.isCurrent())
+ continue;
+ Module m = equinoxRevision.getRevisions().getModule();
+ if (m.equals(module))
+ continue; // don't worry about the updating modules revisions
+ // need to prevent duplicates here; this is in case a revisions object contains multiple revision objects.
+ if (!collisionCandidates.contains(m))
+ collisionCandidates.add(m);
+ }
+ }
+
+ } finally {
+ moduleDataBase.unlockRead();
+ }
+
+ // Check that the module does not collide with other modules with the same name and version
+ // This is from the perspective of the module being updated
+ if (module != null && !collisionCandidates.isEmpty()) {
+ adaptor.getModuleCollisionHook().filterCollisions(ModuleCollisionHook.UPDATING, module, collisionCandidates);
+ }
+
+ if (!collisionCandidates.isEmpty()) {
+ throw new BundleException("A bundle is already installed with name \"" + name + "\" and version \"" + builder.getVersion(), BundleException.DUPLICATE_BUNDLE_ERROR);
+ }
+
+ module.lockStateChange(Event.UPDATED);
+ State previousState = module.getState();
+ BundleException updateError = null;
+ try {
+ // throwing an exception from stop terminates update
+ module.stop(StopOptions.TRANSIENT);
+ try {
+ // throwing an exception from updateWorker keeps the previous revision
+ module.updateWorker(builder);
+ if (Module.RESOLVED_SET.contains(previousState)) {
+ // set the state to installed and publish unresolved event
+ module.setState(State.INSTALLED);
+ module.publishEvent(Event.UNRESOLVED);
+ }
+ moduleDataBase.update(module, builder);
+ } catch (BundleException e) {
+ updateError = e;
+ }
+
+ } finally {
+ module.unlockStateChange(Event.UPDATED);
+ }
+ if (updateError == null) {
+ // only publish updated event on success
+ module.publishEvent(Event.UPDATED);
+ }
+ if (Module.ACTIVE_SET.contains(previousState)) {
+ // restart the module if necessary
+ module.start(StartOptions.TRANSIENT_RESUME);
+ }
+ if (updateError != null) {
+ // throw cause of update error
+ throw updateError;
+ }
+ } finally {
+ if (nameLocked)
+ nameLocks.unlock(name);
+ }
+ }
+
+ /**
+ * Uninstalls the specified module.
+ * @param module the module to uninstall
+ * @throws BundleException if some error occurs uninstalling the module
+ */
+ public void uninstall(Module module) throws BundleException {
+ module.lockStateChange(Event.UNINSTALLED);
+ try {
+ if (Module.ACTIVE_SET.equals(module.getState())) {
+ try {
+ module.stop(StopOptions.TRANSIENT);
+ } catch (BundleException e) {
+ adaptor.publishContainerEvent(ContainerEvent.ERROR, module, e);
+ }
+ }
+ moduleDataBase.uninstall(module);
+ module.setState(State.UNINSTALLED);
+ } finally {
+ module.unlockStateChange(Event.UNINSTALLED);
+ }
+ module.publishEvent(Event.UNINSTALLED);
+ }
+
+ ModuleWiring getWiring(ModuleRevision revision) {
+ return moduleDataBase.getWiring(revision);
+ }
+
+ /**
+ * Returns the {@link FrameworkWiring} for this container
+ * @return the framework wiring for this container.
+ */
+ public FrameworkWiring getFrameworkWiring() {
+ return frameworkWiring;
+ }
+
+ /**
+ * Returns the {@link FrameworkStartLevel} for this container
+ * @return the framework start level for this container
+ */
+ public FrameworkStartLevel getFrameworkStartLevel() {
+ return frameworkStartLevel;
+ }
+
+ /**
+ * Attempts to resolve the current revisions of the specified modules.
+ * @param triggers the modules to resolve or {@code null} to resolve all unresolved
+ * current revisions.
+ * @param triggersMandatory true if the triggers must be resolved. This will result in
+ * a {@link ResolutionException} if set to true and one of the triggers could not be resolved.
+ * @throws ResolutionException if a resolution error occurs
+ * @see FrameworkWiring#resolveBundles(Collection)
+ */
+ public void resolve(Collection<Module> triggers, boolean triggersMandatory) throws ResolutionException {
+ while (!resolve0(triggers, triggersMandatory)) {
+ // nothing
+ }
+ }
+
+ private boolean resolve0(Collection<Module> triggers, boolean triggersMandatory) throws ResolutionException {
+ if (triggers == null)
+ triggers = new ArrayList<Module>(0);
+ Collection<ModuleRevision> triggerRevisions = new ArrayList<ModuleRevision>(triggers.size());
+ Collection<ModuleRevision> unresolved = new ArrayList<ModuleRevision>();
+ Map<ModuleRevision, ModuleWiring> wiringClone;
+ long timestamp;
+ moduleDataBase.lockRead();
+ try {
+ timestamp = moduleDataBase.getTimestamp();
+ wiringClone = moduleDataBase.getWiringsClone();
+ for (Module module : triggers) {
+ ModuleRevision current = module.getCurrentRevision();
+ if (current != null)
+ triggerRevisions.add(current);
+ }
+ Collection<Module> allModules = moduleDataBase.getModules();
+ for (Module module : allModules) {
+ ModuleRevision revision = module.getCurrentRevision();
+ if (revision != null && !wiringClone.containsKey(revision))
+ unresolved.add(revision);
+ }
+ } finally {
+ moduleDataBase.unlockRead();
+ }
+
+ Map<ModuleRevision, ModuleWiring> deltaWiring = moduleResolver.resolveDelta(triggerRevisions, triggersMandatory, unresolved, wiringClone, moduleDataBase);
+ if (deltaWiring.isEmpty())
+ return true; // nothing to do
+
+ Collection<Module> modulesResolved = new ArrayList<Module>();
+ for (ModuleRevision deltaRevision : deltaWiring.keySet()) {
+ if (!wiringClone.containsKey(deltaRevision))
+ modulesResolved.add(deltaRevision.getRevisions().getModule());
+ }
+
+ return applyDelta(deltaWiring, modulesResolved, timestamp);
+ }
+
+ public ModuleWire resolveDynamic(String dynamicPkgName, ModuleRevision revision) throws ResolutionException {
+ ModuleWire result;
+ Map<ModuleRevision, ModuleWiring> deltaWiring;
+ Collection<Module> modulesResolved;
+ long timestamp;
+ do {
+ result = null;
+ Map<ModuleRevision, ModuleWiring> wiringClone = null;
+ List<DynamicModuleRequirement> dynamicReqs = null;
+ Collection<ModuleRevision> unresolved = new ArrayList<ModuleRevision>();
+ moduleDataBase.lockRead();
+ try {
+ dynamicReqs = getDynamicRequirements(dynamicPkgName, revision);
+ if (dynamicReqs.isEmpty()) {
+ // do nothing
+ return null;
+ }
+ timestamp = moduleDataBase.getTimestamp();
+ wiringClone = moduleDataBase.getWiringsClone();
+ Collection<Module> allModules = moduleDataBase.getModules();
+ for (Module module : allModules) {
+ ModuleRevision current = module.getCurrentRevision();
+ if (current != null && !wiringClone.containsKey(current))
+ unresolved.add(current);
+ }
+ } finally {
+ moduleDataBase.unlockRead();
+ }
+
+ deltaWiring = null;
+ for (DynamicModuleRequirement dynamicReq : dynamicReqs) {
+ deltaWiring = moduleResolver.resolveDynamicDelta(dynamicReq, unresolved, wiringClone, moduleDataBase);
+ if (deltaWiring.get(revision) != null) {
+ break;
+ }
+ }
+ if (deltaWiring == null || deltaWiring.get(revision) == null)
+ return null; // nothing to do
+
+ modulesResolved = new ArrayList<Module>();
+ for (ModuleRevision deltaRevision : deltaWiring.keySet()) {
+ if (!wiringClone.containsKey(deltaRevision))
+ modulesResolved.add(deltaRevision.getRevisions().getModule());
+ }
+
+ // Save the result
+ ModuleWiring wiring = deltaWiring.get(revision);
+ if (wiring != null) {
+ List<ModuleWire> wires = wiring.getRequiredModuleWires(null);
+ result = wires.isEmpty() ? null : wires.get(wires.size() - 1);
+ // Doing a sanity check, may not be necessary
+ if (result != null) {
+ if (!PackageNamespace.PACKAGE_NAMESPACE.equals(result.getCapability().getNamespace()) || !dynamicPkgName.equals(result.getCapability().getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE))) {
+ throw new ResolutionException("Resolver provided an invalid dynamic wire: " + result);
+ }
+ }
+ }
+ } while (!applyDelta(deltaWiring, modulesResolved, timestamp));
+
+ return result;
+ }
+
+ private boolean applyDelta(Map<ModuleRevision, ModuleWiring> deltaWiring, Collection<Module> modulesResolved, long timestamp) {
+ Collection<Module> modulesLocked = new ArrayList<Module>(modulesResolved.size());
+ // now attempt to apply the delta
+ try {
+ // acquire the necessary RESOLVED state change lock
+ for (Module module : modulesResolved) {
+ try {
+ module.lockStateChange(Event.RESOLVED);
+ modulesLocked.add(module);
+ } catch (BundleException e) {
+ // TODO throw some appropriate exception
+ throw new IllegalStateException("Could not acquire state change lock.", e);
+ }
+ }
+ moduleDataBase.lockWrite();
+ try {
+ if (timestamp != moduleDataBase.getTimestamp())
+ return false; // need to try again
+ Map<ModuleRevision, ModuleWiring> wiringCopy = moduleDataBase.getWiringsCopy();
+ for (Map.Entry<ModuleRevision, ModuleWiring> deltaEntry : deltaWiring.entrySet()) {
+ ModuleWiring current = wiringCopy.get(deltaEntry.getKey());
+ if (current != null) {
+ // only need to update the provided and required wires for currently resolved
+ current.setProvidedWires(deltaEntry.getValue().getProvidedModuleWires(null));
+ current.setRequiredWires(deltaEntry.getValue().getRequiredModuleWires(null));
+ deltaEntry.setValue(current); // set the real wiring into the delta
+ } else {
+ modulesResolved.add(deltaEntry.getValue().getRevision().getRevisions().getModule());
+ }
+ }
+ moduleDataBase.mergeWiring(deltaWiring);
+ } finally {
+ moduleDataBase.unlockWrite();
+ }
+ // set the modules state to resolved
+ for (Module module : modulesLocked) {
+ module.setState(State.RESOLVED);
+ }
+ } finally {
+ for (Module module : modulesLocked) {
+ module.unlockStateChange(Event.RESOLVED);
+ }
+ }
+
+ for (Module module : modulesLocked) {
+ module.publishEvent(Event.RESOLVED);
+ }
+ return true;
+ }
+
+ private List<DynamicModuleRequirement> getDynamicRequirements(String dynamicPkgName, ModuleRevision revision) {
+ // TODO Will likely need to optimize this
+ if ((revision.getTypes() & BundleRevision.TYPE_FRAGMENT) != 0) {
+ // only do this for hosts
+ return null;
+ }
+ ModuleWiring wiring = revision.getWiring();
+ if (wiring == null) {
+ // not resolved!
+ return null;
+ }
+ List<DynamicModuleRequirement> result = new ArrayList<ModuleRequirement.DynamicModuleRequirement>(1);
+ // check the dynamic import packages
+ DynamicModuleRequirement dynamicRequirement;
+ for (ModuleRequirement requirement : wiring.getModuleRequirements(PackageNamespace.PACKAGE_NAMESPACE)) {
+ dynamicRequirement = requirement.getDynamicPackageRequirement(revision, dynamicPkgName);
+ if (dynamicRequirement != null) {
+ result.add(dynamicRequirement);
+ }
+ }
+
+ return result;
+ }
+
+ private Collection<Module> unresolve(Collection<Module> initial) {
+ Collection<Module> refreshTriggers = null;
+ while (refreshTriggers == null) {
+ refreshTriggers = unresolve0(initial);
+ }
+ return refreshTriggers;
+ }
+
+ private Collection<Module> unresolve0(Collection<Module> initial) {
+ Map<ModuleRevision, ModuleWiring> wiringCopy;
+ Collection<Module> refreshTriggers;
+ Collection<ModuleRevision> toRemoveRevisions;
+ Collection<ModuleWiring> toRemoveWirings;
+ Map<ModuleWiring, Collection<ModuleWire>> toRemoveWireLists;
+ long timestamp;
+ moduleDataBase.lockRead();
+ try {
+ timestamp = moduleDataBase.getTimestamp();
+ wiringCopy = moduleDataBase.getWiringsCopy();
+ refreshTriggers = getRefreshClosure(initial, wiringCopy);
+ toRemoveRevisions = new ArrayList<ModuleRevision>();
+ toRemoveWirings = new ArrayList<ModuleWiring>();
+ toRemoveWireLists = new HashMap<ModuleWiring, Collection<ModuleWire>>();
+ for (Module module : refreshTriggers) {
+ boolean first = true;
+ for (ModuleRevision revision : module.getRevisions().getModuleRevisions()) {
+ ModuleWiring removedWiring = wiringCopy.remove(revision);
+ if (removedWiring != null) {
+ toRemoveWirings.add(removedWiring);
+ List<ModuleWire> removedWires = removedWiring.getRequiredModuleWires(null);
+ for (ModuleWire wire : removedWires) {
+ Collection<ModuleWire> providerWires = toRemoveWireLists.get(wire.getProviderWiring());
+ if (providerWires == null) {
+ providerWires = new ArrayList<ModuleWire>();
+ toRemoveWireLists.put(wire.getProviderWiring(), providerWires);
+ }
+ providerWires.add(wire);
+ }
+ }
+ if (!first || revision.getRevisions().isUninstalled()) {
+ toRemoveRevisions.add(revision);
+ }
+ first = false;
+ }
+ }
+ } finally {
+ moduleDataBase.unlockRead();
+ }
+ Collection<Module> modulesLocked = new ArrayList<Module>(refreshTriggers.size());
+ Collection<Module> modulesUnresolved = new ArrayList<Module>();
+ try {
+ // acquire module state change locks
+ try {
+ for (Module refreshModule : refreshTriggers) {
+ refreshModule.lockStateChange(Event.UNRESOLVED);
+ modulesLocked.add(refreshModule);
+ }
+ } catch (BundleException e) {
+ // TODO throw some appropriate exception
+ throw new IllegalStateException("Could not acquire state change lock.", e);
+ }
+ // Stop any active bundles and remove non-active modules from the refreshTriggers
+ for (Iterator<Module> iTriggers = refreshTriggers.iterator(); iTriggers.hasNext();) {
+ Module refreshModule = iTriggers.next();
+ if (Module.ACTIVE_SET.contains(refreshModule.getState())) {
+ try {
+ refreshModule.stop(StopOptions.TRANSIENT);
+ } catch (BundleException e) {
+ adaptor.publishContainerEvent(ContainerEvent.ERROR, refreshModule, e);
+ }
+ } else {
+ iTriggers.remove();
+ }
+ }
+
+ // do a sanity check on states of the modules, they must be INSTALLED, RESOLVED or UNINSTALLED
+ for (Module module : modulesLocked) {
+ if (Module.ACTIVE_SET.contains(module.getState())) {
+ throw new IllegalStateException("Module is in the wrong state: " + module + ": " + module.getState());
+ }
+ }
+
+ // finally apply the unresolve to the database
+ moduleDataBase.lockWrite();
+ try {
+ if (timestamp != moduleDataBase.getTimestamp())
+ return null; // need to try again
+ // remove any wires from unresolved wirings that got removed
+ for (Map.Entry<ModuleWiring, Collection<ModuleWire>> entry : toRemoveWireLists.entrySet()) {
+ List<ModuleWire> provided = entry.getKey().getProvidedModuleWires(null);
+ provided.removeAll(entry.getValue());
+ entry.getKey().setProvidedWires(provided);
+ for (ModuleWire removedWire : entry.getValue()) {
+ // invalidate the wire
+ removedWire.invalidate();
+ }
+
+ }
+ // remove any revisions that got removed as part of the refresh
+ for (ModuleRevision removed : toRemoveRevisions) {
+ removed.getRevisions().removeRevision(removed);
+ moduleDataBase.removeCapabilities(removed);
+ }
+ // invalidate any removed wiring objects
+ for (ModuleWiring moduleWiring : toRemoveWirings) {
+ moduleWiring.invalidate();
+ }
+ moduleDataBase.setWiring(wiringCopy);
+ } finally {
+ moduleDataBase.unlockWrite();
+ }
+ // set the state of modules to unresolved
+ for (Module module : modulesLocked) {
+ if (State.RESOLVED.equals(module.getState())) {
+ module.setState(State.INSTALLED);
+ modulesUnresolved.add(module);
+ }
+ }
+ } finally {
+ for (Module module : modulesLocked) {
+ module.unlockStateChange(Event.UNRESOLVED);
+ }
+ }
+
+ // publish unresolved events after giving up all locks
+ for (Module module : modulesUnresolved) {
+ module.publishEvent(Event.UNRESOLVED);
+ }
+ return refreshTriggers;
+ }
+
+ /**
+ * Refreshes the specified collection of modules.
+ * @param initial the modules to refresh or {@code null} to refresh the
+ * removal pending.
+ * @throws ResolutionException
+ * @see FrameworkWiring#refreshBundles(Collection, FrameworkListener...)
+ */
+ public void refresh(Collection<Module> initial) throws ResolutionException {
+ Collection<Module> refreshTriggers = unresolve(initial);
+ resolve(refreshTriggers, false);
+ for (Module module : refreshTriggers) {
+ try {
+ module.start(StartOptions.TRANSIENT_RESUME);
+ } catch (BundleException e) {
+ adaptor.publishContainerEvent(ContainerEvent.ERROR, module, e);
+ }
+ }
+ }
+
+ /**
+ * Returns the dependency closure of for the specified modules.
+ * @param initial The initial modules for which to generate the dependency closure
+ * @return A collection containing a snapshot of the dependency closure of the specified
+ * modules, or an empty collection if there were no specified modules.
+ */
+ public Collection<Module> getDependencyClosure(Collection<Module> initial) {
+ moduleDataBase.lockRead();
+ try {
+ return getRefreshClosure(initial, moduleDataBase.getWiringsCopy());
+ } finally {
+ moduleDataBase.unlockRead();
+ }
+ }
+
+ /**
+ * Returns the revisions that have {@link ModuleWiring#isCurrent() non-current}, {@link ModuleWiring#isInUse() in use} module wirings.
+ * @return A collection containing a snapshot of the revisions which have non-current, in use ModuleWirings,
+ * or an empty collection if there are no such revisions.
+ */
+ public Collection<ModuleRevision> getRemovalPending() {
+ return moduleDataBase.getRemovalPending();
+ }
+
+ /**
+ * Return the active start level value of this container.
+ *
+ * If the container is in the process of changing the start level this
+ * method must return the active start level if this differs from the
+ * requested start level.
+ *
+ * @return The active start level value of the Framework.
+ */
+ public int getStartLevel() {
+ return frameworkStartLevel.getStartLevel();
+ }
+
+ void setStartLevel(Module module, int startlevel) {
+ frameworkStartLevel.setStartLevel(module, startlevel);
+ }
+
+ void open() {
+ frameworkStartLevel.open();
+ frameworkWiring.open();
+ }
+
+ void close() {
+ frameworkStartLevel.close();
+ frameworkWiring.close();
+ }
+
+ Collection<Module> getRefreshClosure(Collection<Module> initial, Map<ModuleRevision, ModuleWiring> wiringCopy) {
+ Set<Module> refreshClosure = new HashSet<Module>();
+ if (initial == null) {
+ initial = new HashSet<Module>();
+ Collection<ModuleRevision> removalPending = moduleDataBase.getRemovalPending();
+ for (ModuleRevision revision : removalPending) {
+ initial.add(revision.getRevisions().getModule());
+ }
+ }
+ for (Module module : initial)
+ addDependents(module, wiringCopy, refreshClosure);
+ return refreshClosure;
+ }
+
+ private static void addDependents(Module module, Map<ModuleRevision, ModuleWiring> wiringCopy, Set<Module> refreshClosure) {
+ if (refreshClosure.contains(module))
+ return;
+ refreshClosure.add(module);
+ List<ModuleRevision> revisions = module.getRevisions().getModuleRevisions();
+ for (ModuleRevision revision : revisions) {
+ ModuleWiring wiring = wiringCopy.get(revision);
+ if (wiring == null)
+ continue;
+ List<ModuleWire> provided = wiring.getProvidedModuleWires(null);
+ // add all requirers of the provided wires
+ for (ModuleWire providedWire : provided) {
+ addDependents(providedWire.getRequirer().getRevisions().getModule(), wiringCopy, refreshClosure);
+ }
+ // add all hosts of a fragment
+ if (revision.getTypes() == BundleRevision.TYPE_FRAGMENT) {
+ List<ModuleWire> hosts = wiring.getRequiredModuleWires(HostNamespace.HOST_NAMESPACE);
+ for (ModuleWire hostWire : hosts) {
+ addDependents(hostWire.getProvider().getRevisions().getModule(), wiringCopy, refreshClosure);
+ }
+ }
+ }
+ }
+
+ static Collection<ModuleRevision> getDependencyClosure(ModuleRevision initial, Map<ModuleRevision, ModuleWiring> wiringCopy) {
+ Set<ModuleRevision> dependencyClosure = new HashSet<ModuleRevision>();
+ addDependents(initial, wiringCopy, dependencyClosure);
+ return dependencyClosure;
+ }
+
+ private static void addDependents(ModuleRevision revision, Map<ModuleRevision, ModuleWiring> wiringCopy, Set<ModuleRevision> dependencyClosure) {
+ if (dependencyClosure.contains(revision))
+ return;
+ dependencyClosure.add(revision);
+ ModuleWiring wiring = wiringCopy.get(revision);
+ if (wiring == null)
+ return;
+ List<ModuleWire> provided = wiring.getProvidedModuleWires(null);
+ // add all requirers of the provided wires
+ for (ModuleWire providedWire : provided) {
+ addDependents(providedWire.getRequirer(), wiringCopy, dependencyClosure);
+ }
+ // add all hosts of a fragment
+ if (revision.getTypes() == BundleRevision.TYPE_FRAGMENT) {
+ List<ModuleWire> hosts = wiring.getRequiredModuleWires(HostNamespace.HOST_NAMESPACE);
+ for (ModuleWire hostWire : hosts) {
+ addDependents(hostWire.getProvider(), wiringCopy, dependencyClosure);
+ }
+ }
+ }
+
+ Bundle getSystemBundle() {
+ Module systemModule = moduleDataBase.getModule(0);
+ return systemModule == null ? null : systemModule.getBundle();
+ }
+
+ void checkAdminPermission(Bundle bundle, String action) {
+ if (bundle == null)
+ return;
+ SecurityManager sm = System.getSecurityManager();
+ if (sm != null)
+ sm.checkPermission(new AdminPermission(bundle, action));
+ }
+
+ class ContainerWiring implements FrameworkWiring, EventDispatcher<ContainerWiring, FrameworkListener[], Collection<Module>> {
+ private final Object monitor = new Object();
+ private EventManager refreshThread = null;
+
+ @Override
+ public Bundle getBundle() {
+ return getSystemBundle();
+ }
+
+ @Override
+ public void refreshBundles(Collection<Bundle> bundles, FrameworkListener... listeners) {
+ checkAdminPermission(getBundle(), AdminPermission.RESOLVE);
+ Collection<Module> modules = getModules(bundles);
+
+ // queue to refresh in the background
+ // notice that we only do one refresh operation at a time
+ CopyOnWriteIdentityMap<ContainerWiring, FrameworkListener[]> dispatchListeners = new CopyOnWriteIdentityMap<ModuleContainer.ContainerWiring, FrameworkListener[]>();
+ dispatchListeners.put(this, listeners);
+ ListenerQueue<ContainerWiring, FrameworkListener[], Collection<Module>> queue = new ListenerQueue<ModuleContainer.ContainerWiring, FrameworkListener[], Collection<Module>>(refreshThread);
+ queue.queueListeners(dispatchListeners.entrySet(), this);
+
+ // dispatch the refresh job
+ queue.dispatchEventAsynchronous(0, modules);
+ }
+
+ @Override
+ public boolean resolveBundles(Collection<Bundle> bundles) {
+ checkAdminPermission(getBundle(), AdminPermission.RESOLVE);
+ Collection<Module> modules = getModules(bundles);
+ try {
+ resolve(modules, false);
+ } catch (ResolutionException e) {
+ return false;
+ }
+ for (Module module : modules) {
+ if (getWiring(module.getCurrentRevision()) == null)
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public Collection<Bundle> getRemovalPendingBundles() {
+ moduleDataBase.lockRead();
+ try {
+ Collection<Bundle> removalPendingBundles = new HashSet<Bundle>();
+ Collection<ModuleRevision> removalPending = moduleDataBase.getRemovalPending();
+ for (ModuleRevision moduleRevision : removalPending) {
+ removalPendingBundles.add(moduleRevision.getBundle());
+ }
+ return removalPendingBundles;
+ } finally {
+ moduleDataBase.unlockRead();
+ }
+ }
+
+ @Override
+ public Collection<Bundle> getDependencyClosure(Collection<Bundle> bundles) {
+ Collection<Module> modules = getModules(bundles);
+ moduleDataBase.lockRead();
+ try {
+ Collection<Module> closure = getRefreshClosure(modules, moduleDataBase.getWiringsCopy());
+ Collection<Bundle> result = new ArrayList<Bundle>(closure.size());
+ for (Module module : closure) {
+ result.add(module.getBundle());
+ }
+ return result;
+ } finally {
+ moduleDataBase.unlockRead();
+ }
+ }
+
+ private Collection<Module> getModules(final Collection<Bundle> bundles) {
+ if (bundles == null)
+ return null;
+ return AccessController.doPrivileged(new PrivilegedAction<Collection<Module>>() {
+ @Override
+ public Collection<Module> run() {
+ Collection<Module> result = new ArrayList<Module>(bundles.size());
+ for (Bundle bundle : bundles) {
+ Module module = bundle.adapt(Module.class);
+ if (module == null)
+ throw new IllegalStateException("Could not adapt a bundle to a module."); //$NON-NLS-1$
+ result.add(module);
+ }
+ return result;
+ }
+ });
+ }
+
+ @Override
+ public void dispatchEvent(ContainerWiring eventListener, FrameworkListener[] frameworkListeners, int eventAction, Collection<Module> eventObject) {
+ try {
+ refresh(eventObject);
+ } catch (ResolutionException e) {
+ adaptor.publishContainerEvent(ContainerEvent.ERROR, moduleDataBase.getModule(0), e);
+ } finally {
+ adaptor.publishContainerEvent(ContainerEvent.REFRESH, moduleDataBase.getModule(0), null, frameworkListeners);
+ }
+ }
+
+ private EventManager getManager() {
+ synchronized (monitor) {
+ if (refreshThread == null) {
+ refreshThread = new EventManager("Start Level: " + adaptor.toString());
+ }
+ return refreshThread;
+ }
+ }
+
+ // because of bug 378491 we have to synchronize access to the manager
+ // so we can close and re-open ourselves
+ void close() {
+ synchronized (monitor) {
+ // force a manager to be created if it did not exist
+ EventManager manager = getManager();
+ // this prevents any operations until open is called
+ manager.close();
+ }
+ }
+
+ void open() {
+ synchronized (monitor) {
+ if (refreshThread != null) {
+ // Make sure it is closed just incase
+ refreshThread.close();
+ // a new one will be constructed on demand
+ refreshThread = null;
+ }
+ }
+ }
+ }
+
+ class ContainerStartLevel implements FrameworkStartLevel, EventDispatcher<Module, FrameworkListener[], Integer> {
+ static final int USE_BEGINNING_START_LEVEL = Integer.MIN_VALUE;
+ private static final int FRAMEWORK_STARTLEVEL = 1;
+ private static final int MODULE_STARTLEVEL = 2;
+ private final AtomicInteger activeStartLevel = new AtomicInteger(0);
+ private final Object monitor = new Object();
+ private EventManager startLevelThread = null;
+
+ @Override
+ public Bundle getBundle() {
+ return getSystemBundle();
+ }
+
+ @Override
+ public int getStartLevel() {
+ return activeStartLevel.get();
+ }
+
+ void setStartLevel(Module module, int startlevel) {
+ checkAdminPermission(module.getBundle(), AdminPermission.EXECUTE);
+ if (module.getId() == 0) {
+ throw new IllegalArgumentException("Cannot set the start level of the system bundle.");
+ }
+ if (startlevel < 1) {
+ throw new IllegalArgumentException("Cannot set the start level to less than 1: " + startlevel);
+ }
+ if (module.getStartLevel() == startlevel) {
+ return; // do nothing
+ }
+ moduleDataBase.setStartLevel(module, startlevel);
+ // queue start level operation in the background
+ // notice that we only do one start level operation at a time
+ CopyOnWriteIdentityMap<Module, FrameworkListener[]> dispatchListeners = new CopyOnWriteIdentityMap<Module, FrameworkListener[]>();
+ dispatchListeners.put(module, new FrameworkListener[0]);
+ ListenerQueue<Module, FrameworkListener[], Integer> queue = new ListenerQueue<Module, FrameworkListener[], Integer>(getManager());
+ queue.queueListeners(dispatchListeners.entrySet(), this);
+
+ // dispatch the start level job
+ queue.dispatchEventAsynchronous(MODULE_STARTLEVEL, startlevel);
+ }
+
+ @Override
+ public void setStartLevel(int startlevel, FrameworkListener... listeners) {
+ checkAdminPermission(getBundle(), AdminPermission.STARTLEVEL);
+ if (startlevel < 1) {
+ throw new IllegalArgumentException("Cannot set the start level to less than 1: " + startlevel);
+ }
+
+ if (activeStartLevel.get() == 0) {
+ throw new IllegalStateException("The system has not be activated yet.");
+ }
+ // queue start level operation in the background
+ // notice that we only do one start level operation at a time
+ CopyOnWriteIdentityMap<Module, FrameworkListener[]> dispatchListeners = new CopyOnWriteIdentityMap<Module, FrameworkListener[]>();
+ dispatchListeners.put(moduleDataBase.getModule(0), listeners);
+ ListenerQueue<Module, FrameworkListener[], Integer> queue = new ListenerQueue<Module, FrameworkListener[], Integer>(getManager());
+ queue.queueListeners(dispatchListeners.entrySet(), this);
+
+ // dispatch the start level job
+ queue.dispatchEventAsynchronous(FRAMEWORK_STARTLEVEL, startlevel);
+ }
+
+ @Override
+ public int getInitialBundleStartLevel() {
+ return moduleDataBase.getInitialModuleStartLevel();
+ }
+
+ @Override
+ public void setInitialBundleStartLevel(int startlevel) {
+ checkAdminPermission(getBundle(), AdminPermission.STARTLEVEL);
+ moduleDataBase.setInitialModuleStartLevel(startlevel);
+ }
+
+ @Override
+ public void dispatchEvent(Module module, FrameworkListener[] listeners, int eventAction, Integer startlevel) {
+ switch (eventAction) {
+ case FRAMEWORK_STARTLEVEL :
+ doContainerStartLevel(module, startlevel, listeners);
+ break;
+ case MODULE_STARTLEVEL :
+ try {
+ if (getStartLevel() < startlevel) {
+ module.stop(StopOptions.TRANSIENT);
+ } else {
+ module.start(StartOptions.TRANSIENT_IF_AUTO_START, StartOptions.TRANSIENT_RESUME);
+ }
+ } catch (BundleException e) {
+ adaptor.publishContainerEvent(ContainerEvent.ERROR, module, e);
+ }
+ break;
+ default :
+ break;
+ }
+ }
+
+ void doContainerStartLevel(Module module, int newStartLevel, FrameworkListener... listeners) {
+ if (newStartLevel == USE_BEGINNING_START_LEVEL) {
+ String beginningSL = (String) adaptor.getConfiguration().get(Constants.FRAMEWORK_BEGINNING_STARTLEVEL);
+ newStartLevel = beginningSL == null ? 1 : Integer.parseInt(beginningSL);
+ }
+ try {
+ int currentSL = getStartLevel();
+ // Note that we must get a new list of modules each time;
+ // this is because additional modules could have been installed from the previous start-level
+ if (newStartLevel > currentSL) {
+ for (int i = currentSL; i < newStartLevel; i++) {
+ int toStartLevel = i + 1;
+ activeStartLevel.set(toStartLevel);
+ incStartLevel(toStartLevel, moduleDataBase.getSortedModules(Sort.BY_START_LEVEL));
+ }
+ } else {
+ for (int i = currentSL; i > newStartLevel; i--) {
+ int toStartLevel = i - 1;
+ activeStartLevel.set(toStartLevel);
+ decStartLevel(toStartLevel, moduleDataBase.getSortedModules(Sort.BY_START_LEVEL, Sort.BY_DEPENDENCY));
+ }
+ }
+ adaptor.publishContainerEvent(ContainerEvent.START_LEVEL, module, null, listeners);
+ } catch (Error e) {
+ adaptor.publishContainerEvent(ContainerEvent.ERROR, module, e, listeners);
+ throw e;
+ } catch (RuntimeException e) {
+ adaptor.publishContainerEvent(ContainerEvent.ERROR, module, e, listeners);
+ throw e;
+ }
+ }
+
+ private void incStartLevel(int toStartLevel, List<Module> sortedModules) {
+ incStartLevel(toStartLevel, sortedModules, true);
+ incStartLevel(toStartLevel, sortedModules, false);
+ }
+
+ private void incStartLevel(int toStartLevel, List<Module> sortedModules, boolean lazyOnly) {
+ for (Module module : sortedModules) {
+ if (module.getStartLevel() < toStartLevel) {
+ // skip modules who should have already been started
+ continue;
+ } else if (module.getStartLevel() == toStartLevel) {
+ boolean isLazyStart = module.isLazyActivate();
+ if (lazyOnly ? isLazyStart : !isLazyStart) {
+ try {
+ module.start(StartOptions.TRANSIENT_IF_AUTO_START, StartOptions.TRANSIENT_RESUME);
+ } catch (BundleException e) {
+ adaptor.publishContainerEvent(ContainerEvent.ERROR, module, e);
+ }
+ }
+ } else {
+ // can stop resumin since any remaining modules have a greater startlevel than the active startlevel
+ break;
+ }
+ }
+ }
+
+ private void decStartLevel(int toStartLevel, List<Module> sortedModules) {
+ ListIterator<Module> iModules = sortedModules.listIterator(sortedModules.size());
+ while (iModules.hasPrevious()) {
+ Module module = iModules.previous();
+
+ if (module.getStartLevel() > toStartLevel + 1) {
+ // skip modules who should have already been stopped
+ continue;
+ } else if (module.getStartLevel() <= toStartLevel) {
+ // stopped all modules we are going to for this start level
+ break;
+ }
+ try {
+ module.stop(StopOptions.TRANSIENT);
+ } catch (BundleException e) {
+ adaptor.publishContainerEvent(ContainerEvent.ERROR, module, e);
+ }
+ }
+ }
+
+ private EventManager getManager() {
+ synchronized (monitor) {
+ if (startLevelThread == null) {
+ startLevelThread = new EventManager("Start Level: " + adaptor.toString());
+ }
+ return startLevelThread;
+ }
+ }
+
+ // because of bug 378491 we have to synchronize access to the manager
+ // so we can close and re-open ourselves
+ void close() {
+ synchronized (monitor) {
+ // force a manager to be created if it did not exist
+ EventManager manager = getManager();
+ // this prevents any operations until open is called
+ manager.close();
+ }
+ }
+
+ void open() {
+ synchronized (monitor) {
+ if (startLevelThread != null) {
+ // Make sure it is closed just incase
+ startLevelThread.close();
+ // a new one will be constructed on demand
+ startLevelThread = null;
+ }
+ }
+ }
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleContainerAdaptor.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleContainerAdaptor.java
new file mode 100644
index 000000000..14dc4642b
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleContainerAdaptor.java
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * Copyright (c) 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.container;
+
+import java.util.Collections;
+import java.util.Map;
+import org.apache.felix.resolver.Logger;
+import org.apache.felix.resolver.ResolverImpl;
+import org.osgi.framework.FrameworkListener;
+import org.osgi.framework.hooks.resolver.ResolverHookFactory;
+import org.osgi.service.resolver.Resolver;
+
+/**
+ * Adapts the behavior of a container.
+ */
+public abstract class ModuleContainerAdaptor {
+ public enum ContainerEvent {
+ REFRESH, START_LEVEL, STARTED, STOPPED, STOPPED_UPDATE, STOPPED_REFRESH, ERROR, WARNING, INFO
+ }
+
+ /**
+ * Returns the resolver the container will use. This implementation will
+ * return the default implementation of the resolver. Override this method
+ * to provide an alternative resolver implementation for the container.
+ * @return the resolver the container will use.
+ */
+ public Resolver getResolver() {
+ return new ResolverImpl(new Logger(4));
+ }
+
+ /**
+ * Returns the collision hook the container will use.
+ * @return the collision hook the container will use.
+ */
+ public abstract ModuleCollisionHook getModuleCollisionHook();
+
+ /**
+ * Returns the resolver hook factory the container will use.
+ * @return the resolver hook factory the container will use.
+ */
+ public abstract ResolverHookFactory getResolverHookFactory();
+
+ /**
+ * Publishes the specified container event.
+ * @param type the type of event
+ * @param module the module associated with the event
+ * @param error the error associated with the event, may be {@code null}
+ * @param listeners additional listeners to publish the event to synchronously
+ */
+ public abstract void publishContainerEvent(ContainerEvent type, Module module, Throwable error, FrameworkListener... listeners);
+
+ /**
+ * Returns an unmodifiable map of the configuration for the container
+ * @return an unmodifiable map of the configuration for the container
+ */
+ public Map<String, Object> getConfiguration() {
+ return Collections.emptyMap();
+ }
+
+ /**
+ * Creates a new {@link ModuleClassLoader} for the specified wiring.
+ * @param wiring the module wiring to create a module class loader for
+ * @return a new {@link ModuleClassLoader} for the specified wiring.
+ */
+ public ModuleClassLoader createClassLoader(ModuleWiring wiring) {
+ throw new UnsupportedOperationException("Container adaptor does not support module class loaders.");
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleDataBase.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleDataBase.java
new file mode 100644
index 000000000..95f42d643
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleDataBase.java
@@ -0,0 +1,1523 @@
+/*******************************************************************************
+ * Copyright (c) 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.container;
+
+import java.io.*;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.locks.*;
+import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
+import org.eclipse.osgi.container.Module.Settings;
+import org.eclipse.osgi.container.Module.State;
+import org.eclipse.osgi.framework.util.ObjectPool;
+import org.eclipse.osgi.internal.container.Capabilities;
+import org.eclipse.osgi.internal.resolver.ComputeNodeOrder;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Version;
+import org.osgi.framework.wiring.BundleRevision;
+import org.osgi.resource.*;
+import org.osgi.service.resolver.Resolver;
+
+/**
+ * A database for storing modules, their revisions and wiring states. The
+ * database is responsible for assigning ids and providing access to the
+ * capabilities provided by the revisions currently installed as well as
+ * the wiring states.
+ * <p>
+ * <strong>Concurrent Semantics</strong><br />
+ *
+ * Implementations must be thread safe. The database allows for concurrent
+ * read operations and all read operations are protected by the
+ * {@link #lockRead() read} lock. All write operations are
+ * protected by the {@link #lockWrite() write} lock. The read and write
+ * locks are reentrant and follow the semantics of the
+ * {@link ReentrantReadWriteLock}. Just like the {@code ReentrantReadWriteLock}
+ * the lock on a database can not be upgraded from a read to a write. Doing so will result in an
+ * {@link IllegalMonitorStateException} being thrown. This is behavior is different from
+ * the {@code ReentrantReadWriteLock} which results in a deadlock if an attempt is made
+ * to upgrade from a read to a write lock.
+ * <p>
+ * A database is associated with a {@link ModuleContainer container}. The container
+ * associated with a database provides public API for manipulating the modules
+ * and their wiring states. For example, installing, updating, uninstalling,
+ * resolving and unresolving modules. Except for the {@link #load(DataInputStream)},
+ * all other methods that perform write operations are intended to be used by
+ * the associated container.
+ */
+public abstract class ModuleDataBase {
+ /**
+ * The container this database is associated with
+ */
+ protected ModuleContainer container = null;
+
+ /**
+ * A map of modules by location.
+ */
+ private final Map<String, Module> modulesByLocations;
+
+ /**
+ * A map of modules by id.
+ */
+ private final Map<Long, Module> modulesById;
+
+ /**
+ * A map of revision collections by symbolic name
+ */
+ private final Map<String, Collection<ModuleRevision>> revisionByName;
+
+ /**
+ * A map of revision wiring objects.
+ */
+ private final Map<ModuleRevision, ModuleWiring> wirings;
+
+ /**
+ * Holds the next id to be assigned to a module when it is installed
+ */
+ final AtomicLong nextId;
+
+ /**
+ * Holds the current timestamp of this database.
+ */
+ final AtomicLong timeStamp;
+
+ private final Capabilities capabilities;
+
+ /**
+ * A map of module settings keyed by module id.
+ */
+ private final Map<Long, EnumSet<Settings>> moduleSettings;
+
+ /**
+ * The initial module start level.
+ */
+ private int initialModuleStartLevel = 1;
+
+ /**
+ * Monitors read and write access to this database
+ */
+ private final ReentrantReadWriteLock monitor = new ReentrantReadWriteLock(true);
+
+ static enum Sort {
+ BY_DEPENDENCY, BY_START_LEVEL, BY_ID;
+ /**
+ * Tests if this option is contained in the specified options
+ */
+ public boolean isContained(Sort... options) {
+ for (Sort option : options) {
+ if (equals(option)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Constructs a new empty database.
+ */
+ public ModuleDataBase() {
+ this.modulesByLocations = new HashMap<String, Module>();
+ this.modulesById = new HashMap<Long, Module>();
+ this.revisionByName = new HashMap<String, Collection<ModuleRevision>>();
+ this.wirings = new HashMap<ModuleRevision, ModuleWiring>();
+ // Start at id 1 because 0 is reserved for the system bundle
+ this.nextId = new AtomicLong(1);
+ this.timeStamp = new AtomicLong(0);
+ this.moduleSettings = new HashMap<Long, EnumSet<Settings>>();
+ this.capabilities = new Capabilities();
+ }
+
+ /**
+ * Sets the container for this database. A database can only
+ * be associated with a single container and that container must
+ * have been constructed with this database.
+ * <p>
+ * This method modifies this database and is considered a write operation.
+ * This method acquires the {@link #lockWrite() write} lock while setting
+ * the container for this database.
+ * @param container the container to associate this database with.
+ */
+ public final void setContainer(ModuleContainer container) {
+ lockWrite();
+ try {
+ if (this.container != null)
+ throw new IllegalStateException("The container is already set."); //$NON-NLS-1$
+ if (container.moduleDataBase != this) {
+ throw new IllegalArgumentException("Container is already using a different database."); //$NON-NLS-1$
+ }
+ this.container = container;
+ } finally {
+ unlockWrite();
+ }
+ }
+
+ /**
+ * Returns the module at the given location or null if no module exists
+ * at the given location.
+ * <p>
+ * A read operation protected by the {@link #lockRead() read} lock.
+ * @param location the location of the module.
+ * @return the module at the given location or null.
+ */
+ final Module getModule(String location) {
+ lockRead();
+ try {
+ return modulesByLocations.get(location);
+ } finally {
+ unlockRead();
+ }
+ }
+
+ /**
+ * Returns the module at the given id or null if no module exists
+ * at the given location.
+ * <p>
+ * A read operation protected by the {@link #lockRead() read} lock.
+ * @param id the id of the module.
+ * @return the module at the given id or null.
+ */
+ final Module getModule(long id) {
+ lockRead();
+ try {
+ return modulesById.get(id);
+ } finally {
+ unlockRead();
+ }
+ }
+
+ /**
+ * Returns a snapshot collection of revisions with the specified name
+ * and version. If version is {@code null} then all revisions with
+ * the specified name are returned.
+ * <p>
+ * A read operation protected by the {@link #lockRead() read} lock.
+ * @param name the name of the modules
+ * @param version the version of the modules or {@code null}
+ * @return a snapshot collection of revisions with the specified name
+ * and version.
+ */
+ final Collection<ModuleRevision> getRevisions(String name, Version version) {
+ lockRead();
+ try {
+ if (version == null)
+ return new ArrayList<ModuleRevision>(revisionByName.get(name));
+
+ Collection<ModuleRevision> existingRevisions = revisionByName.get(name);
+ if (existingRevisions == null) {
+ return Collections.emptyList();
+ }
+ Collection<ModuleRevision> sameVersion = new ArrayList<ModuleRevision>(1);
+ for (ModuleRevision revision : existingRevisions) {
+ if (revision.getVersion().equals(version)) {
+ sameVersion.add(revision);
+ }
+ }
+ return sameVersion;
+ } finally {
+ unlockRead();
+ }
+ }
+
+ /**
+ * Returns a snapshot collection of current revisions which are fragments
+
+ * @return a snapshot collection of current revisions which are fragments
+ */
+ final Collection<ModuleRevision> getFragmentRevisions() {
+ Collection<ModuleRevision> fragments = new ArrayList<ModuleRevision>();
+ lockRead();
+ try {
+ for (Module module : modulesById.values()) {
+ ModuleRevision revision = module.getCurrentRevision();
+ if (revision != null && ((revision.getTypes() & BundleRevision.TYPE_FRAGMENT) != 0)) {
+ fragments.add(revision);
+ }
+ }
+ return fragments;
+ } finally {
+ unlockRead();
+ }
+ }
+
+ /**
+ * Installs a new revision using the specified builder, location and module.
+ * <p>
+ * A write operation protected by the {@link #lockWrite() write} lock.
+ * @param location the location to use for the installation
+ * @param builder the builder to use to create the new revision
+ * @return the installed module
+ */
+ final Module install(String location, ModuleRevisionBuilder builder) {
+ lockWrite();
+ try {
+ int startlevel = Constants.SYSTEM_BUNDLE_LOCATION.equals(location) ? 0 : getInitialModuleStartLevel();
+ long id = Constants.SYSTEM_BUNDLE_LOCATION.equals(location) ? 0 : getNextIdAndIncrement();
+ Module module = load(location, builder, id, null, startlevel);
+ module.setlastModified(System.currentTimeMillis());
+ incrementTimestamp();
+ return module;
+ } finally {
+ unlockWrite();
+ }
+ }
+
+ final Module load(String location, ModuleRevisionBuilder builder, long id, EnumSet<Settings> settings, int startlevel) {
+ // sanity check
+ checkWrite();
+ if (container == null)
+ throw new IllegalStateException("Container is not set."); //$NON-NLS-1$
+ if (modulesByLocations.containsKey(location))
+ throw new IllegalArgumentException("Location is already used: " + location); //$NON-NLS-1$
+ if (modulesById.containsKey(id))
+ throw new IllegalArgumentException("Id is already used: " + id); //$NON-NLS-1$
+ Module module;
+ if (id == 0) {
+ module = createSystemModule();
+ } else {
+ module = createModule(location, id, settings, startlevel);
+ }
+ builder.addRevision(module);
+ modulesByLocations.put(location, module);
+ modulesById.put(id, module);
+ if (settings != null)
+ moduleSettings.put(id, settings);
+ ModuleRevision newRevision = module.getCurrentRevision();
+ addToRevisionByName(newRevision);
+ addCapabilities(newRevision);
+ return module;
+ }
+
+ private void addToRevisionByName(ModuleRevision revision) {
+ // sanity check
+ checkWrite();
+
+ String name = revision.getSymbolicName();
+ Collection<ModuleRevision> sameName = revisionByName.get(name);
+ if (sameName == null) {
+ sameName = new ArrayList<ModuleRevision>(1);
+ revisionByName.put(name, sameName);
+ }
+ sameName.add(revision);
+ }
+
+ /**
+ * Uninstalls the specified module from this database.
+ * Uninstalling a module will attempt to clean up any removal pending
+ * revisions possible.
+ * <p>
+ * A write operation protected by the {@link #lockWrite() write} lock.
+ * @param module the module to uninstall
+ */
+ final void uninstall(Module module) {
+ lockWrite();
+ try {
+ ModuleRevisions uninstalling = module.getRevisions();
+ // remove the location
+ modulesByLocations.remove(module.getLocation());
+ modulesById.remove(module.getId());
+ moduleSettings.remove(module.getId());
+ // remove the revisions by name
+ List<ModuleRevision> revisions = uninstalling.getModuleRevisions();
+ for (ModuleRevision revision : revisions) {
+ removeCapabilities(revision);
+ String name = revision.getSymbolicName();
+ if (name != null) {
+ Collection<ModuleRevision> sameName = revisionByName.get(name);
+ if (sameName != null) {
+ sameName.remove(revision);
+ }
+ }
+ // if the revision does not have a wiring it can safely be removed
+ // from the revisions for the module
+ ModuleWiring oldWiring = wirings.get(revision);
+ if (oldWiring == null) {
+ module.getRevisions().removeRevision(revision);
+ }
+ }
+ // marke the revisions as uninstalled
+ uninstalling.uninstall();
+ // attempt to cleanup any removal pendings
+ cleanupRemovalPending();
+
+ module.setlastModified(System.currentTimeMillis());
+ incrementTimestamp();
+ } finally {
+ unlockWrite();
+ }
+ }
+
+ /**
+ * Updates the specified module with anew revision using the specified builder.
+ * <p>
+ * A write operation protected by the {@link #lockWrite() write} lock.
+ * @param module the module for which the revision provides an update for
+ * @param builder the builder to use to create the new revision
+ */
+ final void update(Module module, ModuleRevisionBuilder builder) {
+ lockWrite();
+ try {
+ ModuleRevision oldRevision = module.getCurrentRevision();
+ ModuleRevision newRevision = builder.addRevision(module);
+ String name = newRevision.getSymbolicName();
+ Collection<ModuleRevision> sameName = revisionByName.get(name);
+ if (sameName == null) {
+ sameName = new ArrayList<ModuleRevision>(1);
+ revisionByName.put(name, sameName);
+ }
+ sameName.add(newRevision);
+ addCapabilities(newRevision);
+
+ // remove the old revision by name
+ String oldName = oldRevision.getSymbolicName();
+ if (oldName != null) {
+ Collection<ModuleRevision> oldSameName = revisionByName.get(oldName);
+ if (oldSameName != null) {
+ oldSameName.remove(oldRevision);
+ }
+ }
+
+ // if the old revision does not have a wiring it can safely be removed
+ ModuleWiring oldWiring = wirings.get(oldRevision);
+ if (oldWiring == null) {
+ module.getRevisions().removeRevision(oldRevision);
+ removeCapabilities(oldRevision);
+ }
+ // attempt to clean up removal pendings
+ cleanupRemovalPending();
+
+ module.setlastModified(System.currentTimeMillis());
+ incrementTimestamp();
+ } finally {
+ unlockWrite();
+ }
+ }
+
+ /**
+ * Examines the wirings to determine if there are any removal
+ * pending wiring objects that can be removed. We consider
+ * a removal pending wiring as removable if all dependent
+ * wiring are also removal pending.
+ */
+ private void cleanupRemovalPending() {
+ // sanity check
+ checkWrite();
+ Collection<ModuleRevision> removalPending = getRemovalPending();
+ for (ModuleRevision removed : removalPending) {
+ if (wirings.get(removed) == null)
+ continue;
+ Collection<ModuleRevision> dependencyClosure = ModuleContainer.getDependencyClosure(removed, wirings);
+ boolean allPendingRemoval = true;
+ for (ModuleRevision pendingRemoval : dependencyClosure) {
+ if (pendingRemoval.isCurrent()) {
+ allPendingRemoval = false;
+ break;
+ }
+ }
+ if (allPendingRemoval) {
+ for (ModuleRevision pendingRemoval : dependencyClosure) {
+ pendingRemoval.getRevisions().removeRevision(pendingRemoval);
+ removeCapabilities(pendingRemoval);
+ wirings.remove(pendingRemoval);
+ }
+ }
+ }
+ }
+
+ /**
+ * Gets all revisions with a removal pending wiring.
+ * <p>
+ * A read operation protected by the {@link #lockRead() read} lock.
+ * @return all revisions with a removal pending wiring.
+ */
+ final Collection<ModuleRevision> getRemovalPending() {
+ Collection<ModuleRevision> removalPending = new ArrayList<ModuleRevision>();
+ lockRead();
+ try {
+ for (ModuleWiring wiring : wirings.values()) {
+ if (!wiring.isCurrent())
+ removalPending.add(wiring.getRevision());
+ }
+ } finally {
+ unlockRead();
+ }
+ return removalPending;
+ }
+
+ /**
+ * Returns the current wiring for the specified revision or
+ * null of no wiring exists for the revision.
+ * <p>
+ * A read operation protected by the {@link #lockRead() read} lock.
+ * @param revision the revision to get the wiring for
+ * @return the current wiring for the specified revision.
+ */
+ final ModuleWiring getWiring(ModuleRevision revision) {
+ lockRead();
+ try {
+ return wirings.get(revision);
+ } finally {
+ unlockRead();
+ }
+ }
+
+ /**
+ * Returns a snapshot of the wirings for all revisions. This
+ * performs a shallow copy of each entry in the wirings map.
+ * <p>
+ * A read operation protected by the {@link #lockRead() read} lock.
+ * @return a snapshot of the wirings for all revisions.
+ */
+ final Map<ModuleRevision, ModuleWiring> getWiringsCopy() {
+ lockRead();
+ try {
+ return new HashMap<ModuleRevision, ModuleWiring>(wirings);
+ } finally {
+ unlockRead();
+ }
+ }
+
+ /**
+ * Returns a cloned snapshot of the wirings of all revisions. This
+ * performs a clone of each {@link ModuleWiring}. The
+ * {@link ModuleWiring#getRevision() revision},
+ * {@link ModuleWiring#getModuleCapabilities(String) capabilities},
+ * {@link ModuleWiring#getModuleRequirements(String) requirements},
+ * {@link ModuleWiring#getProvidedModuleWires(String) provided wires}, and
+ * {@link ModuleWiring#getRequiredModuleWires(String) required wires} of
+ * each wiring are copied into a cloned copy of the wiring.
+ * <p>
+ * The returned map of wirings may be safely read from while not holding
+ * any read or write locks on this database. This is useful for doing
+ * {@link Resolver#resolve(org.osgi.service.resolver.ResolveContext) resolve}
+ * operations without holding the read or write lock on this database.
+ * <p>
+ * A read operation protected by the {@link #lockRead() read} lock.
+ * @return a cloned snapshot of the wirings of all revisions.
+ */
+ final Map<ModuleRevision, ModuleWiring> getWiringsClone() {
+ lockRead();
+ try {
+ Map<ModuleRevision, ModuleWiring> clonedWirings = new HashMap<ModuleRevision, ModuleWiring>();
+ for (Map.Entry<ModuleRevision, ModuleWiring> entry : wirings.entrySet()) {
+ ModuleWiring wiring = new ModuleWiring(entry.getKey(), entry.getValue().getModuleCapabilities(null), entry.getValue().getModuleRequirements(null), entry.getValue().getProvidedModuleWires(null), entry.getValue().getRequiredModuleWires(null), entry.getValue().getSubstitutedNames());
+ clonedWirings.put(entry.getKey(), wiring);
+ }
+ return clonedWirings;
+ } finally {
+ unlockRead();
+ }
+ }
+
+ /**
+ * Replaces the complete wiring map with the specified wiring
+ * <p>
+ * A write operation protected by the {@link #lockWrite() write} lock.
+ * @param newWiring the new wiring to take effect. The values
+ * from the new wiring are copied.
+ */
+ final void setWiring(Map<ModuleRevision, ModuleWiring> newWiring) {
+ lockWrite();
+ try {
+ wirings.clear();
+ wirings.putAll(newWiring);
+ incrementTimestamp();
+ } finally {
+ unlockWrite();
+ }
+ }
+
+ /**
+ * Adds all the values from the specified delta wirings to the
+ * wirings current wirings
+ * <p>
+ * A write operation protected by the {@link #lockWrite() write} lock.
+ * @param deltaWiring the new wiring values to take effect.
+ * The values from the delta wiring are copied.
+ */
+ final void mergeWiring(Map<ModuleRevision, ModuleWiring> deltaWiring) {
+ lockWrite();
+ try {
+ wirings.putAll(deltaWiring);
+ incrementTimestamp();
+ } finally {
+ unlockWrite();
+ }
+ }
+
+ /**
+ * Returns a snapshot of all modules ordered by module ID.
+ * <p>
+ * A read operation protected by the {@link #lockRead() read} lock.
+ * @return a snapshot of all modules.
+ */
+ final List<Module> getModules() {
+ return getSortedModules();
+ }
+
+ /**
+ * Returns a snapshot of all modules ordered according to the sort options
+ * @param sortOptions options for sorting
+ * @return a snapshot of all modules ordered according to the sort options
+ */
+ final List<Module> getSortedModules(Sort... sortOptions) {
+ lockRead();
+ try {
+ List<Module> modules = new ArrayList<Module>(modulesByLocations.values());
+ sortModules(modules, sortOptions);
+ return modules;
+ } finally {
+ unlockRead();
+ }
+ }
+
+ final void sortModules(List<Module> modules, Sort... sortOptions) {
+ if (modules.size() < 2)
+ return;
+ if (sortOptions == null || Sort.BY_ID.isContained(sortOptions) || sortOptions.length == 0) {
+ Collections.sort(modules, new Comparator<Module>() {
+ @Override
+ public int compare(Module m1, Module m2) {
+ return m1.getId().compareTo(m2.getId());
+ }
+ });
+ return;
+ }
+ // first sort by start-level
+ if (Sort.BY_START_LEVEL.isContained(sortOptions)) {
+ Collections.sort(modules);
+ }
+ if (Sort.BY_DEPENDENCY.isContained(sortOptions)) {
+ if (Sort.BY_START_LEVEL.isContained(sortOptions)) {
+ // sort each sublist that has modules of the same start level
+ int currentSL = modules.get(0).getStartLevel();
+ int currentSLindex = 0;
+ boolean lazy = false;
+ for (int i = 0; i < modules.size(); i++) {
+ Module module = modules.get(i);
+ if (currentSL != module.getStartLevel()) {
+ if (lazy)
+ sortByDependencies(modules.subList(currentSLindex, i));
+ currentSL = module.getStartLevel();
+ currentSLindex = i;
+ lazy = false;
+ }
+ lazy |= module.isLazyActivate();
+ }
+ // sort the last set of bundles
+ if (lazy)
+ sortByDependencies(modules.subList(currentSLindex, modules.size()));
+ } else {
+ // sort the whole list by dependency
+ sortByDependencies(modules);
+ }
+ }
+ }
+
+ private Collection<List<Module>> sortByDependencies(List<Module> toSort) {
+ // Build references so we can sort
+ List<Module[]> references = new ArrayList<Module[]>(toSort.size());
+ for (Module module : toSort) {
+ ModuleRevision current = module.getCurrentRevision();
+ if (current == null) {
+ continue;
+ }
+ ModuleWiring wiring = current.getWiring();
+ if (wiring == null) {
+ continue;
+ }
+ for (ModuleWire wire : wiring.getRequiredModuleWires(null)) {
+ references.add(new Module[] {wire.getRequirer().getRevisions().getModule(), wire.getProvider().getRevisions().getModule()});
+ }
+ }
+
+ // Sort an array using the references
+ Module[] sorted = toSort.toArray(new Module[toSort.size()]);
+ Object[][] cycles = ComputeNodeOrder.computeNodeOrder(sorted, references.toArray(new Module[references.size()][]));
+
+ // Apply the sorted array to the list
+ toSort.clear();
+ toSort.addAll(Arrays.asList(sorted));
+
+ if (cycles.length == 0)
+ return Collections.emptyList();
+
+ Collection<List<Module>> moduleCycles = new ArrayList<List<Module>>(cycles.length);
+ for (Object[] cycle : cycles) {
+ List<Module> moduleCycle = new ArrayList<Module>(cycle.length);
+ for (Object module : cycle) {
+ moduleCycle.add((Module) module);
+ }
+ moduleCycles.add(moduleCycle);
+ }
+ return moduleCycles;
+ }
+
+ /**
+ * Increments by one the next module ID
+ */
+ private long getNextIdAndIncrement() {
+ // sanity check
+ checkWrite();
+ return nextId.getAndIncrement();
+
+ }
+
+ private void checkWrite() {
+ if (monitor.getWriteHoldCount() == 0)
+ throw new IllegalMonitorStateException("Must hold the write lock.");
+ }
+
+ /**
+ * returns the next module ID
+ * <p>
+ * A read operation protected by the {@link #lockRead() read} lock.
+ * @return the next module ID
+ */
+ public final long getNextId() {
+ lockRead();
+ try {
+ return nextId.get();
+ } finally {
+ unlockRead();
+ }
+ }
+
+ /**
+ * Returns the current timestamp of this database.
+ * The timestamp is incremented any time a modification
+ * is made to this database. For example:
+ * <ul>
+ * <li> installing a module
+ * <li> updating a module
+ * <li> uninstalling a module
+ * <li> modifying the wirings
+ * </ul>
+ * <p>
+ * A read operation protected by the {@link #lockRead() read} lock.
+ * @return the current timestamp of this database.
+ */
+ final public long getTimestamp() {
+ lockRead();
+ try {
+ return timeStamp.get();
+ } finally {
+ unlockRead();
+ }
+ }
+
+ /**
+ * Increments the timestamp of this database.
+ */
+ private void incrementTimestamp() {
+ // sanity check
+ checkWrite();
+ timeStamp.incrementAndGet();
+ }
+
+ /**
+ * @see ReadLock#lock()
+ */
+ public final void lockRead() {
+ monitor.readLock().lock();
+ }
+
+ /**
+ * Same as {@link WriteLock#lock()} except an illegal
+ * state exception is thrown if the current thread holds
+ * one or more read locks.
+ * @see WriteLock#lock()
+ * @throws IllegalMonitorStateException if the current thread holds
+ * one or more read locks.
+ */
+ public final void lockWrite() {
+ if (monitor.getReadHoldCount() > 0) {
+ // this is not supported and will cause deadlock if allowed to proceed.
+ // fail fast instead of deadlocking
+ throw new IllegalMonitorStateException("Requesting upgrade to write lock."); //$NON-NLS-1$
+ }
+ monitor.writeLock().lock();
+ }
+
+ /**
+ * @see ReadLock#unlock()
+ */
+ public final void unlockRead() {
+ monitor.readLock().unlock();
+ }
+
+ /**
+ * @see WriteLock#unlock()
+ */
+ public final void unlockWrite() {
+ monitor.writeLock().unlock();
+ }
+
+ /**
+ * Adds the {@link ModuleRevision#getModuleCapabilities(String) capabilities}
+ * provided by the specified revision to this database. These capabilities must
+ * become available for lookup with the {@link ModuleDataBase#findCapabilities(ModuleRequirement)}
+ * method.
+ * <p>
+ * This method must be called while holding the {@link #lockWrite() write} lock.
+ * @param revision the revision which has capabilities to add
+ */
+ protected void addCapabilities(ModuleRevision revision) {
+ checkWrite();
+ capabilities.addCapabilities(revision);
+ }
+
+ /**
+ * Removes the {@link ModuleRevision#getModuleCapabilities(String) capabilities}
+ * provided by the specified revision from this database. These capabilities
+ * must no longer be available for lookup with the
+ * {@link ModuleDataBase#findCapabilities(ModuleRequirement)} method.
+ * <p>
+ * This method must be called while holding the {@link #lockWrite() write} lock.
+ * @param revision
+ */
+ protected void removeCapabilities(ModuleRevision revision) {
+ checkWrite();
+ capabilities.removeCapabilities(revision);
+ }
+
+ /**
+ * Returns a mutable snapshot of capabilities that are candidates for
+ * satisfying the specified requirement.
+ * <p>
+ * A read operation protected by the {@link #lockRead() read} lock.
+ * Implementers of this method should acquire the read lock while
+ * finding capabilities.
+ * @param requirement the requirement
+ * @return the candidates for the requirement
+ */
+ protected List<ModuleCapability> findCapabilities(ModuleRequirement requirement) {
+ lockRead();
+ try {
+ return capabilities.findCapabilities(requirement);
+ } finally {
+ unlockRead();
+ }
+ }
+
+ /**
+ * Creates a new module. This gets called when a new module is installed
+ * or when {@link #load(DataInputStream) loading} persistent data into this
+ * database.
+ * @param location the location for the module
+ * @param id the id for the module
+ * @param settings the settings for the module. May be {@code null} if there are no settings.
+ * @param startlevel the start level for the module
+ * @return the Module
+ */
+ protected abstract Module createModule(String location, long id, EnumSet<Settings> settings, int startlevel);
+
+ /**
+ * Creates the system module. This gets called when the system module is installed
+ * or when {@link #load(DataInputStream) loading} persistent data into this
+ * database.
+ * <p>
+ * The returned system module must have an {@link Module#getId() id} of zero and a location
+ * of {@link Constants#SYSTEM_BUNDLE_LOCATION System Bundle}.
+ * @return the system module
+ */
+ protected abstract SystemModule createSystemModule();
+
+ /**
+ * Writes this database in a format suitable for using the {@link #load(DataInputStream)}
+ * method. All modules are stored which have a current {@link ModuleRevision revision}.
+ * Only the current revision of each module is stored (no removal pending revisions
+ * are stored). Optionally the {@link ModuleWiring wiring} of each current revision
+ * may be stored. Wiring can only be stored if there are no {@link #getRemovalPending()
+ * removal pending} revisions.
+ * <p>
+ * This method acquires the {@link #lockRead() read} lock while writing this
+ * database.
+ * <p>
+ * After this database have been written, the output stream is flushed.
+ * The output stream remains open after this method returns.
+ * @param out the data output steam.
+ * @param persistWirings true if wirings should be persisted. This option will be ignored
+ * if there are {@link #getRemovalPending() removal pending} revisions.
+ * @throws IOException if writing this database to the specified output stream throws an IOException
+ */
+ public final void store(DataOutputStream out, boolean persistWirings) throws IOException {
+ lockRead();
+ try {
+ Persistence.store(this, out, persistWirings);
+ } finally {
+ unlockRead();
+ }
+ }
+
+ /**
+ * Loads information into this database from the input data stream. This data
+ * base must be empty and never been modified (the {@link #getTimestamp() timestamp} is zero.
+ * All stored modules are loaded into this database. If the input stream contains
+ * wiring then it will also be loaded into this database.
+ * <p>
+ * Since this method modifies this database it is considered a write operation.
+ * This method acquires the {@link #lockWrite() write} lock while loading
+ * the information into this database.
+ * <p>
+ * The specified stream remains open after this method returns.
+ * @param in the data input stream.
+ * @throws IOException if an error occurred when reading from the input stream.
+ * @throws IllegalStateException if this database is not empty.
+ */
+ public final void load(DataInputStream in) throws IOException {
+ lockWrite();
+ try {
+ if (timeStamp.get() != 0)
+ throw new IllegalStateException("Can only load into a empty database."); //$NON-NLS-1$
+ Persistence.load(this, in);
+ } finally {
+ unlockWrite();
+ }
+ }
+
+ void persistSettings(EnumSet<Settings> settings, Module module) {
+ lockWrite();
+ try {
+ moduleSettings.put(module.getId(), EnumSet.copyOf(settings));
+ } finally {
+ unlockWrite();
+ }
+ }
+
+ void setStartLevel(Module module, int startlevel) {
+ lockWrite();
+ try {
+ module.storeStartLevel(startlevel);
+ } finally {
+ unlockWrite();
+ }
+ }
+
+ int getInitialModuleStartLevel() {
+ lockRead();
+ try {
+ return this.initialModuleStartLevel;
+ } finally {
+ unlockRead();
+ }
+ }
+
+ void setInitialModuleStartLevel(int initialStartlevel) {
+ lockWrite();
+ try {
+ this.initialModuleStartLevel = initialStartlevel;
+ } finally {
+ unlockWrite();
+ }
+ }
+
+ private static class Persistence {
+ private static final int VERSION = 1;
+ private static final byte NULL = 0;
+ private static final byte OBJECT = 1;
+ private static final byte LONG_STRING = 3;
+ private static final String UTF_8 = "UTF-8"; //$NON-NLS-1$
+
+ private static final byte VALUE_STRING = 0;
+ private static final byte VALUE_STRING_ARRAY = 1;
+ private static final byte VAlUE_BOOLEAN = 2;
+ private static final byte VALUE_INTEGER = 3;
+ private static final byte VALUE_LONG = 4;
+ private static final byte VALUE_DOUBLE = 5;
+ private static final byte VALUE_VERSION = 6;
+ private static final byte VALUE_URI = 7;
+ private static final byte VALUE_LIST = 8;
+
+ private static int addToWriteTable(Object object, Map<Object, Integer> objectTable) {
+ if (object == null)
+ throw new NullPointerException();
+ Integer cur = objectTable.get(object);
+ if (cur != null)
+ throw new IllegalStateException("Object is already in the write table: " + object); //$NON-NLS-1$
+ objectTable.put(object, new Integer(objectTable.size()));
+ // return the index of the object just added (i.e. size - 1)
+ return (objectTable.size() - 1);
+ }
+
+ private static void addToReadTable(Object object, int index, Map<Integer, Object> objectTable) {
+ objectTable.put(new Integer(index), object);
+ }
+
+ public static void store(ModuleDataBase moduleDataBase, DataOutputStream out, boolean persistWirings) throws IOException {
+ out.writeInt(VERSION);
+ out.writeLong(moduleDataBase.getTimestamp());
+ out.writeLong(moduleDataBase.getNextId());
+ out.writeInt(moduleDataBase.getInitialModuleStartLevel());
+
+ List<Module> modules = moduleDataBase.getModules();
+ out.writeInt(modules.size());
+
+ Map<Object, Integer> objectTable = new HashMap<Object, Integer>();
+ for (Module module : modules) {
+ writeModule(module, moduleDataBase, out, objectTable);
+ }
+
+ Collection<ModuleRevision> removalPendings = moduleDataBase.getRemovalPending();
+ // only persist wirings if there are no removals pending
+ persistWirings &= removalPendings.isEmpty();
+ out.writeBoolean(persistWirings);
+ if (!persistWirings) {
+ return;
+ }
+
+ Map<ModuleRevision, ModuleWiring> wirings = moduleDataBase.wirings;
+ // prime the object table with all the required wires
+ out.writeInt(wirings.size());
+ for (ModuleWiring wiring : wirings.values()) {
+ List<ModuleWire> requiredWires = wiring.getRequiredModuleWires(null);
+ out.writeInt(requiredWires.size());
+ for (ModuleWire wire : requiredWires) {
+ writeWire(wire, out, objectTable);
+ }
+ }
+
+ // now write all the info about each wiring using only indexes
+ for (ModuleWiring wiring : wirings.values()) {
+ writeWiring(wiring, out, objectTable);
+ }
+
+ out.flush();
+ }
+
+ public static void load(ModuleDataBase moduleDataBase, DataInputStream in) throws IOException {
+ int version = in.readInt();
+ if (version < VERSION)
+ throw new UnsupportedOperationException("Perstence version is not correct for loading: " + version + " expecting: " + VERSION); //$NON-NLS-1$ //$NON-NLS-2$
+ long timeStamp = in.readLong();
+ moduleDataBase.nextId.set(in.readLong());
+ moduleDataBase.setInitialModuleStartLevel(in.readInt());
+
+ int numModules = in.readInt();
+
+ Map<Integer, Object> objectTable = new HashMap<Integer, Object>();
+ for (int i = 0; i < numModules; i++) {
+ readModule(moduleDataBase, in, objectTable);
+ }
+ if (!in.readBoolean())
+ return; // no wires persisted
+
+ int numWirings = in.readInt();
+ // prime the table with all the required wires
+ for (int i = 0; i < numWirings; i++) {
+ int numWires = in.readInt();
+ for (int j = 0; j < numWires; j++) {
+ readWire(in, objectTable);
+ }
+ }
+
+ // now read all the info about each wiring using only indexes
+ Map<ModuleRevision, ModuleWiring> wirings = new HashMap<ModuleRevision, ModuleWiring>();
+ for (int i = 0; i < numWirings; i++) {
+ ModuleWiring wiring = readWiring(in, objectTable);
+ wirings.put(wiring.getRevision(), wiring);
+ }
+ // TODO need to do this without incrementing the timestamp
+ moduleDataBase.setWiring(wirings);
+
+ // need to set the resolution state of the modules
+ for (ModuleWiring wiring : wirings.values()) {
+ wiring.getRevision().getRevisions().getModule().setState(State.RESOLVED);
+ }
+
+ // Setting the timestamp at the end since some operations increment it
+ moduleDataBase.timeStamp.set(timeStamp);
+ }
+
+ private static void writeModule(Module module, ModuleDataBase moduleDataBase, DataOutputStream out, Map<Object, Integer> objectTable) throws IOException {
+ ModuleRevision current = module.getCurrentRevision();
+ if (current == null)
+ return;
+ out.writeInt(addToWriteTable(current, objectTable));
+
+ writeString(module.getLocation(), out);
+ out.writeLong(module.getId());
+
+ writeString(current.getSymbolicName(), out);
+ writeVersion(current.getVersion(), out);
+ out.writeInt(current.getTypes());
+
+ List<Capability> capabilities = current.getCapabilities(null);
+ out.writeInt(capabilities.size());
+ for (Capability capability : capabilities) {
+ out.writeInt(addToWriteTable(capability, objectTable));
+ writeGenericInfo(capability.getNamespace(), capability.getAttributes(), capability.getDirectives(), out);
+ }
+
+ List<Requirement> requirements = current.getRequirements(null);
+ out.writeInt(requirements.size());
+ for (Requirement requirement : requirements) {
+ out.writeInt(addToWriteTable(requirement, objectTable));
+ writeGenericInfo(requirement.getNamespace(), requirement.getAttributes(), requirement.getDirectives(), out);
+ }
+
+ // settings
+ EnumSet<Settings> settings = moduleDataBase.moduleSettings.get(module.getId());
+ out.writeInt(settings == null ? 0 : settings.size());
+ if (settings != null) {
+ for (Settings setting : settings) {
+ writeString(setting.name(), out);
+ }
+ }
+
+ // startlevel
+ out.writeInt(module.getStartLevel());
+
+ // last modified
+ out.writeLong(module.getLastModified());
+ }
+
+ private static void readModule(ModuleDataBase moduleDataBase, DataInputStream in, Map<Integer, Object> objectTable) throws IOException {
+ ModuleRevisionBuilder builder = new ModuleRevisionBuilder();
+ int moduleIndex = in.readInt();
+ String location = readString(in);
+ long id = in.readLong();
+ builder.setSymbolicName(readString(in));
+ builder.setVersion(readVersion(in));
+ builder.setTypes(in.readInt());
+
+ int numCapabilities = in.readInt();
+ int[] capabilityIndexes = new int[numCapabilities];
+ for (int i = 0; i < numCapabilities; i++) {
+ capabilityIndexes[i] = in.readInt();
+ readGenericInfo(true, in, builder);
+ }
+
+ int numRequirements = in.readInt();
+ int[] requirementIndexes = new int[numRequirements];
+ for (int i = 0; i < numRequirements; i++) {
+ requirementIndexes[i] = in.readInt();
+ readGenericInfo(false, in, builder);
+ }
+
+ // settings
+ EnumSet<Settings> settings = null;
+ int numSettings = in.readInt();
+ if (numSettings > 0) {
+ settings = EnumSet.noneOf(Settings.class);
+ for (int i = 0; i < numSettings; i++) {
+ settings.add(Settings.valueOf(readString(in)));
+ }
+ }
+
+ // startlevel
+ int startlevel = in.readInt();
+ Module module = moduleDataBase.load(location, builder, id, settings, startlevel);
+
+ // last modified
+ module.setlastModified(in.readLong());
+
+ ModuleRevision current = module.getCurrentRevision();
+ addToReadTable(current, moduleIndex, objectTable);
+
+ List<ModuleCapability> capabilities = current.getModuleCapabilities(null);
+ for (int i = 0; i < capabilities.size(); i++) {
+ addToReadTable(capabilities.get(i), capabilityIndexes[i], objectTable);
+ }
+
+ List<ModuleRequirement> requirements = current.getModuleRequirements(null);
+ for (int i = 0; i < requirements.size(); i++) {
+ addToReadTable(requirements.get(i), requirementIndexes[i], objectTable);
+ }
+ }
+
+ private static void writeWire(ModuleWire wire, DataOutputStream out, Map<Object, Integer> objectTable) throws IOException {
+ Wire w = wire;
+ Integer capability = objectTable.get(w.getCapability());
+ Integer provider = objectTable.get(w.getProvider());
+ Integer requirement = objectTable.get(w.getRequirement());
+ Integer requirer = objectTable.get(w.getRequirer());
+
+ if (capability == null || provider == null || requirement == null || requirer == null)
+ throw new NullPointerException("Could not find the expected indexes"); //$NON-NLS-1$
+
+ out.writeInt(addToWriteTable(wire, objectTable));
+
+ out.writeInt(capability);
+ out.writeInt(provider);
+ out.writeInt(requirement);
+ out.writeInt(requirer);
+ }
+
+ private static void readWire(DataInputStream in, Map<Integer, Object> objectTable) throws IOException {
+ int wireIndex = in.readInt();
+
+ ModuleCapability capability = (ModuleCapability) objectTable.get(in.readInt());
+ ModuleRevision provider = (ModuleRevision) objectTable.get(in.readInt());
+ ModuleRequirement requirement = (ModuleRequirement) objectTable.get(in.readInt());
+ ModuleRevision requirer = (ModuleRevision) objectTable.get(in.readInt());
+
+ if (capability == null || provider == null || requirement == null || requirer == null)
+ throw new NullPointerException("Could not find the expected indexes"); //$NON-NLS-1$
+
+ ModuleWire result = new ModuleWire(capability, provider, requirement, requirer);
+
+ addToReadTable(result, wireIndex, objectTable);
+ }
+
+ private static void writeWiring(ModuleWiring wiring, DataOutputStream out, Map<Object, Integer> objectTable) throws IOException {
+ Integer revisionIndex = objectTable.get(wiring.getRevision());
+ if (revisionIndex == null)
+ throw new NullPointerException("Could not find revision for wiring."); //$NON-NLS-1$
+ out.writeInt(revisionIndex);
+
+ List<ModuleCapability> capabilities = wiring.getModuleCapabilities(null);
+ out.writeInt(capabilities.size());
+ for (ModuleCapability capability : capabilities) {
+ Integer capabilityIndex = objectTable.get(capability);
+ if (capabilityIndex == null)
+ throw new NullPointerException("Could not find capability for wiring."); //$NON-NLS-1$
+ out.writeInt(capabilityIndex);
+ }
+
+ List<ModuleRequirement> requirements = wiring.getModuleRequirements(null);
+ out.writeInt(requirements.size());
+ for (ModuleRequirement requirement : requirements) {
+ Integer requirementIndex = objectTable.get(requirement);
+ if (requirementIndex == null)
+ throw new NullPointerException("Could not find requirement for wiring."); //$NON-NLS-1$
+ out.writeInt(requirementIndex);
+ }
+
+ List<ModuleWire> providedWires = wiring.getProvidedModuleWires(null);
+ out.writeInt(providedWires.size());
+ for (ModuleWire wire : providedWires) {
+ Integer wireIndex = objectTable.get(wire);
+ if (wireIndex == null)
+ throw new NullPointerException("Could not find provided wire for wiring."); //$NON-NLS-1$
+ out.writeInt(wireIndex);
+ }
+
+ List<ModuleWire> requiredWires = wiring.getRequiredModuleWires(null);
+ out.writeInt(requiredWires.size());
+ for (ModuleWire wire : requiredWires) {
+ Integer wireIndex = objectTable.get(wire);
+ if (wireIndex == null)
+ throw new NullPointerException("Could not find required wire for wiring."); //$NON-NLS-1$
+ out.writeInt(wireIndex);
+ }
+
+ Collection<String> substituted = wiring.getSubstitutedNames();
+ out.writeInt(substituted.size());
+ for (String pkgName : substituted) {
+ writeString(pkgName, out);
+ }
+ }
+
+ private static ModuleWiring readWiring(DataInputStream in, Map<Integer, Object> objectTable) throws IOException {
+ ModuleRevision revision = (ModuleRevision) objectTable.get(in.readInt());
+ if (revision == null)
+ throw new NullPointerException("Could not find revision for wiring."); //$NON-NLS-1$
+
+ int numCapabilities = in.readInt();
+ List<ModuleCapability> capabilities = new ArrayList<ModuleCapability>(numCapabilities);
+ for (int i = 0; i < numCapabilities; i++) {
+ capabilities.add((ModuleCapability) objectTable.get(in.readInt()));
+ }
+
+ int numRequirements = in.readInt();
+ List<ModuleRequirement> requirements = new ArrayList<ModuleRequirement>(numRequirements);
+ for (int i = 0; i < numRequirements; i++) {
+ requirements.add((ModuleRequirement) objectTable.get(in.readInt()));
+ }
+
+ int numProvidedWires = in.readInt();
+ List<ModuleWire> providedWires = new ArrayList<ModuleWire>(numProvidedWires);
+ for (int i = 0; i < numProvidedWires; i++) {
+ providedWires.add((ModuleWire) objectTable.get(in.readInt()));
+ }
+
+ int numRequiredWires = in.readInt();
+ List<ModuleWire> requiredWires = new ArrayList<ModuleWire>(numRequiredWires);
+ for (int i = 0; i < numRequiredWires; i++) {
+ requiredWires.add((ModuleWire) objectTable.get(in.readInt()));
+ }
+
+ int numSubstitutedNames = in.readInt();
+ Collection<String> substituted = new ArrayList<String>(numSubstitutedNames);
+ for (int i = 0; i < numSubstitutedNames; i++) {
+ substituted.add(readString(in));
+ }
+
+ return new ModuleWiring(revision, capabilities, requirements, providedWires, requiredWires, substituted);
+ }
+
+ private static void writeGenericInfo(String namespace, Map<String, ?> attributes, Map<String, String> directives, DataOutputStream out) throws IOException {
+ writeString(namespace, out);
+ writeMap(attributes, out);
+ writeMap(directives, out);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static void readGenericInfo(boolean isCapability, DataInputStream in, ModuleRevisionBuilder builder) throws IOException {
+ String namespace = readString(in);
+ Map<String, Object> attributes = readMap(in);
+ Map<String, ?> directives = readMap(in);
+ if (isCapability) {
+ builder.addCapability(namespace, (Map<String, String>) directives, attributes);
+ } else {
+ builder.addRequirement(namespace, (Map<String, String>) directives, attributes);
+ }
+
+ }
+
+ private static void writeMap(Map<String, ?> source, DataOutputStream out) throws IOException {
+ if (source == null) {
+ out.writeInt(0);
+ } else {
+ out.writeInt(source.size());
+ Iterator<String> iter = source.keySet().iterator();
+ while (iter.hasNext()) {
+ String key = iter.next();
+ Object value = source.get(key);
+ writeString(key, out);
+ if (value instanceof String) {
+ out.writeByte(VALUE_STRING);
+ writeString((String) value, out);
+ } else if (value instanceof String[]) {
+ out.writeByte(VALUE_STRING_ARRAY);
+ writeStringArray(out, (String[]) value);
+ } else if (value instanceof Boolean) {
+ out.writeByte(VAlUE_BOOLEAN);
+ out.writeBoolean(((Boolean) value).booleanValue());
+ } else if (value instanceof Integer) {
+ out.writeByte(VALUE_INTEGER);
+ out.writeInt(((Integer) value).intValue());
+ } else if (value instanceof Long) {
+ out.writeByte(VALUE_LONG);
+ out.writeLong(((Long) value).longValue());
+ } else if (value instanceof Double) {
+ out.writeByte(VALUE_DOUBLE);
+ out.writeDouble(((Double) value).doubleValue());
+ } else if (value instanceof Version) {
+ out.writeByte(VALUE_VERSION);
+ writeVersion((Version) value, out);
+ } else if (value instanceof URI) {
+ out.writeByte(VALUE_URI);
+ writeString(value.toString(), out);
+ } else if (value instanceof List) {
+ out.writeByte(VALUE_LIST);
+ writeList(out, (List<?>) value);
+ }
+ }
+ }
+ }
+
+ private static Map<String, Object> readMap(DataInputStream in) throws IOException {
+ int count = in.readInt();
+ HashMap<String, Object> result = new HashMap<String, Object>(count);
+ for (int i = 0; i < count; i++) {
+ String key = readString(in);
+ Object value = null;
+ byte type = in.readByte();
+ if (type == VALUE_STRING)
+ value = readString(in);
+ else if (type == VALUE_STRING_ARRAY)
+ value = readStringArray(in);
+ else if (type == VAlUE_BOOLEAN)
+ value = in.readBoolean() ? Boolean.TRUE : Boolean.FALSE;
+ else if (type == VALUE_INTEGER)
+ value = new Integer(in.readInt());
+ else if (type == VALUE_LONG)
+ value = new Long(in.readLong());
+ else if (type == VALUE_DOUBLE)
+ value = new Double(in.readDouble());
+ else if (type == VALUE_VERSION)
+ value = readVersion(in);
+ else if (type == VALUE_URI)
+ try {
+ value = new URI(readString(in));
+ } catch (URISyntaxException e) {
+ value = null;
+ }
+ else if (type == VALUE_LIST)
+ value = readList(in);
+
+ result.put(key, value);
+ }
+ return result;
+ }
+
+ private static void writeStringArray(DataOutputStream out, String[] value) throws IOException {
+ if (value == null) {
+ out.writeInt(0);
+ } else {
+ out.writeInt(value.length);
+ for (int i = 0; i < value.length; i++)
+ writeString(value[i], out);
+ }
+
+ }
+
+ private static String[] readStringArray(DataInputStream in) throws IOException {
+ int count = in.readInt();
+ if (count == 0)
+ return null;
+ String[] result = new String[count];
+ for (int i = 0; i < count; i++)
+ result[i] = readString(in);
+ return result;
+ }
+
+ private static void writeList(DataOutputStream out, List<?> list) throws IOException {
+ if (list.isEmpty()) {
+ out.writeInt(0);
+ return;
+ }
+ byte type = getListType(list);
+ if (type < 0) {
+ out.writeInt(0);
+ return; // don't understand the list type
+ }
+ out.writeInt(list.size());
+ out.writeByte(type);
+ for (Object value : list) {
+ switch (type) {
+ case VALUE_STRING :
+ writeString((String) value, out);
+ break;
+ case VALUE_INTEGER :
+ out.writeInt(((Integer) value).intValue());
+ break;
+ case VALUE_LONG :
+ out.writeLong(((Long) value).longValue());
+ break;
+ case VALUE_DOUBLE :
+ out.writeDouble(((Double) value).doubleValue());
+ break;
+ case VALUE_VERSION :
+ writeVersion((Version) value, out);
+ break;
+ default :
+ break;
+ }
+ }
+ }
+
+ private static byte getListType(List<?> list) {
+ if (list.size() == 0)
+ return -1;
+ Object type = list.get(0);
+ if (type instanceof String)
+ return VALUE_STRING;
+ if (type instanceof Integer)
+ return VALUE_INTEGER;
+ if (type instanceof Long)
+ return VALUE_LONG;
+ if (type instanceof Double)
+ return VALUE_DOUBLE;
+ if (type instanceof Version)
+ return VALUE_VERSION;
+ return -2;
+ }
+
+ private static List<?> readList(DataInputStream in) throws IOException {
+
+ int size = in.readInt();
+ if (size == 0)
+ return new ArrayList<Object>(0);
+ byte listType = in.readByte();
+ List<Object> list = new ArrayList<Object>(size);
+ for (int i = 0; i < size; i++) {
+ switch (listType) {
+ case VALUE_STRING :
+ list.add(readString(in));
+ break;
+ case VALUE_INTEGER :
+ list.add(new Integer(in.readInt()));
+ break;
+ case VALUE_LONG :
+ list.add(new Long(in.readLong()));
+ break;
+ case VALUE_DOUBLE :
+ list.add(new Double(in.readDouble()));
+ break;
+ case VALUE_VERSION :
+ list.add(readVersion(in));
+ break;
+ default :
+ throw new IOException("Invalid type: " + listType); //$NON-NLS-1$
+ }
+ }
+ return list;
+ }
+
+ private static void writeVersion(Version version, DataOutputStream out) throws IOException {
+ if (version == null || version.equals(Version.emptyVersion)) {
+ out.writeByte(NULL);
+ return;
+ }
+ out.writeByte(OBJECT);
+ out.writeInt(version.getMajor());
+ out.writeInt(version.getMinor());
+ out.writeInt(version.getMicro());
+ writeQualifier(version.getQualifier(), out);
+ }
+
+ private static void writeQualifier(String string, DataOutputStream out) throws IOException {
+ if (string != null && string.length() == 0)
+ string = null;
+ writeString(string, out);
+ }
+
+ private static Version readVersion(DataInputStream in) throws IOException {
+ byte tag = in.readByte();
+ if (tag == NULL)
+ return Version.emptyVersion;
+ int majorComponent = in.readInt();
+ int minorComponent = in.readInt();
+ int serviceComponent = in.readInt();
+ String qualifierComponent = readString(in);
+ return (Version) ObjectPool.intern(new Version(majorComponent, minorComponent, serviceComponent, qualifierComponent));
+ }
+
+ private static void writeString(String string, DataOutputStream out) throws IOException {
+ if (string == null)
+ out.writeByte(NULL);
+ else {
+ byte[] data = string.getBytes(UTF_8);
+
+ if (data.length > 65535) {
+ out.writeByte(LONG_STRING);
+ out.writeInt(data.length);
+ out.write(data);
+ } else {
+ out.writeByte(OBJECT);
+ out.writeUTF(string);
+ }
+ }
+ }
+
+ static private String readString(DataInputStream in) throws IOException {
+ byte type = in.readByte();
+ if (type == NULL)
+ return null;
+
+ if (type == LONG_STRING) {
+ int length = in.readInt();
+ byte[] data = new byte[length];
+ in.readFully(data);
+ String string = new String(data, UTF_8);
+
+ return (String) ObjectPool.intern(string);
+ }
+
+ return (String) ObjectPool.intern(in.readUTF());
+ }
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleRequirement.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleRequirement.java
new file mode 100644
index 000000000..886630625
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleRequirement.java
@@ -0,0 +1,140 @@
+/*******************************************************************************
+ * Copyright (c) 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.container;
+
+import java.util.*;
+import org.eclipse.osgi.framework.internal.core.FilterImpl;
+import org.eclipse.osgi.internal.container.Capabilities;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.namespace.*;
+import org.osgi.framework.wiring.BundleCapability;
+import org.osgi.framework.wiring.BundleRequirement;
+import org.osgi.resource.Namespace;
+
+/**
+ * An implementation of {@link BundleRequirement}. This requirement implements
+ * the matches method according to the OSGi specification which includes
+ * implementing the mandatory directive for the osgi.wiring.* namespaces.
+ */
+public class ModuleRequirement implements BundleRequirement {
+ private final String namespace;
+ private final Map<String, String> directives;
+ private final Map<String, Object> attributes;
+ private final ModuleRevision revision;
+
+ ModuleRequirement(String namespace, Map<String, String> directives, Map<String, Object> attributes, ModuleRevision revision) {
+ this.namespace = namespace;
+ this.directives = Collections.unmodifiableMap(directives);
+ this.attributes = Collections.unmodifiableMap(attributes);
+ this.revision = revision;
+ }
+
+ @Override
+ public ModuleRevision getRevision() {
+ return revision;
+ }
+
+ @Override
+ public boolean matches(BundleCapability capability) {
+ if (!namespace.equals(capability.getNamespace()))
+ return false;
+ String filterSpec = directives.get(Namespace.REQUIREMENT_FILTER_DIRECTIVE);
+ FilterImpl f = null;
+ if (filterSpec != null) {
+ try {
+ f = FilterImpl.newInstance(filterSpec);
+ } catch (InvalidSyntaxException e) {
+ return false;
+ }
+ }
+ boolean matchMandatory = PackageNamespace.PACKAGE_NAMESPACE.equals(namespace) || BundleNamespace.BUNDLE_NAMESPACE.equals(namespace) || HostNamespace.HOST_NAMESPACE.equals(namespace);
+ return Capabilities.matches(f, capability, matchMandatory);
+ }
+
+ @Override
+ public String getNamespace() {
+ return namespace;
+ }
+
+ @Override
+ public Map<String, String> getDirectives() {
+ return directives;
+ }
+
+ @Override
+ public Map<String, Object> getAttributes() {
+ return attributes;
+ }
+
+ @Override
+ public ModuleRevision getResource() {
+ return revision;
+ }
+
+ public String toString() {
+ return namespace + ModuleRevision.toString(attributes, false) + ModuleRevision.toString(directives, true);
+ }
+
+ private static final String PACKAGENAME_FILTER_COMPONENT = PackageNamespace.PACKAGE_NAMESPACE + "=";
+
+ DynamicModuleRequirement getDynamicPackageRequirement(ModuleRevision host, String dynamicPkgName) {
+ if (!PackageNamespace.PACKAGE_NAMESPACE.equals(namespace)) {
+ return null;
+ }
+ if (!PackageNamespace.RESOLUTION_DYNAMIC.equals(directives.get(PackageNamespace.REQUIREMENT_RESOLUTION_DIRECTIVE))) {
+ // not dynamic
+ return null;
+ }
+ String dynamicFilter = directives.get(PackageNamespace.REQUIREMENT_FILTER_DIRECTIVE);
+ // TODO we make some assumptions here on the format of the filter string
+ int packageNameBegin = dynamicFilter.indexOf(PACKAGENAME_FILTER_COMPONENT);
+ if (packageNameBegin == -1) {
+ // not much we can do
+ return null;
+ }
+ packageNameBegin += PACKAGENAME_FILTER_COMPONENT.length();
+ int packageNameEnd = dynamicFilter.indexOf(')', packageNameBegin);
+ if (packageNameEnd == -1) {
+ // not much we can do
+ return null;
+ }
+ String filterPackageName = dynamicFilter.substring(packageNameBegin, packageNameEnd);
+ String specificPackageFilter = null;
+ if ("*".equals(filterPackageName)) {
+ // matches all
+ specificPackageFilter = dynamicFilter.replace(PACKAGENAME_FILTER_COMPONENT + filterPackageName, PACKAGENAME_FILTER_COMPONENT + dynamicPkgName);
+ } else if (filterPackageName.endsWith(".*")) {
+ if (dynamicPkgName.startsWith(filterPackageName.substring(0, filterPackageName.length() - 1))) {
+ specificPackageFilter = dynamicFilter.replace(PACKAGENAME_FILTER_COMPONENT + filterPackageName, PACKAGENAME_FILTER_COMPONENT + dynamicPkgName);
+ }
+ } else if (dynamicPkgName.equals(filterPackageName)) {
+ specificPackageFilter = dynamicFilter;
+ }
+
+ if (specificPackageFilter != null) {
+ Map<String, String> dynamicDirectives = new HashMap<String, String>(directives);
+ dynamicDirectives.put(PackageNamespace.REQUIREMENT_FILTER_DIRECTIVE, specificPackageFilter);
+ return new DynamicModuleRequirement(host, dynamicDirectives);
+ }
+ return null;
+ }
+
+ class DynamicModuleRequirement extends ModuleRequirement {
+
+ DynamicModuleRequirement(ModuleRevision host, Map<String, String> directives) {
+ super(ModuleRequirement.this.getNamespace(), directives, ModuleRequirement.this.getAttributes(), host);
+ }
+
+ ModuleRequirement getOriginal() {
+ return ModuleRequirement.this;
+ }
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleResolver.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleResolver.java
new file mode 100644
index 000000000..f2b1b3b3b
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleResolver.java
@@ -0,0 +1,663 @@
+/*******************************************************************************
+ * Copyright (c) 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.container;
+
+import java.util.*;
+import org.apache.felix.resolver.ResolverImpl;
+import org.eclipse.osgi.container.ModuleRequirement.DynamicModuleRequirement;
+import org.eclipse.osgi.internal.container.Converters;
+import org.osgi.framework.Version;
+import org.osgi.framework.hooks.resolver.ResolverHook;
+import org.osgi.framework.namespace.*;
+import org.osgi.framework.wiring.*;
+import org.osgi.resource.*;
+import org.osgi.service.resolver.*;
+
+/**
+ * The module resolver handles calls to the {@link Resolver} service for resolving modules
+ * in a module {@link ModuleContainer container}.
+ */
+class ModuleResolver {
+ final ModuleContainerAdaptor adaptor;
+
+ /**
+ * Constructs the module resolver with the specified resolver hook factory
+ * and resolver.
+ * @param adaptor the container adaptor
+ */
+ ModuleResolver(ModuleContainerAdaptor adaptor) {
+ this.adaptor = adaptor;
+ }
+
+ /**
+ * Attempts to resolve all unresolved modules installed in the specified module database.
+ * returns a delta containing the new wirings or modified wirings that should be
+ * merged into the specified moduleDatabase.
+ * <p>
+ * This method only does read operations on the database no wirings are modified
+ * directly by this method. The returned wirings need to be merged into
+ * the database.
+ * @param triggers the triggers that caused the resolver operation to occur
+ * @param triggersMandatory true if the triggers must be resolved by the resolve process
+ * @param unresolved a snapshot of unresolved revisions
+ * @param wiringCopy the wirings snapshot of the currently resolved revisions
+ * @param moduleDataBase the module database.
+ * @return a delta container the new wirings or modified wirings that should be
+ * merged into the moduleDatabase
+ * @throws ResolutionException
+ */
+ Map<ModuleRevision, ModuleWiring> resolveDelta(Collection<ModuleRevision> triggers, boolean triggersMandatory, Collection<ModuleRevision> unresolved, Map<ModuleRevision, ModuleWiring> wiringCopy, ModuleDataBase moduleDataBase) throws ResolutionException {
+ ResolveProcess resolveProcess = new ResolveProcess(unresolved, triggers, triggersMandatory, wiringCopy, moduleDataBase);
+ Map<Resource, List<Wire>> result = resolveProcess.resolve();
+ return generateDelta(result, wiringCopy);
+ }
+
+ Map<ModuleRevision, ModuleWiring> resolveDynamicDelta(DynamicModuleRequirement dynamicReq, Collection<ModuleRevision> unresolved, Map<ModuleRevision, ModuleWiring> wiringCopy, ModuleDataBase moduleDataBase) throws ResolutionException {
+ ResolveProcess resolveProcess = new ResolveProcess(unresolved, dynamicReq, wiringCopy, moduleDataBase);
+ Map<Resource, List<Wire>> result = resolveProcess.resolve();
+ return generateDelta(result, wiringCopy);
+ }
+
+ private static Map<ModuleRevision, ModuleWiring> generateDelta(Map<Resource, List<Wire>> result, Map<ModuleRevision, ModuleWiring> wiringCopy) {
+ Map<ModuleRevision, Map<ModuleCapability, List<ModuleWire>>> provided = new HashMap<ModuleRevision, Map<ModuleCapability, List<ModuleWire>>>();
+ Map<ModuleRevision, List<ModuleWire>> required = new HashMap<ModuleRevision, List<ModuleWire>>();
+ // First populate the list of provided and required wires for revision
+ // This is done this way to share the wire object between both the provider and requirer
+ for (Map.Entry<Resource, List<Wire>> resultEntry : result.entrySet()) {
+ ModuleRevision revision = (ModuleRevision) resultEntry.getKey();
+ List<ModuleWire> requiredWires = new ArrayList<ModuleWire>(resultEntry.getValue().size());
+ for (Wire wire : resultEntry.getValue()) {
+ ModuleWire moduleWire = new ModuleWire((ModuleCapability) wire.getCapability(), (ModuleRevision) wire.getProvider(), (ModuleRequirement) wire.getRequirement(), (ModuleRevision) wire.getRequirer());
+ requiredWires.add(moduleWire);
+ Map<ModuleCapability, List<ModuleWire>> providedWiresMap = provided.get(moduleWire.getProvider());
+ if (providedWiresMap == null) {
+ providedWiresMap = new HashMap<ModuleCapability, List<ModuleWire>>();
+ provided.put(moduleWire.getProvider(), providedWiresMap);
+ }
+ List<ModuleWire> providedWires = providedWiresMap.get(moduleWire.getCapability());
+ if (providedWires == null) {
+ providedWires = new ArrayList<ModuleWire>();
+ providedWiresMap.put(moduleWire.getCapability(), providedWires);
+ }
+ providedWires.add(moduleWire);
+ }
+ required.put(revision, requiredWires);
+ }
+
+ Map<ModuleRevision, ModuleWiring> delta = new HashMap<ModuleRevision, ModuleWiring>();
+ // now create the ModuleWiring for the newly resolved revisions
+ for (ModuleRevision revision : required.keySet()) {
+ ModuleWiring existingWiring = wiringCopy.get(revision);
+ if (existingWiring == null) {
+ delta.put(revision, createNewWiring(revision, provided, required));
+ } else {
+ // this is to handle dynamic imports
+ delta.put(revision, createWiringDelta(revision, existingWiring, provided.get(revision), required.get(revision)));
+ }
+ }
+ // Also need to create the wiring deltas for already resolved bundles
+ // This should only include updating provided wires and
+ // for fragments it may include new hosts
+ for (ModuleRevision revision : provided.keySet()) {
+ ModuleWiring existingWiring = wiringCopy.get(revision);
+ if (existingWiring != null && !delta.containsKey(revision)) {
+ delta.put(revision, createWiringDelta(revision, existingWiring, provided.get(revision), required.get(revision)));
+ }
+ }
+ return delta;
+ }
+
+ private static ModuleWiring createNewWiring(ModuleRevision revision, Map<ModuleRevision, Map<ModuleCapability, List<ModuleWire>>> provided, Map<ModuleRevision, List<ModuleWire>> required) {
+ Map<ModuleCapability, List<ModuleWire>> providedWireMap = provided.get(revision);
+ if (providedWireMap == null)
+ providedWireMap = Collections.emptyMap();
+ List<ModuleWire> requiredWires = required.get(revision);
+ if (requiredWires == null)
+ requiredWires = Collections.emptyList();
+
+ List<ModuleCapability> capabilities = new ArrayList<ModuleCapability>(revision.getModuleCapabilities(null));
+ ListIterator<ModuleCapability> iCapabilities = capabilities.listIterator(capabilities.size());
+ List<ModuleRequirement> requirements = new ArrayList<ModuleRequirement>(revision.getModuleRequirements(null));
+ ListIterator<ModuleRequirement> iRequirements = requirements.listIterator(requirements.size());
+
+ // add fragment capabilities and requirements
+ List<ModuleCapability> hostCapabilities = revision.getModuleCapabilities(HostNamespace.HOST_NAMESPACE);
+ ModuleCapability hostCapability = hostCapabilities.isEmpty() ? null : hostCapabilities.get(0);
+ if (hostCapability != null) {
+ addFragmentContent(providedWireMap.get(hostCapability), iCapabilities, iRequirements);
+ }
+
+ removeNonEffectiveCapabilities(iCapabilities);
+ removeNonEffectiveRequirements(iRequirements, requiredWires);
+ Collection<String> substituted = removeSubstitutedCapabilities(iCapabilities, requiredWires);
+
+ List<ModuleWire> providedWires = new ArrayList<ModuleWire>();
+ addProvidedWires(providedWireMap, providedWires, capabilities);
+
+ return new ModuleWiring(revision, capabilities, requirements, providedWires, requiredWires, substituted);
+ }
+
+ private static Collection<String> removeSubstitutedCapabilities(ListIterator<ModuleCapability> iCapabilities, List<ModuleWire> requiredWires) {
+ Collection<String> substituted = null;
+ for (ModuleWire moduleWire : requiredWires) {
+ if (!PackageNamespace.PACKAGE_NAMESPACE.equals(moduleWire.getCapability().getNamespace()))
+ continue;
+ String packageName = (String) moduleWire.getCapability().getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE);
+ rewind(iCapabilities);
+ while (iCapabilities.hasNext()) {
+ ModuleCapability capability = iCapabilities.next();
+ if (PackageNamespace.PACKAGE_NAMESPACE.equals(capability.getNamespace())) {
+ if (packageName.equals(capability.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE))) {
+ // found a package capability with the same name as a package that got imported
+ // this indicates a substitution
+ iCapabilities.remove();
+ if (substituted == null) {
+ substituted = new ArrayList<String>();
+ }
+ substituted.add(packageName);
+ if (!substituted.contains(packageName)) {
+ substituted.add(packageName);
+ }
+ }
+ }
+ }
+ }
+ return substituted == null ? Collections.<String> emptyList() : substituted;
+ }
+
+ private static void removeNonEffectiveRequirements(ListIterator<ModuleRequirement> iRequirements, List<ModuleWire> requiredWires) {
+ rewind(iRequirements);
+ requirements: while (iRequirements.hasNext()) {
+ ModuleRequirement requirement = iRequirements.next();
+ // check the effective directive;
+ Object effective = requirement.getAttributes().get(Namespace.REQUIREMENT_EFFECTIVE_DIRECTIVE);
+ if (effective != null && !Namespace.EFFECTIVE_RESOLVE.equals(effective)) {
+ iRequirements.remove();
+ break requirements;
+ }
+ // check the resolution directive
+ Object resolution = requirement.getAttributes().get(Namespace.REQUIREMENT_RESOLUTION_DIRECTIVE);
+ if (resolution != null && !Namespace.RESOLUTION_MANDATORY.equals(resolution)) {
+ boolean found = false;
+ // need to check the wires to see if the optional requirement is resolved
+ wires: for (ModuleWire wire : requiredWires) {
+ if (wire.getRequirement().equals(requirement)) {
+ found = true;
+ break wires;
+ }
+ }
+ if (!found) {
+ // optional requirement is not resolved
+ iRequirements.remove();
+ break requirements;
+ }
+ }
+ }
+ }
+
+ static void removeNonEffectiveCapabilities(ListIterator<ModuleCapability> iCapabilities) {
+ rewind(iCapabilities);
+ while (iCapabilities.hasNext()) {
+ Object effective = iCapabilities.next().getAttributes().get(Namespace.CAPABILITY_EFFECTIVE_DIRECTIVE);
+ if (effective != null && !Namespace.EFFECTIVE_RESOLVE.equals(effective))
+ iCapabilities.remove();
+ }
+ }
+
+ private static void addFragmentContent(List<ModuleWire> hostWires, ListIterator<ModuleCapability> iCapabilities, ListIterator<ModuleRequirement> iRequirements) {
+ if (hostWires == null)
+ return;
+ for (ModuleWire hostWire : hostWires) {
+ // add fragment capabilities
+ String currentNamespace = null;
+ List<ModuleCapability> fragmentCapabilities = hostWire.getRequirer().getModuleCapabilities(null);
+ for (ModuleCapability fragmentCapability : fragmentCapabilities) {
+ if (IdentityNamespace.IDENTITY_NAMESPACE.equals(fragmentCapability.getNamespace()))
+ continue; // identity is not a payload
+ if (!fragmentCapability.getNamespace().equals(currentNamespace)) {
+ currentNamespace = fragmentCapability.getNamespace();
+ fastForward(iCapabilities);
+ while (iCapabilities.hasPrevious()) {
+ if (iCapabilities.previous().getNamespace().equals(currentNamespace)) {
+ iCapabilities.next(); // put position after the last one
+ break;
+ }
+ }
+ }
+ iCapabilities.add(fragmentCapability);
+ }
+ // add fragment requirements
+ currentNamespace = null;
+ List<ModuleRequirement> fragmentRequriements = hostWire.getRequirer().getModuleRequirements(null);
+ for (ModuleRequirement fragmentRequirement : fragmentRequriements) {
+ String fragNamespace = fragmentRequirement.getNamespace();
+ if (HostNamespace.HOST_NAMESPACE.equals(fragNamespace) || ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE.equals(fragNamespace))
+ continue; // host and osgi.ee is not a payload
+ if (!fragmentRequirement.getNamespace().equals(currentNamespace)) {
+ currentNamespace = fragmentRequirement.getNamespace();
+ fastForward(iRequirements);
+ while (iRequirements.hasPrevious()) {
+ if (iRequirements.previous().getNamespace().equals(currentNamespace)) {
+ iRequirements.next(); // put position after the last one
+ break;
+ }
+ }
+ }
+ iRequirements.add(fragmentRequirement);
+ }
+ }
+ }
+
+ private static void addProvidedWires(Map<ModuleCapability, List<ModuleWire>> toAdd, List<ModuleWire> existing, final List<ModuleCapability> orderedCapabilities) {
+ if (toAdd == null)
+ return;
+ int originalSize = existing.size();
+ for (ModuleCapability capability : orderedCapabilities) {
+ List<ModuleWire> newWires = toAdd.get(capability);
+ if (newWires != null) {
+ existing.addAll(newWires);
+ }
+ }
+ if (originalSize != 0) {
+ Collections.sort(existing, new Comparator<ModuleWire>() {
+ @Override
+ public int compare(ModuleWire w1, ModuleWire w2) {
+ int index1 = orderedCapabilities.indexOf(w1.getCapability());
+ int index2 = orderedCapabilities.indexOf(w2.getCapability());
+ return index1 - index2;
+ }
+ });
+ }
+ }
+
+ private static void addRequiredWires(List<ModuleWire> toAdd, List<ModuleWire> existing, final List<ModuleRequirement> orderedRequirements) {
+ if (toAdd == null)
+ return;
+ int originalSize = existing.size();
+ existing.addAll(toAdd);
+ if (originalSize != 0) {
+ Collections.sort(existing, new Comparator<ModuleWire>() {
+ @Override
+ public int compare(ModuleWire w1, ModuleWire w2) {
+ int index1 = orderedRequirements.indexOf(w1.getRequirement());
+ int index2 = orderedRequirements.indexOf(w2.getRequirement());
+ return index1 - index2;
+ }
+ });
+ }
+ }
+
+ private static void fastForward(ListIterator<?> listIterator) {
+ while (listIterator.hasNext())
+ listIterator.next();
+ }
+
+ static void rewind(ListIterator<?> listIterator) {
+ while (listIterator.hasPrevious())
+ listIterator.previous();
+ }
+
+ @SuppressWarnings("unchecked")
+ private static ModuleWiring createWiringDelta(ModuleRevision revision, ModuleWiring existingWiring, Map<ModuleCapability, List<ModuleWire>> providedWireMap, List<ModuleWire> requiredWires) {
+ // Create a ModuleWiring that only contains the new ordered list of provided wires
+ List<ModuleWire> existingProvided = existingWiring.getProvidedModuleWires(null);
+ addProvidedWires(providedWireMap, existingProvided, existingWiring.getModuleCapabilities(null));
+
+ // Also need to include any new required wires that may have be added for fragment hosts
+ // Also will be needed for dynamic imports
+ List<ModuleWire> existingRequired = existingWiring.getRequiredModuleWires(null);
+ addRequiredWires(requiredWires, existingRequired, existingWiring.getModuleRequirements(null));
+ return new ModuleWiring(revision, Collections.EMPTY_LIST, Collections.EMPTY_LIST, existingProvided, existingRequired, Collections.EMPTY_LIST);
+ }
+
+ static boolean isSingleton(ModuleRevision revision) {
+ List<Capability> identities = revision.getCapabilities(IdentityNamespace.IDENTITY_NAMESPACE);
+ if (identities.isEmpty())
+ return false;
+ return "true".equals(identities.get(0).getDirectives().get(IdentityNamespace.CAPABILITY_SINGLETON_DIRECTIVE)); //$NON-NLS-1$
+ }
+
+ static Version getVersion(Capability c) {
+ String versionAttr = null;
+ String namespace = c.getNamespace();
+ if (IdentityNamespace.IDENTITY_NAMESPACE.equals(namespace)) {
+ versionAttr = IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE;
+ } else if (PackageNamespace.PACKAGE_NAMESPACE.equals(namespace)) {
+ versionAttr = PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE;
+ } else if (BundleNamespace.BUNDLE_NAMESPACE.equals(namespace)) {
+ versionAttr = BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE;
+ } else if (HostNamespace.HOST_NAMESPACE.equals(namespace)) {
+ versionAttr = HostNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE;
+ } else {
+ // Just default to version attribute
+ versionAttr = IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE;
+ }
+ Object version = c.getAttributes().get(versionAttr);
+ return version instanceof Version ? (Version) version : Version.emptyVersion;
+ }
+
+ class ResolveProcess extends ResolveContext implements Comparator<Capability> {
+ private final Collection<ModuleRevision> unresolved;
+ private final Collection<ModuleRevision> disabled;
+ private final Collection<ModuleRevision> triggers;
+ private final Collection<ModuleRevision> optionals;
+ private final boolean triggersMandatory;
+ private final ModuleDataBase moduleDataBase;
+ private final Map<ModuleRevision, ModuleWiring> wirings;
+ private final DynamicModuleRequirement dynamicReq;
+ private volatile ResolverHook hook = null;
+ private volatile Map<String, Collection<ModuleRevision>> byName = null;
+
+ ResolveProcess(Collection<ModuleRevision> unresolved, Collection<ModuleRevision> triggers, boolean triggersMandatory, Map<ModuleRevision, ModuleWiring> wirings, ModuleDataBase moduleDataBase) {
+ this.unresolved = unresolved;
+ this.disabled = new HashSet<ModuleRevision>(unresolved);
+ this.triggers = triggers;
+ this.triggersMandatory = triggersMandatory;
+ this.optionals = new ArrayList<ModuleRevision>(unresolved);
+ if (this.triggersMandatory) {
+ this.optionals.removeAll(triggers);
+ }
+ this.wirings = wirings;
+ this.moduleDataBase = moduleDataBase;
+ this.dynamicReq = null;
+ }
+
+ ResolveProcess(Collection<ModuleRevision> unresolved, DynamicModuleRequirement dynamicReq, Map<ModuleRevision, ModuleWiring> wirings, ModuleDataBase moduleDataBase) {
+ this.unresolved = unresolved;
+ this.disabled = new HashSet<ModuleRevision>(unresolved);
+ ModuleRevision revision = dynamicReq.getRevision();
+ this.triggers = new ArrayList<ModuleRevision>(1);
+ this.triggers.add(revision);
+ this.triggersMandatory = false;
+ this.optionals = new ArrayList<ModuleRevision>(unresolved);
+ this.wirings = wirings;
+ this.moduleDataBase = moduleDataBase;
+ this.dynamicReq = dynamicReq;
+ }
+
+ @Override
+ public List<Capability> findProviders(Requirement requirement) {
+ List<ModuleCapability> candidates = moduleDataBase.findCapabilities((ModuleRequirement) requirement);
+ return filterProviders(requirement, candidates);
+ }
+
+ private List<Capability> filterProviders(Requirement requirement, List<ModuleCapability> candidates) {
+ ListIterator<ModuleCapability> iCandidates = candidates.listIterator();
+ filterDisabled(iCandidates);
+ removeNonEffectiveCapabilities(iCandidates);
+ removeSubstituted(iCandidates);
+ hook.filterMatches((BundleRequirement) requirement, Converters.asListBundleCapability(candidates));
+ Collections.sort(candidates, this);
+ return Converters.asListCapability(candidates);
+ }
+
+ private void filterDisabled(ListIterator<ModuleCapability> iCandidates) {
+ rewind(iCandidates);
+ while (iCandidates.hasNext()) {
+ if (disabled.contains(iCandidates.next().getResource()))
+ iCandidates.remove();
+ }
+ }
+
+ private void removeSubstituted(ListIterator<ModuleCapability> iCapabilities) {
+ rewind(iCapabilities);
+ while (iCapabilities.hasNext()) {
+ ModuleCapability capability = iCapabilities.next();
+ ModuleWiring wiring = wirings.get(capability.getRevision());
+ if (wiring != null && wiring.isSubtituted(capability)) {
+ iCapabilities.remove();
+ }
+ }
+ }
+
+ @Override
+ public int insertHostedCapability(List<Capability> capabilities, HostedCapability hostedCapability) {
+ int index = Collections.binarySearch(capabilities, hostedCapability, this);
+ if (index < 0)
+ index = -index - 1;
+ capabilities.add(index, hostedCapability);
+ return index;
+ }
+
+ @Override
+ public boolean isEffective(Requirement requirement) {
+ String effective = requirement.getDirectives().get(Namespace.REQUIREMENT_EFFECTIVE_DIRECTIVE);
+ return effective == null || Namespace.EFFECTIVE_RESOLVE.equals(effective);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Map<Resource, Wiring> getWirings() {
+ Map<?, ?> raw = wirings;
+ return Collections.unmodifiableMap((Map<Resource, Wiring>) raw);
+ }
+
+ @Override
+ public Collection<Resource> getMandatoryResources() {
+ if (triggersMandatory) {
+ return Converters.asCollectionResource(triggers);
+ }
+ return super.getMandatoryResources();
+ }
+
+ @Override
+ public Collection<Resource> getOptionalResources() {
+ return Converters.asCollectionResource(optionals);
+ }
+
+ Map<Resource, List<Wire>> resolve() throws ResolutionException {
+ hook = adaptor.getResolverHookFactory().begin(Converters.asListBundleRevision((List<? extends BundleRevision>) triggers));
+ try {
+ filterResolvable();
+ selectSingletons();
+ // remove disabled from optional and triggers to prevent the resolver from resolving them
+ optionals.removeAll(disabled);
+ if (triggers.removeAll(disabled) && triggersMandatory) {
+ throw new ResolutionException("Could not resolve mandatory modules because another singleton was selected or the module was disabled: " + disabled);
+ }
+ if (dynamicReq != null) {
+ return resolveDynamic();
+ }
+ return adaptor.getResolver().resolve(this);
+ } finally {
+ hook.end();
+ }
+ }
+
+ private Map<Resource, List<Wire>> resolveDynamic() throws ResolutionException {
+ Resolver resolver = adaptor.getResolver();
+ if (!(resolver instanceof ResolverImpl)) {
+ throw new ResolutionException("Dynamic import resolution not supported by the resolver: " + resolver.getClass());
+ }
+ List<Capability> dynamicMatches = filterProviders(dynamicReq.getOriginal(), moduleDataBase.findCapabilities(dynamicReq));
+ Collection<Resource> ondemandFragments = Converters.asCollectionResource(moduleDataBase.getFragmentRevisions());
+
+ return ((ResolverImpl) resolver).resolve(this, dynamicReq.getRevision(), dynamicReq.getOriginal(), dynamicMatches, ondemandFragments);
+
+ }
+
+ private void filterResolvable() {
+ Collection<ModuleRevision> enabledCandidates = new ArrayList<ModuleRevision>(unresolved);
+ hook.filterResolvable(Converters.asListBundleRevision((List<? extends BundleRevision>) enabledCandidates));
+ disabled.removeAll(enabledCandidates);
+ }
+
+ private void selectSingletons() {
+ Map<String, Collection<ModuleRevision>> selectedSingletons = new HashMap<String, Collection<ModuleRevision>>();
+ for (ModuleRevision revision : unresolved) {
+ if (!isSingleton(revision) || disabled.contains(revision))
+ continue;
+ String bsn = revision.getSymbolicName();
+ Collection<ModuleRevision> selected = selectedSingletons.get(bsn);
+ if (selected != null)
+ continue; // already processed the bsn
+ selected = new ArrayList<ModuleRevision>(1);
+ selectedSingletons.put(bsn, selected);
+
+ // TODO out of band call that obtains the read lock
+ // Should generate our own copy Map<String, Collection<ModuleRevision>>
+ Collection<ModuleRevision> sameBSN = getRevisions(bsn);
+ if (sameBSN.size() < 2) {
+ selected.add(revision);
+ continue;
+ }
+ // prime selected with resolved singleton bundles
+ for (ModuleRevision singleton : sameBSN) {
+ if (isSingleton(singleton) && wirings.containsKey(singleton))
+ selected.add(singleton);
+ }
+ // get the collision map for the BSN
+ Map<ModuleRevision, Collection<ModuleRevision>> collisionMap = getCollisionMap(sameBSN);
+ // process the collision map
+ for (ModuleRevision singleton : sameBSN) {
+ if (selected.contains(singleton))
+ continue; // no need to process resolved bundles
+ Collection<ModuleRevision> collisions = collisionMap.get(singleton);
+ if (collisions == null || disabled.contains(singleton))
+ continue; // not a singleton or not resolvable
+ Collection<ModuleRevision> pickOneToResolve = new ArrayList<ModuleRevision>();
+ for (ModuleRevision collision : collisions) {
+ if (selected.contains(collision)) {
+ // Must fail since there is already a selected bundle which is a collision of the singleton bundle
+ disabled.add(singleton);
+ // TODO add resolver diagnostics here
+ //state.addResolverError(singleton.getBundleDescription(), ResolverError.SINGLETON_SELECTION, collision.getBundleDescription().toString(), null);
+ break;
+ }
+ if (!pickOneToResolve.contains(collision))
+ pickOneToResolve.add(collision);
+ }
+ // need to make sure the bundle does not collide from the POV of another entry
+ for (Map.Entry<ModuleRevision, Collection<ModuleRevision>> collisionEntry : collisionMap.entrySet()) {
+ if (collisionEntry.getKey() != singleton && collisionEntry.getValue().contains(singleton)) {
+ if (selected.contains(collisionEntry.getKey())) {
+ // Must fail since there is already a selected bundle for which the singleton bundle is a collision
+ disabled.add(singleton);
+ // TODO add resolver diagnostics here
+ // state.addResolverError(singleton.getBundleDescription(), ResolverError.SINGLETON_SELECTION, collisionEntry.getKey().getBundleDescription().toString(), null);
+ break;
+ }
+ if (!pickOneToResolve.contains(collisionEntry.getKey()))
+ pickOneToResolve.add(collisionEntry.getKey());
+ }
+ }
+ if (!disabled.contains(singleton)) {
+ pickOneToResolve.add(singleton);
+ selected.add(pickOneToResolve(pickOneToResolve));
+ }
+ }
+ }
+ }
+
+ private Collection<ModuleRevision> getRevisions(String name) {
+ Map<String, Collection<ModuleRevision>> current = byName;
+ if (current == null) {
+ Set<ModuleRevision> revisions = new HashSet<ModuleRevision>();
+ revisions.addAll(unresolved);
+ revisions.addAll(wirings.keySet());
+ current = new HashMap<String, Collection<ModuleRevision>>();
+ for (ModuleRevision revision : revisions) {
+ Collection<ModuleRevision> sameName = current.get(revision.getSymbolicName());
+ if (sameName == null) {
+ sameName = new ArrayList<ModuleRevision>();
+ current.put(revision.getSymbolicName(), sameName);
+ }
+ sameName.add(revision);
+ }
+ byName = current;
+ }
+ Collection<ModuleRevision> result = current.get(name);
+ if (result == null) {
+ return Collections.emptyList();
+ }
+ return result;
+ }
+
+ private ModuleRevision pickOneToResolve(Collection<ModuleRevision> pickOneToResolve) {
+ ModuleRevision selectedVersion = null;
+ for (ModuleRevision singleton : pickOneToResolve) {
+ if (selectedVersion == null)
+ selectedVersion = singleton;
+ boolean higherVersion = selectedVersion.getVersion().compareTo(singleton.getVersion()) < 0;
+ if (higherVersion)
+ selectedVersion = singleton;
+ }
+
+ for (ModuleRevision singleton : pickOneToResolve) {
+ if (singleton != selectedVersion) {
+ disabled.add(singleton);
+ // TODO add resolver diagnostic here.
+ // state.addResolverError(singleton.getBundleDescription(), ResolverError.SINGLETON_SELECTION, selectedVersion.getBundleDescription().toString(), null);
+ }
+ }
+ return selectedVersion;
+ }
+
+ private Map<ModuleRevision, Collection<ModuleRevision>> getCollisionMap(Collection<ModuleRevision> sameBSN) {
+ Map<ModuleRevision, Collection<ModuleRevision>> result = new HashMap<ModuleRevision, Collection<ModuleRevision>>();
+ for (ModuleRevision singleton : sameBSN) {
+ if (!isSingleton(singleton) || disabled.contains(singleton))
+ continue; // ignore non-singleton and non-resolvable
+ List<BundleCapability> capabilities = new ArrayList<BundleCapability>(sameBSN.size() - 1);
+ for (ModuleRevision collision : sameBSN) {
+ if (collision == singleton || !isSingleton(collision) || disabled.contains(collision))
+ continue; // Ignore the bundle we are checking and non-singletons and non-resolvable
+ capabilities.add(getIdentity(collision));
+ }
+ hook.filterSingletonCollisions(getIdentity(singleton), capabilities);
+ Collection<ModuleRevision> collisionCandidates = new ArrayList<ModuleRevision>(capabilities.size());
+ for (BundleCapability identity : capabilities) {
+ collisionCandidates.add((ModuleRevision) identity.getRevision());
+ }
+ result.put(singleton, collisionCandidates);
+ }
+ return result;
+ }
+
+ private BundleCapability getIdentity(ModuleRevision bundle) {
+ List<BundleCapability> identities = bundle.getDeclaredCapabilities(IdentityNamespace.IDENTITY_NAMESPACE);
+ return identities.isEmpty() ? null : identities.get(0);
+ }
+
+ @Override
+ public int compare(Capability c1, Capability c2) {
+ // TODO Ideally this policy should be handled by the ModuleDataBase.
+ // To do that the wirings would have to be provided since the wirings may
+ // be a subset of the current wirings provided by the ModuleDataBase
+ boolean resolved1 = wirings.get(c1.getResource()) != null;
+ boolean resolved2 = wirings.get(c2.getResource()) != null;
+ if (resolved1 != resolved2)
+ return resolved1 ? -1 : 1;
+
+ Version v1 = getVersion(c1);
+ Version v2 = getVersion(c2);
+ int versionCompare = -(v1.compareTo(v2));
+ if (versionCompare != 0)
+ return versionCompare;
+
+ // We assume all resources here come from us and are ModuleRevision objects
+ ModuleRevision m1 = (ModuleRevision) c1.getResource();
+ ModuleRevision m2 = (ModuleRevision) c2.getResource();
+ Long id1 = m1.getRevisions().getModule().getId();
+ Long id2 = m2.getRevisions().getModule().getId();
+
+ if (id1.equals(id2) && !m1.equals(m2)) {
+ // sort based on revision ordering
+ List<ModuleRevision> revisions = m1.getRevisions().getModuleRevisions();
+ int index1 = revisions.indexOf(m1);
+ int index2 = revisions.indexOf(m2);
+ // we want to sort the indexes from highest to lowest
+ return index2 - index1;
+ }
+ return id1.compareTo(id2);
+ }
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleRevision.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleRevision.java
new file mode 100644
index 000000000..7610b3f9a
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleRevision.java
@@ -0,0 +1,199 @@
+/*******************************************************************************
+ * Copyright (c) 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.container;
+
+import java.util.*;
+import java.util.Map.Entry;
+import org.eclipse.osgi.container.ModuleRevisionBuilder.GenericInfo;
+import org.eclipse.osgi.internal.container.Converters;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Version;
+import org.osgi.framework.namespace.IdentityNamespace;
+import org.osgi.framework.wiring.*;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
+
+/**
+ * An implementation of {@link BundleRevision}.
+ */
+public class ModuleRevision implements BundleRevision {
+ private final String symbolicName;
+ private final Version version;
+ private final int types;
+ private final List<ModuleCapability> capabilities;
+ private final List<ModuleRequirement> requirements;
+ private final ModuleRevisions revisions;
+
+ ModuleRevision(String symbolicName, Version version, int types, List<GenericInfo> capabilityInfos, List<GenericInfo> requirementInfos, ModuleRevisions revisions) {
+ this.symbolicName = symbolicName;
+ this.version = version;
+ this.types = types;
+ this.capabilities = createCapabilities(capabilityInfos);
+ this.requirements = createRequirements(requirementInfos);
+ this.revisions = revisions;
+ }
+
+ private List<ModuleCapability> createCapabilities(List<GenericInfo> capabilityInfos) {
+ if (capabilityInfos == null || capabilityInfos.isEmpty())
+ return Collections.emptyList();
+ List<ModuleCapability> result = new ArrayList<ModuleCapability>(capabilityInfos.size());
+ for (GenericInfo info : capabilityInfos) {
+ result.add(new ModuleCapability(info.namespace, info.directives, info.attributes, this));
+ }
+ return result;
+ }
+
+ private List<ModuleRequirement> createRequirements(List<GenericInfo> requirementInfos) {
+ if (requirementInfos == null || requirementInfos.isEmpty())
+ return Collections.emptyList();
+ List<ModuleRequirement> result = new ArrayList<ModuleRequirement>(requirementInfos.size());
+ for (GenericInfo info : requirementInfos) {
+ result.add(new ModuleRequirement(info.namespace, info.directives, info.attributes, this));
+ }
+ return result;
+ }
+
+ @Override
+ public Bundle getBundle() {
+ return revisions.getBundle();
+ }
+
+ @Override
+ public String getSymbolicName() {
+ return symbolicName;
+ }
+
+ @Override
+ public Version getVersion() {
+ return version;
+ }
+
+ @Override
+ public List<BundleCapability> getDeclaredCapabilities(String namespace) {
+ return Converters.asListBundleCapability(getModuleCapabilities(namespace));
+ }
+
+ @Override
+ public List<BundleRequirement> getDeclaredRequirements(String namespace) {
+ return Converters.asListBundleRequirement(getModuleRequirements(namespace));
+ }
+
+ /**
+ * Returns the capabilities declared by this revision
+ * @param namespace The namespace of the declared capabilities to return or
+ * {@code null} to return the declared capabilities from all namespaces.
+ * @return An unmodifiable list containing the declared capabilities.
+ */
+ public List<ModuleCapability> getModuleCapabilities(String namespace) {
+ if (namespace == null)
+ return Collections.unmodifiableList(capabilities);
+ List<ModuleCapability> result = new ArrayList<ModuleCapability>();
+ for (ModuleCapability capability : capabilities) {
+ if (namespace.equals(capability.getNamespace())) {
+ result.add(capability);
+ }
+ }
+ return Collections.unmodifiableList(result);
+ }
+
+ /**
+ * Returns the requirements declared by this revision
+ * @param namespace The namespace of the declared requirements to return or
+ * {@code null} to return the declared requirements from all namespaces.
+ * @return An unmodifiable list containing the declared requirements.
+ */
+ public List<ModuleRequirement> getModuleRequirements(String namespace) {
+ if (namespace == null)
+ return Collections.unmodifiableList(requirements);
+ List<ModuleRequirement> result = new ArrayList<ModuleRequirement>();
+ for (ModuleRequirement requirement : requirements) {
+ if (namespace.equals(requirement.getNamespace())) {
+ result.add(requirement);
+ }
+ }
+ return Collections.unmodifiableList(result);
+ }
+
+ @Override
+ public int getTypes() {
+ return types;
+ }
+
+ @Override
+ public ModuleWiring getWiring() {
+ return revisions.getContainer().getWiring(this);
+ }
+
+ @Override
+ public List<Capability> getCapabilities(String namespace) {
+ return Converters.asListCapability(getDeclaredCapabilities(namespace));
+ }
+
+ @Override
+ public List<Requirement> getRequirements(String namespace) {
+ return Converters.asListRequirement(getDeclaredRequirements(namespace));
+ }
+
+ /**
+ * Returns the {@link ModuleRevisions revisions} for this revision.
+ * @return the {@link ModuleRevisions revisions} for this revision.
+ */
+ public ModuleRevisions getRevisions() {
+ return revisions;
+ }
+
+ boolean isCurrent() {
+ return this.equals(revisions.getCurrentRevision());
+ }
+
+ @Override
+ public String toString() {
+ List<ModuleCapability> identities = getModuleCapabilities(IdentityNamespace.IDENTITY_NAMESPACE);
+ if (identities.isEmpty())
+ return super.toString();
+ return identities.get(0).toString();
+ }
+
+ static <V> String toString(Map<String, V> map, boolean directives) {
+ if (map.size() == 0)
+ return ""; //$NON-NLS-1$
+ String assignment = directives ? ":=" : "="; //$NON-NLS-1$ //$NON-NLS-2$
+ Set<Entry<String, V>> set = map.entrySet();
+ StringBuffer sb = new StringBuffer();
+ for (Entry<String, V> entry : set) {
+ sb.append("; "); //$NON-NLS-1$
+ String key = entry.getKey();
+ Object value = entry.getValue();
+ if (value instanceof List) {
+ @SuppressWarnings("unchecked")
+ List<Object> list = (List<Object>) value;
+ if (list.size() == 0)
+ continue;
+ Object component = list.get(0);
+ String className = component.getClass().getName();
+ String type = className.substring(className.lastIndexOf('.') + 1);
+ sb.append(key).append(':').append("List<").append(type).append(">").append(assignment).append('"'); //$NON-NLS-1$ //$NON-NLS-2$
+ for (Object object : list)
+ sb.append(object).append(',');
+ sb.setLength(sb.length() - 1);
+ sb.append('"');
+ } else {
+ String type = ""; //$NON-NLS-1$
+ if (!(value instanceof String)) {
+ String className = value.getClass().getName();
+ type = ":" + className.substring(className.lastIndexOf('.') + 1); //$NON-NLS-1$
+ }
+ sb.append(key).append(type).append(assignment).append('"').append(value).append('"');
+ }
+ }
+ return sb.toString();
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleRevisionBuilder.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleRevisionBuilder.java
new file mode 100644
index 000000000..941337523
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleRevisionBuilder.java
@@ -0,0 +1,142 @@
+/*******************************************************************************
+ * Copyright (c) 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.container;
+
+import java.util.*;
+import org.osgi.framework.Version;
+
+/**
+ * A builder for creating module {@link ModuleRevision} objects. A builder can only be used by
+ * the module {@link ModuleContainer container} to build revisions when
+ * {@link ModuleContainer#install(Module, String, ModuleRevisionBuilder)
+ * installing} or {@link ModuleContainer#update(Module, ModuleRevisionBuilder) updating} a module.
+ * <p>
+ * The builder provides the instructions to the container for creating a {@link ModuleRevision}.
+ */
+public class ModuleRevisionBuilder {
+ /**
+ * Provides information about a capability or requirement
+ */
+ static class GenericInfo {
+ GenericInfo(String namespace, Map<String, String> directives, Map<String, Object> attributes) {
+ this.namespace = namespace;
+ this.directives = directives;
+ this.attributes = attributes;
+ }
+
+ final String namespace;
+ final Map<String, String> directives;
+ final Map<String, Object> attributes;
+ }
+
+ private String symbolicName = null;
+ private Version version = Version.emptyVersion;
+ private int types = 0;
+ private List<GenericInfo> capabilityInfos = null;
+ private List<GenericInfo> requirementInfos = null;
+
+ /**
+ * Constructs a new module builder
+ */
+ public ModuleRevisionBuilder() {
+ // nothing
+ }
+
+ /**
+ * Sets the symbolic name for the builder
+ * @param symbolicName the symbolic name
+ */
+ public void setSymbolicName(String symbolicName) {
+ this.symbolicName = symbolicName;
+ }
+
+ /**
+ * Sets the module version for the builder.
+ * @param version the version
+ */
+ public void setVersion(Version version) {
+ this.version = version;
+ }
+
+ /**
+ * Sets the module types for the builder.
+ * @param types the module types
+ */
+ public void setTypes(int types) {
+ this.types = types;
+ }
+
+ /**
+ * Adds a capability to this builder using the specified namespace, directives and attributes
+ * @param namespace the namespace of the capability
+ * @param directives the directives of the capability
+ * @param attributes the attributes of the capability
+ */
+ public void addCapability(String namespace, Map<String, String> directives, Map<String, Object> attributes) {
+ capabilityInfos = addGenericInfo(capabilityInfos, namespace, directives, attributes);
+ }
+
+ /**
+ * Adds a requirement to this builder using the specified namespace, directives and attributes
+ * @param namespace the namespace of the requirement
+ * @param directives the directives of the requirement
+ * @param attributes the attributes of the requirement
+ */
+ public void addRequirement(String namespace, Map<String, String> directives, Map<String, Object> attributes) {
+ requirementInfos = addGenericInfo(requirementInfos, namespace, directives, attributes);
+ }
+
+ /**
+ * Returns the symbolic name for this builder.
+ * @return the symbolic name for this builder.
+ */
+ public String getSymbolicName() {
+ return symbolicName;
+ }
+
+ /**
+ * Returns the module version for this builder.
+ * @return the module version for this builder.
+ */
+ public Version getVersion() {
+ return version;
+ }
+
+ /**
+ * Returns the module type for this builder.
+ * @return the module type for this builder.
+ */
+ public int getTypes() {
+ return types;
+ }
+
+ /**
+ * Used by the container to build a new revision for a module.
+ * This builder is used to build a new {@link Module#getCurrentRevision() current}
+ * revision for the specified module.
+ * @param module the module to build a new revision for
+ * @return the new new {@link Module#getCurrentRevision() current} revision.
+ */
+ ModuleRevision addRevision(Module module) {
+ ModuleRevisions revisions = module.getRevisions();
+ ModuleRevision revision = new ModuleRevision(symbolicName, version, types, capabilityInfos, requirementInfos, revisions);
+ revisions.addRevision(revision);
+ return revision;
+ }
+
+ private static List<GenericInfo> addGenericInfo(List<GenericInfo> infos, String namespace, Map<String, String> directives, Map<String, Object> attributes) {
+ if (infos == null) {
+ infos = new ArrayList<GenericInfo>();
+ }
+ infos.add(new GenericInfo(namespace, directives, attributes));
+ return infos;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleRevisions.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleRevisions.java
new file mode 100644
index 000000000..06e4a3767
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleRevisions.java
@@ -0,0 +1,108 @@
+/*******************************************************************************
+ * Copyright (c) 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.container;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.wiring.BundleRevision;
+import org.osgi.framework.wiring.BundleRevisions;
+
+/**
+ * An implementation of {@link BundleRevisions} which represent a
+ * {@link Module} installed in a {@link ModuleContainer container}.
+ * The ModuleRevisions provides a bridge between the revisions, the
+ * module and the container they are associated with. The
+ * ModuleRevisions holds the information about the installation of
+ * a module in a container such as the module id and location.
+ */
+public class ModuleRevisions implements BundleRevisions {
+ private final Object monitor = new Object();
+ private final Module module;
+ private final ModuleContainer container;
+ /* @GuardedBy("monitor") */
+ private final List<ModuleRevision> revisions = new ArrayList<ModuleRevision>(1);
+ private volatile boolean uninstalled = false;
+
+ ModuleRevisions(Module module, ModuleContainer container) {
+ this.module = module;
+ this.container = container;
+ }
+
+ Module getModule() {
+ return module;
+ }
+
+ ModuleContainer getContainer() {
+ return container;
+ }
+
+ @Override
+ public Bundle getBundle() {
+ return module.getBundle();
+ }
+
+ @Override
+ public List<BundleRevision> getRevisions() {
+ synchronized (monitor) {
+ return new ArrayList<BundleRevision>(revisions);
+ }
+ }
+
+ List<ModuleRevision> getModuleRevisions() {
+ synchronized (monitor) {
+ return new ArrayList<ModuleRevision>(revisions);
+ }
+ }
+
+ /**
+ * Returns the current {@link ModuleRevision revision} associated with this revisions.
+ * @return the current {@link ModuleRevision revision} associated with this revisions
+ * or {@code null} if the current revision does not exist.
+ */
+ ModuleRevision getCurrentRevision() {
+ synchronized (monitor) {
+ if (revisions.isEmpty() || uninstalled) {
+ return null;
+ }
+ return revisions.get(0);
+ }
+ }
+
+ ModuleRevision addRevision(ModuleRevision revision) {
+ synchronized (monitor) {
+ revisions.add(0, revision);
+ }
+ return revision;
+ }
+
+ boolean removeRevision(ModuleRevision revision) {
+ try {
+ synchronized (monitor) {
+ return revisions.remove(revision);
+ }
+ } finally {
+ module.cleanup(revision);
+ }
+ }
+
+ boolean isUninstalled() {
+ return uninstalled;
+ }
+
+ void uninstall() {
+ uninstalled = true;
+ }
+
+ public String toString() {
+ return "moduleID=" + module.getId(); //$NON-NLS-1$
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleWire.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleWire.java
new file mode 100644
index 000000000..085f1dd9e
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleWire.java
@@ -0,0 +1,79 @@
+/*******************************************************************************
+ * Copyright (c) 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.container;
+
+import org.osgi.framework.wiring.BundleWire;
+
+/**
+ * An implementation of {@link BundleWire}.
+ */
+public class ModuleWire implements BundleWire {
+ private final ModuleCapability capability;
+ private final ModuleRevision hostingProvider;
+ private final ModuleRequirement requirement;
+ private final ModuleRevision hostingRequirer;
+ // indicates that the wire points to valid wirings
+ // technically this should be a separate flag for requirer vs provider but that seems like overkill
+ private volatile boolean isValid = true;
+
+ ModuleWire(ModuleCapability capability, ModuleRevision hostingProvider, ModuleRequirement requirement, ModuleRevision hostingRequirer) {
+ super();
+ this.capability = capability;
+ this.hostingProvider = hostingProvider;
+ this.requirement = requirement;
+ this.hostingRequirer = hostingRequirer;
+ }
+
+ @Override
+ public ModuleCapability getCapability() {
+ return capability;
+ }
+
+ @Override
+ public ModuleRequirement getRequirement() {
+ return requirement;
+ }
+
+ @Override
+ public ModuleWiring getProviderWiring() {
+ if (!isValid) {
+ return null;
+ }
+ return hostingProvider.getWiring();
+ }
+
+ @Override
+ public ModuleWiring getRequirerWiring() {
+ if (!isValid) {
+ return null;
+ }
+ return hostingRequirer.getWiring();
+ }
+
+ @Override
+ public ModuleRevision getProvider() {
+ return hostingProvider;
+ }
+
+ @Override
+ public ModuleRevision getRequirer() {
+ return hostingRequirer;
+ }
+
+ public String toString() {
+ return getRequirement() + " -> " + getCapability(); //$NON-NLS-1$
+ }
+
+ void invalidate() {
+ this.isValid = false;
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleWiring.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleWiring.java
new file mode 100644
index 000000000..883ac3856
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleWiring.java
@@ -0,0 +1,255 @@
+/*******************************************************************************
+ * Copyright (c) 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.container;
+
+import java.net.URL;
+import java.util.*;
+import org.eclipse.osgi.internal.container.Converters;
+import org.osgi.framework.AdminPermission;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.namespace.HostNamespace;
+import org.osgi.framework.namespace.PackageNamespace;
+import org.osgi.framework.wiring.*;
+import org.osgi.resource.*;
+
+/**
+ * An implementation of {@link BundleWiring}.
+ */
+public class ModuleWiring implements BundleWiring {
+ private static final RuntimePermission GET_CLASSLOADER_PERM = new RuntimePermission("getClassLoader"); //$NON-NLS-1$
+ private final ModuleRevision revision;
+ private final List<ModuleCapability> capabilities;
+ private final List<ModuleRequirement> requirements;
+ private final Collection<String> substitutedPkgNames;
+ private final Object monitor = new Object();
+ private ModuleClassLoader loader = null;
+ private volatile List<ModuleWire> providedWires;
+ private volatile List<ModuleWire> requiredWires;
+ private volatile boolean isValid = true;
+
+ ModuleWiring(ModuleRevision revision, List<ModuleCapability> capabilities, List<ModuleRequirement> requirements, List<ModuleWire> providedWires, List<ModuleWire> requiredWires, Collection<String> substitutedPkgNames) {
+ super();
+ this.revision = revision;
+ this.capabilities = capabilities;
+ this.requirements = requirements;
+ this.providedWires = providedWires;
+ this.requiredWires = requiredWires;
+ this.substitutedPkgNames = substitutedPkgNames;
+ }
+
+ @Override
+ public Bundle getBundle() {
+ return revision.getBundle();
+ }
+
+ @Override
+ public boolean isCurrent() {
+ return isValid && revision.isCurrent();
+ }
+
+ @Override
+ public boolean isInUse() {
+ return isCurrent() || !providedWires.isEmpty() || isFragmentInUse();
+ }
+
+ private boolean isFragmentInUse() {
+ // A fragment is considered in use if it has any required host wires
+ return ((BundleRevision.TYPE_FRAGMENT & revision.getTypes()) != 0) && !getRequiredWires(HostNamespace.HOST_NAMESPACE).isEmpty();
+ }
+
+ public List<ModuleCapability> getModuleCapabilities(String namespace) {
+ if (!isValid)
+ return null;
+ if (namespace == null)
+ return new ArrayList<ModuleCapability>(capabilities);
+ List<ModuleCapability> result = new ArrayList<ModuleCapability>();
+ for (ModuleCapability capability : capabilities) {
+ if (namespace.equals(capability.getNamespace())) {
+ result.add(capability);
+ }
+ }
+ return result;
+ }
+
+ public List<ModuleRequirement> getModuleRequirements(String namespace) {
+ if (!isValid)
+ return null;
+ if (namespace == null)
+ return new ArrayList<ModuleRequirement>(requirements);
+ List<ModuleRequirement> result = new ArrayList<ModuleRequirement>();
+ for (ModuleRequirement requirement : requirements) {
+ if (namespace.equals(requirement.getNamespace())) {
+ result.add(requirement);
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public List<BundleCapability> getCapabilities(String namespace) {
+ return Converters.asListBundleCapability(getModuleCapabilities(namespace));
+
+ }
+
+ @Override
+ public List<BundleRequirement> getRequirements(String namespace) {
+ return Converters.asListBundleRequirement(getModuleRequirements(namespace));
+ }
+
+ public List<ModuleWire> getProvidedModuleWires(String namespace) {
+ return getWires(namespace, providedWires);
+ }
+
+ public List<ModuleWire> getRequiredModuleWires(String namespace) {
+ return getWires(namespace, requiredWires);
+ }
+
+ @Override
+ public List<BundleWire> getProvidedWires(String namespace) {
+ return Converters.asListBundleWire(getWires(namespace, providedWires));
+ }
+
+ @Override
+ public List<BundleWire> getRequiredWires(String namespace) {
+ return Converters.asListBundleWire(getWires(namespace, requiredWires));
+ }
+
+ private List<ModuleWire> getWires(String namespace, List<ModuleWire> allWires) {
+ if (!isValid)
+ return null;
+ if (namespace == null)
+ return new ArrayList<ModuleWire>(allWires);
+ List<ModuleWire> result = new ArrayList<ModuleWire>();
+ for (ModuleWire moduleWire : allWires) {
+ if (namespace.equals(moduleWire.getCapability().getNamespace())) {
+ result.add(moduleWire);
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public ModuleRevision getRevision() {
+ return revision;
+ }
+
+ @Override
+ public ClassLoader getClassLoader() {
+ return (ClassLoader) getModuleClassLoader();
+ }
+
+ public ModuleClassLoader getModuleClassLoader() {
+ SecurityManager sm = System.getSecurityManager();
+ if (sm != null) {
+ sm.checkPermission(GET_CLASSLOADER_PERM);
+ }
+ synchronized (monitor) {
+ if (!isValid) {
+ return null;
+ }
+ if (loader == null) {
+ loader = revision.getRevisions().getContainer().adaptor.createClassLoader(this);
+ }
+ return loader;
+ }
+
+ }
+
+ @Override
+ public List<URL> findEntries(String path, String filePattern, int options) {
+ if (!hasResourcePermission())
+ return Collections.emptyList();
+ ModuleClassLoader current = getModuleClassLoader();
+ if (current == null) {
+ // must not be valid
+ return null;
+ }
+ return current.findEntries(path, filePattern, options);
+ }
+
+ @Override
+ public Collection<String> listResources(String path, String filePattern, int options) {
+ if (!hasResourcePermission())
+ return Collections.emptyList();
+ ModuleClassLoader current = getModuleClassLoader();
+ if (current == null) {
+ // must not be valid
+ return null;
+ }
+ return current.listResources(path, filePattern, options);
+ }
+
+ @Override
+ public List<Capability> getResourceCapabilities(String namespace) {
+ return Converters.asListCapability(getCapabilities(namespace));
+ }
+
+ @Override
+ public List<Requirement> getResourceRequirements(String namespace) {
+ return Converters.asListRequirement(getRequirements(namespace));
+ }
+
+ @Override
+ public List<Wire> getProvidedResourceWires(String namespace) {
+ return Converters.asListWire(getWires(namespace, providedWires));
+ }
+
+ @Override
+ public List<Wire> getRequiredResourceWires(String namespace) {
+ return Converters.asListWire(getWires(namespace, requiredWires));
+ }
+
+ @Override
+ public ModuleRevision getResource() {
+ return revision;
+ }
+
+ void setProvidedWires(List<ModuleWire> providedWires) {
+ this.providedWires = providedWires;
+ }
+
+ void setRequiredWires(List<ModuleWire> requiredWires) {
+ this.requiredWires = requiredWires;
+ }
+
+ void invalidate() {
+ synchronized (monitor) {
+ this.isValid = false;
+ if (loader != null) {
+ loader.close();
+ loader = null;
+ }
+ }
+ }
+
+ boolean isSubtituted(ModuleCapability capability) {
+ if (!PackageNamespace.PACKAGE_NAMESPACE.equals(capability.getNamespace())) {
+ return false;
+ }
+ return substitutedPkgNames.contains(capability.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE));
+ }
+
+ Collection<String> getSubstitutedNames() {
+ return substitutedPkgNames;
+ }
+
+ private boolean hasResourcePermission() {
+ SecurityManager sm = System.getSecurityManager();
+ if (sm != null) {
+ try {
+ sm.checkPermission(new AdminPermission(getBundle(), AdminPermission.RESOURCE));
+ } catch (SecurityException e) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/SystemModule.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/SystemModule.java
new file mode 100644
index 000000000..28a69b8c5
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/SystemModule.java
@@ -0,0 +1,113 @@
+/*******************************************************************************
+ * Copyright (c) 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.container;
+
+import java.util.Arrays;
+import java.util.EnumSet;
+import org.eclipse.osgi.container.ModuleContainer.ContainerStartLevel;
+import org.eclipse.osgi.container.ModuleContainerAdaptor.ContainerEvent;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.service.resolver.ResolutionException;
+
+public abstract class SystemModule extends Module {
+
+ public SystemModule(ModuleContainer container) {
+ super(new Long(0), Constants.SYSTEM_BUNDLE_LOCATION, container, EnumSet.of(Settings.AUTO_START, Settings.USE_ACTIVATION_POLICY), new Integer(0));
+ }
+
+ public void init() throws BundleException {
+ getRevisions().getContainer().open();
+ lockStateChange(Event.STARTED);
+ try {
+ checkValid();
+ if (ACTIVE_SET.contains(getState()))
+ return;
+ if (getState().equals(State.INSTALLED)) {
+ try {
+ getRevisions().getContainer().resolve(Arrays.asList((Module) this), true);
+ } catch (ResolutionException e) {
+ throw new BundleException("Could not resolve module.", BundleException.RESOLVE_ERROR, e);
+ }
+ }
+ if (getState().equals(State.INSTALLED)) {
+ throw new BundleException("Could not resolve module.", BundleException.RESOLVE_ERROR);
+ }
+ setState(State.STARTING);
+ publishEvent(Event.STARTING);
+ try {
+ initWorker();
+ } catch (Throwable t) {
+ setState(State.STOPPING);
+ publishEvent(Event.STOPPING);
+ setState(State.RESOLVED);
+ publishEvent(Event.STOPPED);
+ getRevisions().getContainer().close();
+ if (t instanceof BundleException) {
+ throw (BundleException) t;
+ }
+ throw new BundleException("Error initializing container.", BundleException.ACTIVATOR_ERROR, t);
+ }
+ } finally {
+ unlockStateChange(Event.STARTED);
+ }
+
+ }
+
+ /**
+ * @throws BundleException
+ */
+ public void initWorker() throws BundleException {
+ // Do nothing
+ }
+
+ @Override
+ public void start(StartOptions... options) throws BundleException {
+ // make sure to init if needed
+ init();
+ // Always transient
+ super.start(StartOptions.TRANSIENT, StartOptions.USE_ACTIVATION_POLICY);
+ getRevisions().getContainer().adaptor.publishContainerEvent(ContainerEvent.STARTED, this, null);
+ }
+
+ @Override
+ public void stop(StopOptions... options) throws BundleException {
+ // Always transient
+ super.stop(StopOptions.TRANSIENT);
+ ContainerEvent containerEvent;
+ if (holdsTransitionEventLock(Event.UPDATED)) {
+ containerEvent = ContainerEvent.STOPPED_UPDATE;
+ } else if (holdsTransitionEventLock(Event.UNRESOLVED)) {
+ containerEvent = ContainerEvent.STOPPED_REFRESH;
+ } else {
+ containerEvent = ContainerEvent.STOPPED;
+ }
+ getRevisions().getContainer().adaptor.publishContainerEvent(containerEvent, this, null);
+ getRevisions().getContainer().close();
+ }
+
+ @Override
+ protected void startWorker() throws BundleException {
+ super.startWorker();
+ if (getId() != 0)
+ return;
+ ((ContainerStartLevel) getRevisions().getContainer().getFrameworkStartLevel()).doContainerStartLevel(this, ContainerStartLevel.USE_BEGINNING_START_LEVEL);
+ }
+
+ @Override
+ protected void stopWorker() throws BundleException {
+ super.stopWorker();
+ if (getId() != 0)
+ return;
+ ((ContainerStartLevel) getRevisions().getContainer().getFrameworkStartLevel()).doContainerStartLevel(this, 0);
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/builders/OSGiManifestBuilderFactory.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/builders/OSGiManifestBuilderFactory.java
new file mode 100644
index 000000000..6e8355cc7
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/builders/OSGiManifestBuilderFactory.java
@@ -0,0 +1,557 @@
+/*******************************************************************************
+ * Copyright (c) 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.container.builders;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.*;
+import org.eclipse.osgi.container.ModuleRevisionBuilder;
+import org.eclipse.osgi.container.namespaces.EclipsePlatformNamespace;
+import org.eclipse.osgi.container.namespaces.EquinoxModuleDataNamespace;
+import org.eclipse.osgi.framework.internal.core.FilterImpl;
+import org.eclipse.osgi.framework.internal.core.Tokenizer;
+import org.eclipse.osgi.util.ManifestElement;
+import org.eclipse.osgi.util.NLS;
+import org.osgi.framework.*;
+import org.osgi.framework.namespace.*;
+import org.osgi.framework.wiring.BundleRevision;
+
+public class OSGiManifestBuilderFactory {
+ private static final String ATTR_TYPE_STRING = "string"; //$NON-NLS-1$
+ private static final String ATTR_TYPE_VERSION = "version"; //$NON-NLS-1$
+ private static final String ATTR_TYPE_URI = "uri"; //$NON-NLS-1$
+ private static final String ATTR_TYPE_LONG = "long"; //$NON-NLS-1$
+ private static final String ATTR_TYPE_DOUBLE = "double"; //$NON-NLS-1$
+ private static final String ATTR_TYPE_SET = "set"; //$NON-NLS-1$
+ private static final String ATTR_TYPE_LIST = "List"; //$NON-NLS-1$
+
+ public static ModuleRevisionBuilder createBuilder(Map<String, String> manifest) throws BundleException {
+ return createBuilder(manifest, null, null, null);
+ }
+
+ public static ModuleRevisionBuilder createBuilder(Map<String, String> manifest, String symbolicNameAlias, String extraExports, String extraCapabilities) throws BundleException {
+ ModuleRevisionBuilder builder = new ModuleRevisionBuilder();
+
+ int manifestVersion = getManifestVersion(manifest);
+
+ Object symbolicName = getSymbolicNameAndVersion(builder, manifest, symbolicNameAlias, manifestVersion);
+
+ Collection<Map<String, Object>> exportedPackages = new ArrayList<Map<String, Object>>();
+ getPackageExports(builder, ManifestElement.parseHeader(Constants.EXPORT_PACKAGE, manifest.get(Constants.EXPORT_PACKAGE)), symbolicName, exportedPackages);
+ if (extraExports != null) {
+ getPackageExports(builder, ManifestElement.parseHeader(Constants.EXPORT_PACKAGE, extraExports), symbolicName, exportedPackages);
+ }
+ getPackageImports(builder, manifest, exportedPackages, manifestVersion);
+
+ getRequireBundle(builder, ManifestElement.parseHeader(Constants.REQUIRE_BUNDLE, manifest.get(Constants.REQUIRE_BUNDLE)));
+
+ getProvideCapabilities(builder, ManifestElement.parseHeader(Constants.PROVIDE_CAPABILITY, manifest.get(Constants.PROVIDE_CAPABILITY)));
+ if (extraCapabilities != null) {
+ getProvideCapabilities(builder, ManifestElement.parseHeader(Constants.PROVIDE_CAPABILITY, extraCapabilities));
+ }
+ getRequireCapabilities(builder, ManifestElement.parseHeader(Constants.REQUIRE_CAPABILITY, manifest.get(Constants.REQUIRE_CAPABILITY)));
+
+ addRequireEclipsePlatform(builder, manifest);
+
+ getEquinoxDataCapability(builder, manifest);
+
+ getFragmentHost(builder, ManifestElement.parseHeader(Constants.FRAGMENT_HOST, manifest.get(Constants.FRAGMENT_HOST)));
+
+ convertBREEs(builder, manifest);
+ return builder;
+ }
+
+ private static int getManifestVersion(Map<String, String> manifest) {
+ String manifestVersionHeader = manifest.get(Constants.BUNDLE_MANIFESTVERSION);
+ return manifestVersionHeader == null ? 1 : Integer.parseInt(manifestVersionHeader);
+ }
+
+ private static Object getSymbolicNameAndVersion(ModuleRevisionBuilder builder, Map<String, String> manifest, String symbolicNameAlias, int manifestVersion) throws BundleException {
+ boolean isFragment = manifest.get(Constants.FRAGMENT_HOST) != null;
+ builder.setTypes(isFragment ? BundleRevision.TYPE_FRAGMENT : 0);
+ String version = manifest.get(Constants.BUNDLE_VERSION);
+ try {
+ builder.setVersion((version != null) ? Version.parseVersion(version) : Version.emptyVersion);
+ } catch (IllegalArgumentException ex) {
+ if (manifestVersion >= 2) {
+ String message = NLS.bind("Invalid Manifest header \"{0}\": {1}", Constants.BUNDLE_VERSION, version);
+ throw new BundleException(message, BundleException.MANIFEST_ERROR, ex);
+ }
+ // prior to R4 the Bundle-Version header was not interpreted by the Framework;
+ // must not fail for old R3 style bundles
+ }
+
+ Object symbolicName = null;
+ String symbolicNameHeader = manifest.get(Constants.BUNDLE_SYMBOLICNAME);
+ if (symbolicNameHeader != null) {
+ ManifestElement[] symbolicNameElements = ManifestElement.parseHeader(Constants.BUNDLE_SYMBOLICNAME, symbolicNameHeader);
+ if (symbolicNameElements.length > 0) {
+ ManifestElement bsnElement = symbolicNameElements[0];
+ builder.setSymbolicName(bsnElement.getValue());
+ if (symbolicNameAlias != null) {
+ List<String> result = new ArrayList<String>();
+ result.add(builder.getSymbolicName());
+ result.add(symbolicNameAlias);
+ symbolicName = result;
+ } else {
+ symbolicName = builder.getSymbolicName();
+ }
+ Map<String, String> directives = getDirectives(bsnElement);
+ directives.remove(BundleNamespace.CAPABILITY_USES_DIRECTIVE);
+ directives.remove(BundleNamespace.CAPABILITY_EFFECTIVE_DIRECTIVE);
+ Map<String, Object> attributes = getAttributes(bsnElement);
+ if (!isFragment) {
+ // create the bundle namespace
+ Map<String, Object> bundleAttributes = new HashMap<String, Object>(attributes);
+ bundleAttributes.put(BundleNamespace.BUNDLE_NAMESPACE, symbolicName);
+ bundleAttributes.put(BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE, builder.getVersion());
+ builder.addCapability(BundleNamespace.BUNDLE_NAMESPACE, directives, bundleAttributes);
+
+ // create the host namespace
+ Map<String, Object> hostAttributes = new HashMap<String, Object>(attributes);
+ hostAttributes.put(HostNamespace.HOST_NAMESPACE, symbolicName);
+ hostAttributes.put(HostNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE, builder.getVersion());
+ builder.addCapability(HostNamespace.HOST_NAMESPACE, directives, hostAttributes);
+ }
+ // every bundle that has a symbolic name gets an identity;
+ // never use the symbolic name alias for the identity namespace
+ Map<String, Object> identityAttributes = new HashMap<String, Object>(attributes);
+ identityAttributes.put(IdentityNamespace.IDENTITY_NAMESPACE, builder.getSymbolicName());
+ identityAttributes.put(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE, builder.getVersion());
+ identityAttributes.put(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE, isFragment ? IdentityNamespace.TYPE_FRAGMENT : IdentityNamespace.TYPE_BUNDLE);
+ builder.addCapability(IdentityNamespace.IDENTITY_NAMESPACE, directives, identityAttributes);
+ }
+ }
+
+ return symbolicName == null ? symbolicNameAlias : symbolicName;
+ }
+
+ private static void getPackageExports(ModuleRevisionBuilder builder, ManifestElement[] exportElements, Object symbolicName, Collection<Map<String, Object>> exportedPackages) throws BundleException {
+ if (exportElements == null)
+ return;
+ for (ManifestElement exportElement : exportElements) {
+ String[] packageNames = exportElement.getValueComponents();
+ Map<String, Object> attributes = getAttributes(exportElement);
+ Map<String, String> directives = getDirectives(exportElement);
+ directives.remove(PackageNamespace.CAPABILITY_EFFECTIVE_DIRECTIVE);
+ String versionAttr = (String) attributes.remove(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE);
+ @SuppressWarnings("deprecation")
+ String specVersionAttr = (String) attributes.remove(Constants.PACKAGE_SPECIFICATION_VERSION);
+ Version version = versionAttr == null ? (specVersionAttr == null ? Version.parseVersion(specVersionAttr) : Version.emptyVersion) : Version.parseVersion(versionAttr);
+ attributes.put(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE, version);
+ if (symbolicName != null) {
+ attributes.put(PackageNamespace.CAPABILITY_BUNDLE_SYMBOLICNAME_ATTRIBUTE, symbolicName);
+ }
+ attributes.put(PackageNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE, builder.getVersion());
+ for (String packageName : packageNames) {
+ Map<String, Object> packageAttrs = new HashMap<String, Object>(attributes);
+ packageAttrs.put(PackageNamespace.PACKAGE_NAMESPACE, packageName);
+ builder.addCapability(PackageNamespace.PACKAGE_NAMESPACE, directives, packageAttrs);
+ exportedPackages.add(packageAttrs);
+ }
+ }
+ }
+
+ private static void getPackageImports(ModuleRevisionBuilder builder, Map<String, String> manifest, Collection<Map<String, Object>> exportedPackages, int manifestVersion) throws BundleException {
+ Collection<String> importPackageNames = new ArrayList<String>();
+ ManifestElement[] importElements = ManifestElement.parseHeader(Constants.IMPORT_PACKAGE, manifest.get(Constants.IMPORT_PACKAGE));
+ ManifestElement[] dynamicImportElements = ManifestElement.parseHeader(Constants.DYNAMICIMPORT_PACKAGE, manifest.get(Constants.DYNAMICIMPORT_PACKAGE));
+ addPackageImports(builder, importElements, importPackageNames, false);
+ addPackageImports(builder, dynamicImportElements, importPackageNames, true);
+ if (manifestVersion < 2)
+ addImplicitImports(builder, exportedPackages, importPackageNames);
+ }
+
+ private static void addPackageImports(ModuleRevisionBuilder builder, ManifestElement[] importElements, Collection<String> importPackageNames, boolean dynamic) {
+ if (importElements == null)
+ return;
+ for (ManifestElement importElement : importElements) {
+ String[] packageNames = importElement.getValueComponents();
+ Map<String, Object> attributes = getAttributes(importElement);
+ Map<String, String> directives = getDirectives(importElement);
+ directives.remove(PackageNamespace.REQUIREMENT_EFFECTIVE_DIRECTIVE);
+ directives.remove(PackageNamespace.REQUIREMENT_CARDINALITY_DIRECTIVE);
+ if (dynamic) {
+ directives.put(PackageNamespace.REQUIREMENT_RESOLUTION_DIRECTIVE, PackageNamespace.RESOLUTION_DYNAMIC);
+ }
+ String versionRangeAttr = (String) attributes.remove(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE);
+ @SuppressWarnings("deprecation")
+ String specVersionRangeAttr = (String) attributes.remove(Constants.PACKAGE_SPECIFICATION_VERSION);
+ VersionRange versionRange = versionRangeAttr == null ? (specVersionRangeAttr == null ? null : new VersionRange(specVersionRangeAttr)) : new VersionRange(versionRangeAttr);
+ String bundleVersionRangeAttr = (String) attributes.remove(PackageNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE);
+ VersionRange bundleVersionRange = bundleVersionRangeAttr == null ? null : new VersionRange(bundleVersionRangeAttr);
+ for (String packageName : packageNames) {
+ if (dynamic && importPackageNames.contains(packageName))
+ continue; // already importing this package, don't add a dynamic import for it
+ importPackageNames.add(packageName);
+
+ // fill in the filter directive based on the attributes
+ Map<String, String> packageDirectives = new HashMap<String, String>(directives);
+ StringBuilder filter = new StringBuilder();
+ filter.append('(').append(PackageNamespace.PACKAGE_NAMESPACE).append('=').append(packageName).append(')');
+ int size = filter.length();
+ for (Map.Entry<String, Object> attribute : attributes.entrySet())
+ filter.append('(').append(attribute.getKey()).append('=').append(attribute.getValue()).append(')');
+ if (versionRange != null)
+ filter.append(versionRange.toFilterString(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE));
+ if (bundleVersionRange != null)
+ filter.append(bundleVersionRange.toFilterString(PackageNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE));
+ if (size != filter.length())
+ // need to add (&...)
+ filter.insert(0, "(&").append(')'); //$NON-NLS-1$
+ packageDirectives.put(PackageNamespace.REQUIREMENT_FILTER_DIRECTIVE, filter.toString());
+
+ // fill in cardinality for dynamic wild cards
+ if (dynamic && packageName.indexOf('*') >= 0)
+ packageDirectives.put(PackageNamespace.REQUIREMENT_CARDINALITY_DIRECTIVE, PackageNamespace.CARDINALITY_MULTIPLE);
+
+ builder.addRequirement(PackageNamespace.PACKAGE_NAMESPACE, packageDirectives, new HashMap<String, Object>(0));
+ }
+ }
+ }
+
+ private static void addImplicitImports(ModuleRevisionBuilder builder, Collection<Map<String, Object>> exportedPackages, Collection<String> importPackageNames) {
+ for (Map<String, Object> exportAttributes : exportedPackages) {
+ String packageName = (String) exportAttributes.get(PackageNamespace.PACKAGE_NAMESPACE);
+ if (importPackageNames.contains(packageName))
+ continue;
+ importPackageNames.add(packageName);
+ Version packageVersion = (Version) exportAttributes.get(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE);
+ StringBuilder filter = new StringBuilder();
+ filter.append("(&(").append(PackageNamespace.PACKAGE_NAMESPACE).append('=').append(packageName).append(')'); //$NON-NLS-1$
+ filter.append('(').append(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE).append(">=").append(packageVersion).append("))"); //$NON-NLS-1$//$NON-NLS-2$
+ Map<String, String> directives = new HashMap<String, String>(1);
+ directives.put(PackageNamespace.REQUIREMENT_FILTER_DIRECTIVE, filter.toString());
+ builder.addRequirement(PackageNamespace.PACKAGE_NAMESPACE, directives, new HashMap<String, Object>(0));
+ }
+ }
+
+ private static Map<String, String> getDirectives(ManifestElement element) {
+ Map<String, String> directives = new HashMap<String, String>();
+ Enumeration<String> keys = element.getDirectiveKeys();
+ if (keys == null)
+ return directives;
+ while (keys.hasMoreElements()) {
+ String key = keys.nextElement();
+ directives.put(key, element.getDirective(key));
+ }
+ return directives;
+ }
+
+ private static void getRequireBundle(ModuleRevisionBuilder builder, ManifestElement[] requireBundles) throws BundleException {
+ if (requireBundles == null)
+ return;
+ for (ManifestElement requireElement : requireBundles) {
+ String[] bundleNames = requireElement.getValueComponents();
+ Map<String, Object> attributes = getAttributes(requireElement);
+ Map<String, String> directives = getDirectives(requireElement);
+ directives.remove(BundleNamespace.REQUIREMENT_CARDINALITY_DIRECTIVE);
+ directives.remove(BundleNamespace.REQUIREMENT_EFFECTIVE_DIRECTIVE);
+ String versionRangeAttr = (String) attributes.remove(BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE);
+ VersionRange versionRange = versionRangeAttr == null ? null : new VersionRange(versionRangeAttr);
+ for (String bundleName : bundleNames) {
+ // fill in the filter directive based on the attributes
+ Map<String, String> bundleDirectives = new HashMap<String, String>(directives);
+ StringBuilder filter = new StringBuilder();
+ filter.append('(').append(BundleNamespace.BUNDLE_NAMESPACE).append('=').append(bundleName).append(')');
+ int size = filter.length();
+ for (Map.Entry<String, Object> attribute : attributes.entrySet())
+ filter.append('(').append(attribute.getKey()).append('=').append(attribute.getValue()).append(')');
+ if (versionRange != null)
+ filter.append(versionRange.toFilterString(BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE));
+ if (size != filter.length())
+ // need to add (&...)
+ filter.insert(0, "(&").append(')'); //$NON-NLS-1$
+ bundleDirectives.put(BundleNamespace.REQUIREMENT_FILTER_DIRECTIVE, filter.toString());
+ builder.addRequirement(BundleNamespace.BUNDLE_NAMESPACE, bundleDirectives, new HashMap<String, Object>(0));
+ }
+ }
+ }
+
+ private static void getFragmentHost(ModuleRevisionBuilder builder, ManifestElement[] fragmentHosts) throws BundleException {
+ if (fragmentHosts == null || fragmentHosts.length == 0)
+ return;
+
+ ManifestElement fragmentHost = fragmentHosts[0];
+ String hostName = fragmentHost.getValue();
+ Map<String, Object> attributes = getAttributes(fragmentHost);
+ Map<String, String> directives = getDirectives(fragmentHost);
+ directives.remove(HostNamespace.REQUIREMENT_CARDINALITY_DIRECTIVE);
+ directives.remove(HostNamespace.REQUIREMENT_EFFECTIVE_DIRECTIVE);
+
+ String versionRangeAttr = (String) attributes.remove(HostNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE);
+ VersionRange versionRange = versionRangeAttr == null ? null : new VersionRange(versionRangeAttr);
+
+ // fill in the filter directive based on the attributes
+ StringBuilder filter = new StringBuilder();
+ filter.append('(').append(HostNamespace.HOST_NAMESPACE).append('=').append(hostName).append(')');
+ int size = filter.length();
+ for (Map.Entry<String, Object> attribute : attributes.entrySet())
+ filter.append('(').append(attribute.getKey()).append('=').append(attribute.getValue()).append(')');
+ if (versionRange != null)
+ filter.append(versionRange.toFilterString(HostNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE));
+ if (size != filter.length())
+ // need to add (&...)
+ filter.insert(0, "(&").append(')'); //$NON-NLS-1$
+ directives.put(BundleNamespace.REQUIREMENT_FILTER_DIRECTIVE, filter.toString());
+ builder.addRequirement(HostNamespace.HOST_NAMESPACE, directives, new HashMap<String, Object>(0));
+ }
+
+ private static void getProvideCapabilities(ModuleRevisionBuilder builder, ManifestElement[] provideElements) throws BundleException {
+ if (provideElements == null)
+ return;
+ for (ManifestElement provideElement : provideElements) {
+ String[] namespaces = provideElement.getValueComponents();
+ Map<String, Object> attributes = getAttributes(provideElement);
+ Map<String, String> directives = getDirectives(provideElement);
+ for (String namespace : namespaces) {
+ builder.addCapability(namespace, directives, attributes);
+ }
+ }
+ }
+
+ private static void getRequireCapabilities(ModuleRevisionBuilder builder, ManifestElement[] requireElements) throws BundleException {
+ if (requireElements == null)
+ return;
+ for (ManifestElement requireElement : requireElements) {
+ String[] namespaces = requireElement.getValueComponents();
+ Map<String, Object> attributes = getAttributes(requireElement);
+ Map<String, String> directives = getDirectives(requireElement);
+ for (String namespace : namespaces) {
+ if (IdentityNamespace.IDENTITY_NAMESPACE.equals(namespace))
+ throw new BundleException("A bundle is not allowed to define a capability in the " + IdentityNamespace.IDENTITY_NAMESPACE + " name space."); //$NON-NLS-1$ //$NON-NLS-2$
+ builder.addRequirement(namespace, directives, attributes);
+ }
+ }
+ }
+
+ private static void addRequireEclipsePlatform(ModuleRevisionBuilder builder, Map<String, String> manifest) {
+ String platformFilter = manifest.get(EclipsePlatformNamespace.ECLIPSE_PLATFORM_FILTER_HEADER);
+ if (platformFilter == null) {
+ return;
+ }
+ // only support one
+ HashMap<String, String> directives = new HashMap<String, String>();
+ directives.put(EclipsePlatformNamespace.REQUIREMENT_FILTER_DIRECTIVE, platformFilter);
+ builder.addRequirement(EclipsePlatformNamespace.ECLIPSE_PLATFORM_NAMESPACE, directives, Collections.<String, Object> emptyMap());
+ }
+
+ private static void getEquinoxDataCapability(ModuleRevisionBuilder builder, Map<String, String> manifest) throws BundleException {
+ Map<String, Object> attributes = new HashMap<String, Object>();
+
+ // Get the activation policy attributes
+ ManifestElement[] policyElements = ManifestElement.parseHeader(Constants.BUNDLE_ACTIVATIONPOLICY, manifest.get(Constants.BUNDLE_ACTIVATIONPOLICY));
+ if (policyElements != null) {
+ ManifestElement policy = policyElements[0];
+ String policyName = policy.getValue();
+ if (EquinoxModuleDataNamespace.CAPABILITY_ACTIVATION_POLICY_LAZY.equals(policyName)) {
+ attributes.put(EquinoxModuleDataNamespace.CAPABILITY_ACTIVATION_POLICY, policyName);
+ String includeSpec = policy.getDirective(Constants.INCLUDE_DIRECTIVE);
+ if (includeSpec != null) {
+ attributes.put(EquinoxModuleDataNamespace.CAPABILITY_LAZY_INCLUDE_ATTRIBUTE, convertValue("List<String>", includeSpec));
+ }
+ String excludeSpec = policy.getDirective(Constants.EXCLUDE_DIRECTIVE);
+ if (excludeSpec != null) {
+ attributes.put(EquinoxModuleDataNamespace.CAPABILITY_LAZY_EXCLUDE_ATTRIBUTE, convertValue("List<String>", excludeSpec));
+ }
+ }
+ }
+
+ // Get the activator
+ String activator = manifest.get(Constants.BUNDLE_ACTIVATOR);
+ if (activator != null) {
+ attributes.put(EquinoxModuleDataNamespace.CAPABILITY_ACTIVATOR, activator);
+ }
+
+ // Get the class path
+ ManifestElement[] classpathElements = ManifestElement.parseHeader(Constants.BUNDLE_CLASSPATH, manifest.get(Constants.BUNDLE_CLASSPATH));
+ if (classpathElements != null) {
+ List<String> classpath = new ArrayList<String>();
+ for (ManifestElement element : classpathElements) {
+ String[] components = element.getValueComponents();
+ for (String component : components) {
+ classpath.add(component);
+ }
+ }
+ attributes.put(EquinoxModuleDataNamespace.CAPABILITY_CLASSPATH, classpath);
+ }
+
+ // only create the capability if the attributes is not empty
+ if (!attributes.isEmpty()) {
+ builder.addCapability(EquinoxModuleDataNamespace.MODULE_DATA_NAMESPACE, Collections.<String, String> emptyMap(), attributes);
+ }
+ }
+
+ private static Map<String, Object> getAttributes(ManifestElement element) {
+ Enumeration<String> keys = element.getKeys();
+ Map<String, Object> attributes = new HashMap<String, Object>();
+ if (keys == null)
+ return attributes;
+ while (keys.hasMoreElements()) {
+ String key = keys.nextElement();
+ String value = element.getAttribute(key);
+ int colonIndex = key.indexOf(':');
+ String type = ATTR_TYPE_STRING;
+ if (colonIndex > 0) {
+ type = key.substring(colonIndex + 1).trim();
+ key = key.substring(0, colonIndex).trim();
+ }
+ attributes.put(key, convertValue(type, value));
+ }
+ return attributes;
+ }
+
+ private static Object convertValue(String type, String value) {
+
+ if (ATTR_TYPE_STRING.equalsIgnoreCase(type))
+ return value;
+
+ String trimmed = value.trim();
+ if (ATTR_TYPE_DOUBLE.equalsIgnoreCase(type))
+ return new Double(trimmed);
+ else if (ATTR_TYPE_LONG.equalsIgnoreCase(type))
+ return new Long(trimmed);
+ else if (ATTR_TYPE_URI.equalsIgnoreCase(type))
+ try {
+ return new URI(trimmed);
+ } catch (URISyntaxException e) {
+ throw new RuntimeException(e);
+ }
+ else if (ATTR_TYPE_VERSION.equalsIgnoreCase(type))
+ return new Version(trimmed);
+ else if (ATTR_TYPE_SET.equalsIgnoreCase(type))
+ return ManifestElement.getArrayFromList(trimmed, ","); //$NON-NLS-1$
+
+ // assume list type, anything else will throw an exception
+ Tokenizer listTokenizer = new Tokenizer(type);
+ String listType = listTokenizer.getToken("<"); //$NON-NLS-1$
+ if (!ATTR_TYPE_LIST.equalsIgnoreCase(listType))
+ throw new RuntimeException("Unsupported type: " + type); //$NON-NLS-1$
+ char c = listTokenizer.getChar();
+ String componentType = ATTR_TYPE_STRING;
+ if (c == '<') {
+ componentType = listTokenizer.getToken(">"); //$NON-NLS-1$
+ if (listTokenizer.getChar() != '>')
+ throw new RuntimeException("Invalid type, missing ending '>' : " + type); //$NON-NLS-1$
+ }
+ List<String> tokens = new Tokenizer(value).getEscapedTokens(","); //$NON-NLS-1$
+ List<Object> components = new ArrayList<Object>();
+ for (String component : tokens) {
+ components.add(convertValue(componentType, component));
+ }
+ return components;
+ }
+
+ private static void convertBREEs(ModuleRevisionBuilder builder, Map<String, String> manifest) throws BundleException {
+ @SuppressWarnings("deprecation")
+ String[] brees = ManifestElement.getArrayFromList(manifest.get(Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT));
+ if (brees == null || brees.length == 0)
+ return;
+ List<String> breeFilters = new ArrayList<String>();
+ for (String bree : brees)
+ breeFilters.add(createOSGiEERequirementFilter(bree));
+ String filterSpec;
+ if (breeFilters.size() == 1) {
+ filterSpec = breeFilters.get(0);
+ } else {
+ StringBuffer filterBuf = new StringBuffer("(|"); //$NON-NLS-1$
+ for (String breeFilter : breeFilters) {
+ filterBuf.append(breeFilter);
+ }
+ filterSpec = filterBuf.append(")").toString(); //$NON-NLS-1$
+ }
+
+ Map<String, String> directives = new HashMap<String, String>(1);
+ directives.put(ExecutionEnvironmentNamespace.REQUIREMENT_FILTER_DIRECTIVE, filterSpec);
+ builder.addRequirement(ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE, directives, new HashMap<String, Object>(0));
+ }
+
+ private static String createOSGiEERequirementFilter(String bree) throws BundleException {
+ String[] nameVersion = getOSGiEENameVersion(bree);
+ String eeName = nameVersion[0];
+ String v = nameVersion[1];
+ String filterSpec;
+ if (v == null)
+ filterSpec = "(osgi.ee=" + eeName + ")"; //$NON-NLS-1$ //$NON-NLS-2$
+ else
+ filterSpec = "(&(osgi.ee=" + eeName + ")(version=" + v + "))"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ try {
+ // do a sanity check
+ FilterImpl.newInstance(filterSpec);
+ } catch (InvalidSyntaxException e) {
+ filterSpec = "(osgi.ee=" + bree + ")"; //$NON-NLS-1$ //$NON-NLS-2$
+ try {
+ // do another sanity check
+ FilterImpl.newInstance(filterSpec);
+ } catch (InvalidSyntaxException e1) {
+ throw new BundleException("Error converting required execution environment.", e1); //$NON-NLS-1$
+ }
+ }
+ return filterSpec;
+ }
+
+ private static String[] getOSGiEENameVersion(String bree) {
+ String ee1 = null;
+ String ee2 = null;
+ String v1 = null;
+ String v2 = null;
+ int separator = bree.indexOf('/');
+ if (separator <= 0 || separator == bree.length() - 1) {
+ ee1 = bree;
+ } else {
+ ee1 = bree.substring(0, separator);
+ ee2 = bree.substring(separator + 1);
+ }
+ int v1idx = ee1.indexOf('-');
+ if (v1idx > 0 && v1idx < ee1.length() - 1) {
+ // check for > 0 to avoid EEs starting with -
+ // check for < len - 1 to avoid ending with -
+ try {
+ v1 = ee1.substring(v1idx + 1);
+ // sanity check version format
+ Version.parseVersion(v1);
+ ee1 = ee1.substring(0, v1idx);
+ } catch (IllegalArgumentException e) {
+ v1 = null;
+ }
+ }
+
+ int v2idx = ee2 == null ? -1 : ee2.indexOf('-');
+ if (v2idx > 0 && v2idx < ee2.length() - 1) {
+ // check for > 0 to avoid EEs starting with -
+ // check for < len - 1 to avoid ending with -
+ try {
+ v2 = ee2.substring(v2idx + 1);
+ Version.parseVersion(v2);
+ ee2 = ee2.substring(0, v2idx);
+ } catch (IllegalArgumentException e) {
+ v2 = null;
+ }
+ }
+
+ if (v1 == null)
+ v1 = v2;
+ if (v1 != null && v2 != null && !v1.equals(v2)) {
+ ee1 = bree;
+ ee2 = null;
+ v1 = null;
+ v2 = null;
+ }
+ if ("J2SE".equals(ee1)) //$NON-NLS-1$
+ ee1 = "JavaSE"; //$NON-NLS-1$
+ if ("J2SE".equals(ee2)) //$NON-NLS-1$
+ ee2 = "JavaSE"; //$NON-NLS-1$
+
+ String eeName = ee1 + (ee2 == null ? "" : '/' + ee2); //$NON-NLS-1$
+
+ return new String[] {eeName, v1};
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/namespaces/EclipsePlatformNamespace.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/namespaces/EclipsePlatformNamespace.java
new file mode 100644
index 000000000..b89822495
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/namespaces/EclipsePlatformNamespace.java
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ * Copyright (c) 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.container.namespaces;
+
+import org.osgi.resource.Namespace;
+
+/**
+ * Eclipse Platform and Requirement Namespace.
+ *
+ * <p>
+ * This class defines the names for the attributes and directives for this
+ * namespace. All unspecified capability attributes are of type {@code String}
+ * and are used as arbitrary matching attributes for the capability. The values
+ * associated with the specified directive and attribute keys are of type
+ * {@code String}, unless otherwise indicated.
+ *
+ * @Immutable
+ */
+public class EclipsePlatformNamespace extends Namespace {
+
+ /**
+ * Namespace name for the eclipse platform. Unlike typical name spaces
+ * this namespace is not intended to be used as an attribute.
+ */
+ public static final String ECLIPSE_PLATFORM_NAMESPACE = "eclipse.platform";
+
+ /**
+ * Manifest header identifying the eclipse platform for the
+ * bundle. The framework may run this bundle if filter
+ * specified by this header matches the running eclipse platform.
+ */
+ public static final String ECLIPSE_PLATFORM_FILTER_HEADER = "Eclipse-PlatformFilter";
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/namespaces/EquinoxModuleDataNamespace.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/namespaces/EquinoxModuleDataNamespace.java
new file mode 100644
index 000000000..3b2ab29c5
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/namespaces/EquinoxModuleDataNamespace.java
@@ -0,0 +1,106 @@
+/*******************************************************************************
+ * Copyright (c) 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.container.namespaces;
+
+import org.osgi.framework.Constants;
+import org.osgi.resource.Namespace;
+
+/**
+ * Equinox module data capability namespace. This namespace is used
+ * to store immutable data about a module revision. This includes the following
+ * <ul>
+ * <li>The activation policy as specified by the {@link Constants#BUNDLE_ACTIVATIONPOLICY}
+ * Bundle-ActivationPolicy} header.</li>
+ * <li>The activator as specified by the {@link Constants#BUNDLE_ACTIVATOR Bundle-Activator}
+ * header.</li>
+ * <li>The class path as specified by the {@link Constants#BUNDLE_CLASSPATH Bundle-ClassPath}
+ * header.</li>
+ * </ul>
+ * Activation
+ * policy capability is provided for informational purposes and
+ * should not be considered as effective by the resolver.
+ * <p>
+ * For bundles, the attributes of this capability are extracted
+ * from the Bundle-ActivationPolicy header. The directives
+ * {@link Constants#EXCLUDE_DIRECTIVE} and {@link Constants#INCLUDE_DIRECTIVE}
+ * are converted to attributes of type {@code List<String>}.
+ *
+ * <p>
+ * This class defines the names for the attributes and directives for this
+ * namespace. Capabilities in this namespace are not intended to be used to
+ * match requirements and should not be considered as effective by a resolver.
+ *
+ * @Immutable
+ */
+public class EquinoxModuleDataNamespace extends Namespace {
+
+ /**
+ * Namespace name for equinox module data. Unlike typical name spaces
+ * this namespace is not intended to be used as an attribute.
+ */
+ public static final String MODULE_DATA_NAMESPACE = "equinox.module.data";
+
+ /**
+ * The directive value identifying a {@link #CAPABILITY_EFFECTIVE_DIRECTIVE
+ * capability} that is effective for information purposes. Capabilities
+ * in this namespace must have an effective directive value of information.
+ *
+ * @see #CAPABILITY_EFFECTIVE_DIRECTIVE
+ */
+ public final static String EFFECTIVE_INFORMATION = "information";
+
+ /**
+ * The capability attribute contains the {@link Constants#BUNDLE_ACTIVATIONPOLICY
+ * activation policy} for the providing module revision. The value of this
+ * attribute must be of type {@code String}. When not specified then
+ * the module revision uses an eager activation policy.
+ */
+ public final static String CAPABILITY_ACTIVATION_POLICY = "activation.policy";
+
+ /**
+ * An activation policy name indicating the lazy activation policy
+ * is used.
+ */
+ public final static String CAPABILITY_ACTIVATION_POLICY_LAZY = "lazy";
+
+ /**
+ * When the {@link #CAPABILITY_ACTIVATION_POLICY_LAZY lazy} policy is used this
+ * attribute contains the package names that must
+ * trigger the activation when a class is loaded of these packages.
+ * If the attribute is not defined then the default is all package names.
+ * The value of this attribute must be of type {@code List<String>}.
+ */
+ public final static String CAPABILITY_LAZY_INCLUDE_ATTRIBUTE = "lazy.include";
+
+ /**
+ * When the {@link #CAPABILITY_ACTIVATION_POLICY_LAZY lazy} policy is used this
+ * attribute contains the package names that must not
+ * trigger the activation when a class is loaded of these packages.
+ * If the attribute is not defined then the default is no package names.
+ * The value of this attribute must be of type {@code List<String>}.
+ */
+ public final static String CAPABILITY_LAZY_EXCLUDE_ATTRIBUTE = "lazy.include";
+
+ /**
+ * The capability attribute contains the {@link Constants#BUNDLE_ACTIVATOR activator}
+ * for the providing module revision. The value of this attribute must be of type
+ * {@code String}. When not specified then the module revision has no activator.
+ */
+ public final static String CAPABILITY_ACTIVATOR = "activator";
+
+ /**
+ * The capability attribute contains the {@link Constants#BUNDLE_CLASSPATH class path}
+ * for the providing module revision. The value of this attribute must be of type
+ * {@code List<String>}. When not specified the module revision uses the default
+ * class path of '.'.
+ */
+ public final static String CAPABILITY_CLASSPATH = "classpath";
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/container/Capabilities.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/container/Capabilities.java
new file mode 100644
index 000000000..0fb7cf970
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/container/Capabilities.java
@@ -0,0 +1,236 @@
+/*******************************************************************************
+ * Copyright (c) 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.internal.container;
+
+import java.util.*;
+import org.eclipse.osgi.container.*;
+import org.eclipse.osgi.framework.internal.core.FilterImpl;
+import org.eclipse.osgi.util.ManifestElement;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.namespace.*;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Namespace;
+
+public class Capabilities {
+
+ static class NamespaceSet {
+ private final String name;
+ private final Map<String, Set<ModuleCapability>> indexes = new HashMap<String, Set<ModuleCapability>>();
+ private final Set<ModuleCapability> all = new HashSet<ModuleCapability>();
+ private final Set<ModuleCapability> nonStringIndexes = new HashSet<ModuleCapability>(0);
+ private final boolean matchMandatory;
+
+ NamespaceSet(String name) {
+ this.name = name;
+ this.matchMandatory = PackageNamespace.PACKAGE_NAMESPACE.equals(name) || BundleNamespace.BUNDLE_NAMESPACE.equals(name) || HostNamespace.HOST_NAMESPACE.equals(name);
+ }
+
+ void addCapability(ModuleCapability capability) {
+ if (!name.equals(capability.getNamespace())) {
+ throw new IllegalArgumentException("Invalid namespace: " + capability.getNamespace() + ": expecting: " + name);
+ }
+ all.add(capability);
+ // by convention we index by the namespace attribute
+ Object index = capability.getAttributes().get(name);
+ if (index == null) {
+ return;
+ }
+ Collection<?> indexCollection = null;
+ if (index instanceof Collection) {
+ indexCollection = (Collection<?>) index;
+ } else if (index.getClass().isArray()) {
+ indexCollection = Arrays.asList((Object[]) index);
+ }
+ if (indexCollection == null) {
+ addIndex(index, capability);
+ } else {
+ for (Object indexKey : indexCollection) {
+ addIndex(indexKey, capability);
+ }
+ }
+ }
+
+ private void addIndex(Object indexKey, ModuleCapability capability) {
+ if (!(indexKey instanceof String)) {
+ nonStringIndexes.add(capability);
+ } else {
+ Set<ModuleCapability> capabilities = indexes.get(indexKey);
+ if (capabilities == null) {
+ capabilities = new HashSet<ModuleCapability>(1);
+ indexes.put((String) indexKey, capabilities);
+ }
+ capabilities.add(capability);
+ }
+ }
+
+ void removeCapability(ModuleCapability capability) {
+ if (!name.equals(capability.getNamespace())) {
+ throw new IllegalArgumentException("Invalid namespace: " + capability.getNamespace() + ": expecting: " + name);
+ }
+ all.remove(capability);
+ // by convention we index by the namespace attribute
+ Object index = capability.getAttributes().get(name);
+ if (index == null) {
+ return;
+ }
+ Collection<?> indexCollection = null;
+ if (index instanceof Collection) {
+ indexCollection = (Collection<?>) index;
+ } else if (index.getClass().isArray()) {
+ indexCollection = Arrays.asList((Object[]) index);
+ }
+ if (indexCollection == null) {
+ removeIndex(index, capability);
+ } else {
+ for (Object indexKey : indexCollection) {
+ removeIndex(indexKey, capability);
+ }
+ }
+ }
+
+ private void removeIndex(Object indexKey, ModuleCapability capability) {
+ if (!(indexKey instanceof String)) {
+ nonStringIndexes.remove(capability);
+ } else {
+ Set<ModuleCapability> capabilities = indexes.get(indexKey);
+ if (capabilities != null) {
+ capabilities.remove(capability);
+ }
+ }
+ }
+
+ List<ModuleCapability> findCapabilities(ModuleRequirement requirement) {
+ if (!name.equals(requirement.getNamespace())) {
+ throw new IllegalArgumentException("Invalid namespace: " + requirement.getNamespace() + ": expecting: " + name);
+ }
+ FilterImpl f = null;
+ String filterSpec = requirement.getDirectives().get(Namespace.REQUIREMENT_FILTER_DIRECTIVE);
+ if (filterSpec != null) {
+ try {
+ f = FilterImpl.newInstance(filterSpec);
+ } catch (InvalidSyntaxException e) {
+ return Collections.emptyList();
+ }
+ }
+
+ if (filterSpec == null) {
+ return match(null, all);
+ }
+
+ String indexKey = f.getPrimaryKeyValue(name);
+ if (indexKey == null) {
+ return match(f, all);
+ }
+
+ List<ModuleCapability> result;
+ Set<ModuleCapability> indexed = indexes.get(indexKey);
+ if (indexed == null) {
+ result = new ArrayList<ModuleCapability>(0);
+ } else {
+ result = match(f, indexed);
+ }
+
+ if (!nonStringIndexes.isEmpty()) {
+ List<ModuleCapability> nonStringResult = match(f, nonStringIndexes);
+ for (ModuleCapability capability : nonStringResult) {
+ if (!result.contains(capability)) {
+ result.add(capability);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ private List<ModuleCapability> match(FilterImpl f, Set<ModuleCapability> candidates) {
+ List<ModuleCapability> result = new ArrayList<ModuleCapability>(1);
+ for (ModuleCapability candidate : candidates) {
+ if (matches(f, candidate, matchMandatory)) {
+ result.add(candidate);
+ }
+ }
+ return result;
+ }
+ }
+
+ public static boolean matches(FilterImpl f, Capability candidate, boolean matchMandatory) {
+ if (f != null && !f.matches(candidate.getAttributes())) {
+ return false;
+ }
+ if (matchMandatory) {
+ // check for mandatory directive
+ String mandatory = candidate.getDirectives().get(AbstractWiringNamespace.CAPABILITY_MANDATORY_DIRECTIVE);
+ if (mandatory == null) {
+ return true;
+ }
+ if (f == null) {
+ return false;
+ }
+ String[] mandatoryAttrs = ManifestElement.getArrayFromList(mandatory, ","); //$NON-NLS-1$
+ boolean allPresent = true;
+ for (String attribute : mandatoryAttrs) {
+ allPresent &= f.getPrimaryKeyValue(attribute) != null;
+ }
+ return allPresent;
+ }
+ return true;
+ }
+
+ Map<String, NamespaceSet> namespaceSets = new HashMap<String, NamespaceSet>();
+
+ /**
+ * Adds the {@link ModuleRevision#getModuleCapabilities(String) capabilities}
+ * provided by the specified revision to this database. These capabilities must
+ * become available for lookup with the {@link #findCapabilities(ModuleRequirement)}
+ * method.
+ * @param revision the revision which has capabilities to add
+ */
+ public void addCapabilities(ModuleRevision revision) {
+ for (ModuleCapability capability : revision.getModuleCapabilities(null)) {
+ NamespaceSet namespaceSet = namespaceSets.get(capability.getNamespace());
+ if (namespaceSet == null) {
+ namespaceSet = new NamespaceSet(capability.getNamespace());
+ namespaceSets.put(capability.getNamespace(), namespaceSet);
+ }
+ namespaceSet.addCapability(capability);
+ }
+ }
+
+ /**
+ * Removes the {@link ModuleRevision#getModuleCapabilities(String) capabilities}
+ * provided by the specified revision from this database. These capabilities
+ * must no longer be available for lookup with the
+ * {@link #findCapabilities(ModuleRequirement)} method.
+ * @param revision
+ */
+ public void removeCapabilities(ModuleRevision revision) {
+ for (ModuleCapability capability : revision.getModuleCapabilities(null)) {
+ NamespaceSet namespaceSet = namespaceSets.get(capability.getNamespace());
+ if (namespaceSet != null) {
+ namespaceSet.removeCapability(capability);
+ }
+ }
+ }
+
+ /**
+ * Returns a mutable snapshot of capabilities that are candidates for
+ * satisfying the specified requirement.
+ * @param requirement the requirement
+ * @return the candidates for the requirement
+ */
+ public List<ModuleCapability> findCapabilities(ModuleRequirement requirement) {
+ NamespaceSet namespaceSet = namespaceSets.get(requirement.getNamespace());
+ if (namespaceSet == null) {
+ return Collections.emptyList();
+ }
+ return namespaceSet.findCapabilities(requirement);
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/container/Converters.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/container/Converters.java
new file mode 100644
index 000000000..5c2c0fc64
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/container/Converters.java
@@ -0,0 +1,107 @@
+/*******************************************************************************
+ * Copyright (c) 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.internal.container;
+
+import java.util.Collection;
+import java.util.List;
+import org.osgi.framework.wiring.*;
+import org.osgi.resource.*;
+
+public class Converters {
+
+ /**
+ * Coerce the generic type of a list from List<BundleCapability>
+ * to List<Capability>
+ * @param l List to be coerced.
+ * @return l coerced to List<Capability>
+ */
+ @SuppressWarnings("unchecked")
+ public static List<Capability> asListCapability(List<? extends Capability> l) {
+ return (List<Capability>) l;
+ }
+
+ /**
+ * Coerce the generic type of a list from List<BundleRequirement>
+ * to List<Requirement>
+ * @param l List to be coerced.
+ * @return l coerced to List<Requirement>
+ */
+ @SuppressWarnings("unchecked")
+ public static List<Requirement> asListRequirement(List<? extends Requirement> l) {
+ return (List<Requirement>) l;
+ }
+
+ /**
+ * Coerce the generic type of a list from List<? extends BundleCapability>
+ * to List<BundleCapability>
+ * @param l List to be coerced.
+ * @return l coerced to List<BundleCapability>
+ */
+ @SuppressWarnings("unchecked")
+ public static List<BundleCapability> asListBundleCapability(List<? extends BundleCapability> l) {
+ return (List<BundleCapability>) l;
+ }
+
+ /**
+ * Coerce the generic type of a list from List<? extends BundleRequirement>
+ * to List<BundleRequirement>
+ * @param l List to be coerced.
+ * @return l coerced to List<BundleRequirement>
+ */
+ @SuppressWarnings("unchecked")
+ public static List<BundleRequirement> asListBundleRequirement(List<? extends BundleRequirement> l) {
+ return (List<BundleRequirement>) l;
+ }
+
+ /**
+ * Coerce the generic type of a list from List<? extends BundleWire>
+ * to List<BundleWire>
+ * @param l List to be coerced.
+ * @return l coerced to List<BundleWire>
+ */
+ @SuppressWarnings("unchecked")
+ public static List<BundleWire> asListBundleWire(List<? extends BundleWire> l) {
+ return (List<BundleWire>) l;
+ }
+
+ /**
+ * Coerce the generic type of a list from List<? extends BundleWire>
+ * to List<BundleWire>
+ * @param l List to be coerced.
+ * @return l coerced to List<BundleWire>
+ */
+ @SuppressWarnings("unchecked")
+ public static List<Wire> asListWire(List<? extends Wire> l) {
+ return (List<Wire>) l;
+ }
+
+ /**
+ * Coerce the generic type of a list from List<? extends BundleRevision>
+ * to List<BundleRevision>
+ * @param l List to be coerced.
+ * @return l coerced to List<BundleRevision>
+ */
+ @SuppressWarnings("unchecked")
+ public static List<BundleRevision> asListBundleRevision(List<? extends BundleRevision> l) {
+ return (List<BundleRevision>) l;
+ }
+
+ /**
+ * Coerce the generic type of a collection from Collection<? extends Resource>
+ * to Collection<Resource>
+ * @param c List to be coerced.
+ * @return c coerced to Collection<Resource>
+ */
+ @SuppressWarnings("unchecked")
+ public static Collection<Resource> asCollectionResource(Collection<? extends Resource> c) {
+ return (Collection<Resource>) c;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/container/LockSet.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/container/LockSet.java
new file mode 100644
index 000000000..764c6b69a
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/container/LockSet.java
@@ -0,0 +1,86 @@
+/*******************************************************************************
+ * Copyright (c) 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.internal.container;
+
+import java.util.Map;
+import java.util.WeakHashMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class LockSet<T> {
+ private final Map<T, ReentrantLock> locks = new WeakHashMap<T, ReentrantLock>();
+ private final Object monitor = new Object();
+ private final boolean reentrant;
+
+ public LockSet(boolean reentrant) {
+ this.reentrant = reentrant;
+ }
+
+ public boolean lock(T t) {
+ ReentrantLock lock = getLock(t);
+ lock.lock();
+ if (reentrant)
+ return true;
+ if (lock.getHoldCount() > 1) {
+ lock.unlock();
+ return false;
+ }
+ return true;
+ }
+
+ public boolean tryLock(T t) {
+ ReentrantLock lock = getLock(t);
+ boolean obtained = lock.tryLock();
+ if (obtained) {
+ if (reentrant)
+ return true;
+ if (lock.getHoldCount() > 1) {
+ lock.unlock();
+ return false;
+ }
+ }
+ return obtained;
+ }
+
+ public boolean tryLock(T t, long time, TimeUnit unit) throws InterruptedException {
+ ReentrantLock lock = getLock(t);
+ boolean obtained = lock.tryLock(time, unit);
+ if (obtained) {
+ if (reentrant)
+ return true;
+ if (lock.getHoldCount() > 1) {
+ lock.unlock();
+ return false;
+ }
+ }
+ return obtained;
+ }
+
+ public void unlock(T t) {
+ synchronized (monitor) {
+ ReentrantLock lock = locks.get(t);
+ if (lock == null)
+ throw new IllegalStateException("No lock found."); //$NON-NLS-1$
+ lock.unlock();
+ }
+ }
+
+ private ReentrantLock getLock(T t) {
+ synchronized (monitor) {
+ ReentrantLock lock = locks.get(t);
+ if (lock == null) {
+ lock = new ReentrantLock();
+ locks.put(t, lock);
+ }
+ return lock;
+ }
+ }
+}
diff --git a/bundles/org.eclipse.osgi/resolver/src/org/apache/felix/resolver/Activator.java b/bundles/org.eclipse.osgi/resolver/src/org/apache/felix/resolver/Activator.java
new file mode 100644
index 000000000..2ed989a0c
--- /dev/null
+++ b/bundles/org.eclipse.osgi/resolver/src/org/apache/felix/resolver/Activator.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.felix.resolver;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.resolver.Resolver;
+
+public class Activator implements BundleActivator
+{
+ public static final String LOG_LEVEL = "felix.resolver.log.level";
+
+ public void start(BundleContext bc) throws Exception
+ {
+ int logLevel = 4;
+ if (bc.getProperty(LOG_LEVEL) != null)
+ {
+ try
+ {
+ logLevel = Integer.parseInt(bc.getProperty(LOG_LEVEL));
+ }
+ catch (NumberFormatException ex)
+ {
+ // Use default log level.
+ }
+ }
+ bc.registerService(
+ Resolver.class,
+ new ResolverImpl(new Logger(logLevel)),
+ null);
+ }
+
+ public void stop(BundleContext bc) throws Exception
+ {
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.osgi/resolver/src/org/apache/felix/resolver/Candidates.java b/bundles/org.eclipse.osgi/resolver/src/org/apache/felix/resolver/Candidates.java
new file mode 100644
index 000000000..3bffc1995
--- /dev/null
+++ b/bundles/org.eclipse.osgi/resolver/src/org/apache/felix/resolver/Candidates.java
@@ -0,0 +1,1051 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.Collections;
+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.osgi.framework.Version;
+import org.osgi.framework.namespace.HostNamespace;
+import org.osgi.framework.namespace.PackageNamespace;
+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;
+
+class Candidates
+{
+ public static final int MANDATORY = 0;
+ public static final int OPTIONAL = 1;
+ public static final int ON_DEMAND = 2;
+
+ private final Set<Resource> m_mandatoryResources;
+ // Maps a capability to requirements that match it.
+ private final Map<Capability, Set<Requirement>> m_dependentMap;
+ // Maps a requirement to the capability it matches.
+ private final Map<Requirement, List<Capability>> m_candidateMap;
+ // Maps a bundle revision to its associated wrapped revision; this only happens
+ // when a revision being resolved has fragments to attach to it.
+ private final Map<Resource, WrappedResource> m_allWrappedHosts;
+ // Map used when populating candidates to hold intermediate and final results.
+ private final Map<Resource, Object> m_populateResultCache;
+
+ // Flag to signal if fragments are present in the candidate map.
+ private boolean m_fragmentsPresent = false;
+
+ /**
+ * Private copy constructor used by the copy() method.
+ * @param dependentMap the capability dependency map.
+ * @param candidateMap the requirement candidate map.
+ * @param hostFragments the fragment map.
+ * @param wrappedHosts the wrapped hosts map.
+ **/
+ private Candidates(
+ Set<Resource> mandatoryResources,
+ Map<Capability, Set<Requirement>> dependentMap,
+ Map<Requirement, List<Capability>> candidateMap,
+ Map<Resource, WrappedResource> wrappedHosts, Map<Resource, Object> populateResultCache,
+ boolean fragmentsPresent)
+ {
+ m_mandatoryResources = mandatoryResources;
+ m_dependentMap = dependentMap;
+ m_candidateMap = candidateMap;
+ m_allWrappedHosts = wrappedHosts;
+ m_populateResultCache = populateResultCache;
+ m_fragmentsPresent = fragmentsPresent;
+ }
+
+ /**
+ * Constructs an empty Candidates object.
+ **/
+ public Candidates()
+ {
+ m_mandatoryResources = new HashSet<Resource>();
+ m_dependentMap = new HashMap<Capability, Set<Requirement>>();
+ m_candidateMap = new HashMap<Requirement, List<Capability>>();
+ m_allWrappedHosts = new HashMap<Resource, WrappedResource>();
+ m_populateResultCache = new HashMap<Resource, Object>();
+ }
+
+ /**
+ * Populates candidates for the specified revision. How a revision is
+ * resolved depends on its resolution type as follows:
+ * <ul>
+ * <li><tt>MANDATORY</tt> - must resolve and failure to do so throws
+ * an exception.</li>
+ * <li><tt>OPTIONAL</tt> - attempt to resolve, but no exception is thrown
+ * if the resolve fails.</li>
+ * <li><tt>ON_DEMAND</tt> - only resolve on demand; this only applies to
+ * fragments and will only resolve a fragment if its host is already
+ * selected as a candidate.</li>
+ * </ul>
+ * @param rc the resolve context used for populating the candidates.
+ * @param resource the resource whose candidates should be populated.
+ * @param resolution indicates the resolution type.
+ */
+ public final void populate(
+ ResolveContext rc, Resource resource, int resolution) throws ResolutionException
+ {
+ // Get the current result cache value, to make sure the revision
+ // hasn't already been populated.
+ Object cacheValue = m_populateResultCache.get(resource);
+ // Has been unsuccessfully populated.
+ if (cacheValue instanceof ResolutionException)
+ {
+ return;
+ }
+ // Has been successfully populated.
+ else if (cacheValue instanceof Boolean)
+ {
+ return;
+ }
+
+ // We will always attempt to populate fragments, since this is necessary
+ // for ondemand attaching of fragment. However, we'll only attempt to
+ // populate optional non-fragment revisions if they aren't already
+ // resolved.
+ boolean isFragment = Util.isFragment(resource);
+ if (!isFragment && rc.getWirings().containsKey(resource))
+ {
+ return;
+ }
+
+ // Always attempt to populate mandatory or optional revisions.
+ // However, for on-demand fragments only populate if their host
+ // is already populated.
+ if ((resolution != ON_DEMAND)
+ || (isFragment && populateFragmentOndemand(rc, resource)))
+ {
+ if (resolution == MANDATORY)
+ {
+ m_mandatoryResources.add(resource);
+ }
+ try
+ {
+ // Try to populate candidates for the optional revision.
+ populateResource(rc, resource);
+ }
+ catch (ResolutionException ex)
+ {
+ // Only throw an exception if resolution is mandatory.
+ if (resolution == MANDATORY)
+ {
+ throw ex;
+ }
+ }
+ }
+ }
+
+ /**
+ * Populates candidates for the specified revision.
+ * @param state the resolver state used for populating the candidates.
+ * @param revision the revision whose candidates should be populated.
+ */
+// TODO: FELIX3 - Modify to not be recursive.
+ private void populateResource(ResolveContext rc, Resource resource) throws ResolutionException
+ {
+ // Determine if we've already calculated this revision's candidates.
+ // The result cache will have one of three values:
+ // 1. A resolve exception if we've already attempted to populate the
+ // revision's candidates but were unsuccessful.
+ // 2. Boolean.TRUE indicating we've already attempted to populate the
+ // revision's candidates and were successful.
+ // 3. An array containing the cycle count, current map of candidates
+ // for already processed requirements, and a list of remaining
+ // requirements whose candidates still need to be calculated.
+ // For case 1, rethrow the exception. For case 2, simply return immediately.
+ // For case 3, this means we have a cycle so we should continue to populate
+ // the candidates where we left off and not record any results globally
+ // until we've popped completely out of the cycle.
+
+ // Keeps track of the number of times we've reentered this method
+ // for the current revision.
+ Integer cycleCount = null;
+
+ // Keeps track of the candidates we've already calculated for the
+ // current revision's requirements.
+ Map<Requirement, List<Capability>> localCandidateMap = null;
+
+ // Keeps track of the current revision's requirements for which we
+ // haven't yet found candidates.
+ List<Requirement> remainingReqs = null;
+
+ // Get the cache value for the current revision.
+ Object cacheValue = m_populateResultCache.get(resource);
+
+ // This is case 1.
+ if (cacheValue instanceof ResolutionException)
+ {
+ throw (ResolutionException) cacheValue;
+ }
+ // This is case 2.
+ else if (cacheValue instanceof Boolean)
+ {
+ return;
+ }
+ // This is case 3.
+ else if (cacheValue != null)
+ {
+ // Increment and get the cycle count.
+ cycleCount = (Integer)
+ (((Object[]) cacheValue)[0]
+ = new Integer(((Integer) ((Object[]) cacheValue)[0]).intValue() + 1));
+ // Get the already populated candidates.
+ localCandidateMap = (Map) ((Object[]) cacheValue)[1];
+ // Get the remaining requirements.
+ remainingReqs = (List) ((Object[]) cacheValue)[2];
+ }
+
+ // If there is no cache value for the current revision, then this is
+ // the first time we are attempting to populate its candidates, so
+ // do some one-time checks and initialization.
+ if ((remainingReqs == null) && (localCandidateMap == null))
+ {
+ // Record cycle count.
+ cycleCount = new Integer(0);
+
+ // Create a local map for populating candidates first, just in case
+ // the revision is not resolvable.
+ localCandidateMap = new HashMap();
+
+ // Create a modifiable list of the revision's requirements.
+ remainingReqs = new ArrayList(resource.getRequirements(null));
+
+ // Add these value to the result cache so we know we are
+ // in the middle of populating candidates for the current
+ // revision.
+ m_populateResultCache.put(resource,
+ cacheValue = new Object[] { cycleCount, localCandidateMap, remainingReqs });
+ }
+
+ // If we have requirements remaining, then find candidates for them.
+ while (!remainingReqs.isEmpty())
+ {
+ Requirement req = remainingReqs.remove(0);
+
+ // Ignore non-effective and dynamic requirements.
+ String resolution = req.getDirectives()
+ .get(PackageNamespace.REQUIREMENT_RESOLUTION_DIRECTIVE);
+ if (!rc.isEffective(req)
+ || ((resolution != null)
+ && resolution.equals(PackageNamespace.RESOLUTION_DYNAMIC)))
+ {
+ continue;
+ }
+
+ // Process the candidates, removing any candidates that
+ // cannot resolve.
+ List<Capability> candidates = rc.findProviders(req);
+ ResolutionException rethrow = processCandidates(rc, resource, candidates);
+
+ // First, due to cycles, makes sure we haven't already failed in
+ // a deeper recursion.
+ Object result = m_populateResultCache.get(resource);
+ if (result instanceof ResolutionException)
+ {
+ throw (ResolutionException) result;
+ }
+ // Next, if are no candidates remaining and the requirement is not
+ // not optional, then record and throw a resolve exception.
+ else if (candidates.isEmpty() && !Util.isOptional(req))
+ {
+ String msg = "Unable to resolve " + resource
+ + ": missing requirement " + req;
+ if (rethrow != null)
+ {
+ msg = msg + " [caused by: " + rethrow.getMessage() + "]";
+ }
+ rethrow = new ResolutionException(msg, null, Collections.singleton(req));
+ m_populateResultCache.put(resource, rethrow);
+ throw rethrow;
+ }
+ // Otherwise, if we actually have candidates for the requirement, then
+ // add them to the local candidate map.
+ else if (candidates.size() > 0)
+ {
+ localCandidateMap.put(req, candidates);
+ }
+ }
+
+ // If we are exiting from a cycle then decrement
+ // cycle counter, otherwise record the result.
+ if (cycleCount.intValue() > 0)
+ {
+ ((Object[]) cacheValue)[0] = new Integer(cycleCount.intValue() - 1);
+ }
+ else if (cycleCount.intValue() == 0)
+ {
+ // Record that the revision was successfully populated.
+ m_populateResultCache.put(resource, Boolean.TRUE);
+
+ // Merge local candidate map into global candidate map.
+ if (localCandidateMap.size() > 0)
+ {
+ add(localCandidateMap);
+ }
+ }
+ }
+
+ private boolean populateFragmentOndemand(ResolveContext rc, Resource resource)
+ throws ResolutionException
+ {
+ // Create a modifiable list of the revision's requirements.
+ List<Requirement> remainingReqs =
+ new ArrayList(resource.getRequirements(null));
+ // Find the host requirement.
+ Requirement hostReq = null;
+ for (Iterator<Requirement> it = remainingReqs.iterator();
+ it.hasNext(); )
+ {
+ Requirement r = it.next();
+ if (r.getNamespace().equals(HostNamespace.HOST_NAMESPACE))
+ {
+ hostReq = r;
+ it.remove();
+ break;
+ }
+ }
+ // Get candidates hosts and keep any that have been populated.
+ List<Capability> hosts = rc.findProviders(hostReq);
+ for (Iterator<Capability> it = hosts.iterator(); it.hasNext(); )
+ {
+ Capability host = it.next();
+ if (!isPopulated(host.getResource()))
+ {
+ it.remove();
+ }
+ }
+ // If there aren't any populated hosts, then we can just
+ // return since this fragment isn't needed.
+ if (hosts.isEmpty())
+ {
+ return false;
+ }
+
+ // If there are populated host candidates, then prepopulate
+ // the result cache with the work we've done so far.
+ // Record cycle count, but start at -1 since it will
+ // be incremented again in populate().
+ Integer cycleCount = new Integer(-1);
+ // Create a local map for populating candidates first, just in case
+ // the revision is not resolvable.
+ Map<Requirement, List<Capability>> localCandidateMap =
+ new HashMap<Requirement, List<Capability>>();
+ // Add the discovered host candidates to the local candidate map.
+ localCandidateMap.put(hostReq, hosts);
+ // Add these value to the result cache so we know we are
+ // in the middle of populating candidates for the current
+ // revision.
+ m_populateResultCache.put(resource,
+ new Object[] { cycleCount, localCandidateMap, remainingReqs });
+ return true;
+ }
+
+ public void populateDynamic(
+ ResolveContext rc, Resource resource,
+ Requirement req, List<Capability> candidates) throws ResolutionException
+ {
+ // Record the revision associated with the dynamic require
+ // as a mandatory revision.
+ m_mandatoryResources.add(resource);
+
+ // Add the dynamic imports candidates.
+ add(req, candidates);
+
+ // Process the candidates, removing any candidates that
+ // cannot resolve.
+ ResolutionException rethrow = processCandidates(rc, resource, candidates);
+
+ if (candidates.isEmpty())
+ {
+ if (rethrow == null)
+ {
+ rethrow = new ResolutionException(
+ "Dynamic import failed.", null, Collections.singleton(req));
+ }
+ throw rethrow;
+ }
+
+ m_populateResultCache.put(resource, Boolean.TRUE);
+ }
+
+ /**
+ * This method performs common processing on the given set of candidates.
+ * Specifically, it removes any candidates which cannot resolve and it
+ * synthesizes candidates for any candidates coming from any attached
+ * fragments, since fragment capabilities only appear once, but technically
+ * each host represents a unique capability.
+ * @param state the resolver state.
+ * @param revision the revision being resolved.
+ * @param candidates the candidates to process.
+ * @return a resolve exception to be re-thrown, if any, or null.
+ */
+ private ResolutionException processCandidates(
+ ResolveContext rc,
+ Resource resource,
+ List<Capability> candidates)
+ {
+ // Get satisfying candidates and populate their candidates if necessary.
+ ResolutionException rethrow = null;
+ Set<Capability> fragmentCands = null;
+ for (Iterator<Capability> itCandCap = candidates.iterator();
+ itCandCap.hasNext(); )
+ {
+ Capability candCap = itCandCap.next();
+
+ boolean isFragment = Util.isFragment(candCap.getResource());
+
+ // If the capability is from a fragment, then record it
+ // because we have to insert associated host capabilities
+ // if the fragment is already attached to any hosts.
+ if (isFragment)
+ {
+ if (fragmentCands == null)
+ {
+ fragmentCands = new HashSet<Capability>();
+ }
+ fragmentCands.add(candCap);
+ }
+
+ // If the candidate revision is a fragment, then always attempt
+ // to populate candidates for its dependency, since it must be
+ // attached to a host to be used. Otherwise, if the candidate
+ // revision is not already resolved and is not the current version
+ // we are trying to populate, then populate the candidates for
+ // its dependencies as well.
+ // NOTE: Technically, we don't have to check to see if the
+ // candidate revision is equal to the current revision, but this
+ // saves us from recursing and also simplifies exceptions messages
+ // since we effectively chain exception messages for each level
+ // of recursion; thus, any avoided recursion results in fewer
+ // exceptions to chain when an error does occur.
+ if ((isFragment || !rc.getWirings().containsKey(candCap.getResource()))
+ && !candCap.getResource().equals(resource))
+ {
+ try
+ {
+ populateResource(rc, candCap.getResource());
+ }
+ catch (ResolutionException ex)
+ {
+ if (rethrow == null)
+ {
+ rethrow = ex;
+ }
+ // Remove the candidate since we weren't able to
+ // populate its candidates.
+ itCandCap.remove();
+ }
+ }
+ }
+
+ // If any of the candidates for the requirement were from a fragment,
+ // then also insert synthesized hosted capabilities for any other host
+ // to which the fragment is attached since they are all effectively
+ // unique capabilities.
+ if (fragmentCands != null)
+ {
+ for (Capability fragCand : fragmentCands)
+ {
+ // Only necessary for resolved fragments.
+ Wiring wiring = rc.getWirings().get(fragCand.getResource());
+ if (wiring != null)
+ {
+ // Fragments only have host wire, so each wire represents
+ // an attached host.
+ for (Wire wire : wiring.getRequiredResourceWires(null))
+ {
+ // If the capability is a package, then make sure the
+ // host actually provides it in its resolved capabilities,
+ // since it may be a substitutable export.
+ if (!fragCand.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE)
+ || rc.getWirings().get(wire.getProvider())
+ .getResourceCapabilities(null).contains(fragCand))
+ {
+ // Note that we can just add this as a candidate
+ // directly, since we know it is already resolved.
+ // NOTE: We are synthesizing a hosted capability here,
+ // but we are not using a ShadowList like we do when
+ // we synthesizing capabilities for unresolved hosts.
+ // It is not necessary to use the ShadowList here since
+ // the host is resolved, because in that case we can
+ // calculate the proper package space by traversing
+ // the wiring. In the unresolved case, this isn't possible
+ // so we need to use the ShadowList so we can keep
+ // a reference to a synthesized resource with attached
+ // fragments so we can correctly calculate its package
+ // space.
+ rc.insertHostedCapability(
+ candidates,
+ new WrappedCapability(
+ wire.getCapability().getResource(),
+ fragCand));
+ }
+ }
+ }
+ }
+ }
+
+ return rethrow;
+ }
+
+ public boolean isPopulated(Resource resource)
+ {
+ Object value = m_populateResultCache.get(resource);
+ return ((value != null) && (value instanceof Boolean));
+ }
+
+ public ResolutionException getResolveException(Resource resource)
+ {
+ Object value = m_populateResultCache.get(resource);
+ return ((value != null) && (value instanceof ResolutionException))
+ ? (ResolutionException) value : null;
+ }
+
+ /**
+ * Adds a requirement and its matching candidates to the internal data
+ * structure. This method assumes it owns the data being passed in and
+ * does not make a copy. It takes the data and processes, such as calculating
+ * which requirements depend on which capabilities and recording any fragments
+ * it finds for future merging.
+ * @param req the requirement to add.
+ * @param candidates the candidates matching the requirement.
+ **/
+ private void add(Requirement req, List<Capability> candidates)
+ {
+ if (req.getNamespace().equals(HostNamespace.HOST_NAMESPACE))
+ {
+ m_fragmentsPresent = true;
+ }
+
+ // Record the candidates.
+ m_candidateMap.put(req, candidates);
+ }
+
+ /**
+ * Adds requirements and candidates in bulk. The outer map is not retained
+ * by this method, but the inner data structures are, so they should not
+ * be further modified by the caller.
+ * @param candidates the bulk requirements and candidates to add.
+ **/
+ private void add(Map<Requirement, List<Capability>> candidates)
+ {
+ for (Entry<Requirement, List<Capability>> entry : candidates.entrySet())
+ {
+ add(entry.getKey(), entry.getValue());
+ }
+ }
+
+ /**
+ * Returns the wrapped resource associated with the given resource. If the resource
+ * was not wrapped, then the resource itself is returned. This is really only
+ * needed to determine if the root resources of the resolve have been wrapped.
+ * @param r the resource whose wrapper is desired.
+ * @return the wrapper resource or the resource itself if it was not wrapped.
+ **/
+ public Resource getWrappedHost(Resource r)
+ {
+ Resource wrapped = m_allWrappedHosts.get(r);
+ return (wrapped == null) ? r : wrapped;
+ }
+
+ /**
+ * Gets the candidates associated with a given requirement.
+ * @param req the requirement whose candidates are desired.
+ * @return the matching candidates or null.
+ **/
+ public List<Capability> getCandidates(Requirement req)
+ {
+ return m_candidateMap.get(req);
+ }
+
+ /**
+ * Merges fragments into their hosts. It does this by wrapping all host
+ * modules and attaching their selected fragments, removing all unselected
+ * fragment modules, and replacing all occurrences of the original fragments
+ * in the internal data structures with the wrapped host modules instead.
+ * Thus, fragment capabilities and requirements are merged into the appropriate
+ * host and the candidates for the fragment now become candidates for the host.
+ * Likewise, any module depending on a fragment now depend on the host. Note
+ * that this process is sort of like multiplication, since one fragment that
+ * can attach to two hosts effectively gets multiplied across the two hosts.
+ * So, any modules being satisfied by the fragment will end up having the
+ * two hosts as potential candidates, rather than the single fragment.
+ * @throws ResolutionException if the removal of any unselected fragments result
+ * in the root module being unable to resolve.
+ **/
+ public void prepare(ResolveContext rc) throws ResolutionException
+ {
+ // Maps a host capability to a map containing its potential fragments;
+ // the fragment map maps a fragment symbolic name to a map that maps
+ // a version to a list of fragments requirements matching that symbolic
+ // name and version.
+ Map<Capability, Map<String, Map<Version, List<Requirement>>>>
+ hostFragments = Collections.EMPTY_MAP;
+ if (m_fragmentsPresent)
+ {
+ hostFragments = populateDependents();
+ }
+
+ // This method performs the following steps:
+ // 1. Select the fragments to attach to a given host.
+ // 2. Wrap hosts and attach fragments.
+ // 3. Remove any unselected fragments. This is necessary because
+ // other revisions may depend on the capabilities of unselected
+ // fragments, so we need to remove the unselected fragments and
+ // any revisions that depends on them, which could ultimately cause
+ // the entire resolve to fail.
+ // 4. Replace all fragments with any host it was merged into
+ // (effectively multiplying it).
+ // * This includes setting candidates for attached fragment
+ // requirements as well as replacing fragment capabilities
+ // with host's attached fragment capabilities.
+
+ // Steps 1 and 2
+ List<WrappedResource> hostResources = new ArrayList<WrappedResource>();
+ List<Resource> unselectedFragments = new ArrayList<Resource>();
+ for (Entry<Capability, Map<String, Map<Version, List<Requirement>>>>
+ hostEntry : hostFragments.entrySet())
+ {
+ // Step 1
+ Capability hostCap = hostEntry.getKey();
+ Map<String, Map<Version, List<Requirement>>> fragments
+ = hostEntry.getValue();
+ List<Resource> selectedFragments = new ArrayList<Resource>();
+ for (Entry<String, Map<Version, List<Requirement>>> fragEntry
+ : fragments.entrySet())
+ {
+ boolean isFirst = true;
+ for (Entry<Version, List<Requirement>> versionEntry
+ : fragEntry.getValue().entrySet())
+ {
+ for (Requirement hostReq : versionEntry.getValue())
+ {
+ // Selecting the first fragment in each entry, which
+ // is equivalent to selecting the highest version of
+ // each fragment with a given symbolic name.
+ if (isFirst)
+ {
+ selectedFragments.add(hostReq.getResource());
+ isFirst = false;
+ }
+ // For any fragment that wasn't selected, remove the
+ // current host as a potential host for it and remove it
+ // as a dependent on the host. If there are no more
+ // potential hosts for the fragment, then mark it as
+ // unselected for later removal.
+ else
+ {
+ m_dependentMap.get(hostCap).remove(hostReq);
+ List<Capability> hosts = m_candidateMap.get(hostReq);
+ hosts.remove(hostCap);
+ if (hosts.isEmpty())
+ {
+ unselectedFragments.add(hostReq.getResource());
+ }
+ }
+ }
+ }
+ }
+
+ // Step 2
+ WrappedResource wrappedHost =
+ new WrappedResource(hostCap.getResource(), selectedFragments);
+ hostResources.add(wrappedHost);
+ m_allWrappedHosts.put(hostCap.getResource(), wrappedHost);
+ }
+
+ // Step 3
+ for (Resource fragment : unselectedFragments)
+ {
+ removeResource(fragment,
+ new ResolutionException(
+ "Fragment was not selected for attachment: " + fragment));
+ }
+
+ // Step 4
+ for (WrappedResource hostResource : hostResources)
+ {
+ // Replaces capabilities from fragments with the capabilities
+ // from the merged host.
+ for (Capability c : hostResource.getCapabilities(null))
+ {
+ // Don't replace the host capability, since the fragment will
+ // really be attached to the original host, not the wrapper.
+ if (!c.getNamespace().equals(HostNamespace.HOST_NAMESPACE))
+ {
+ Capability origCap = ((HostedCapability) c).getDeclaredCapability();
+ // Note that you might think we could remove the original cap
+ // from the dependent map, but you can't since it may come from
+ // a fragment that is attached to multiple hosts, so each host
+ // will need to make their own copy.
+ Set<Requirement> dependents = m_dependentMap.get(origCap);
+ if (dependents != null)
+ {
+ dependents = new HashSet<Requirement>(dependents);
+ m_dependentMap.put(c, dependents);
+ for (Requirement r : dependents)
+ {
+ // We have synthesized hosted capabilities for all
+ // fragments that have been attached to hosts by
+ // wrapping the host bundle and their attached
+ // fragments. We need to use the ResolveContext to
+ // determine the proper priority order for hosted
+ // capabilities since the order may depend on the
+ // declaring host/fragment combination. However,
+ // internally we completely wrap the host revision
+ // and make all capabilities/requirements point back
+ // to the wrapped host not the declaring host. The
+ // ResolveContext expects HostedCapabilities to point
+ // to the declaring revision, so we need two separate
+ // candidate lists: one for the ResolveContext with
+ // HostedCapabilities pointing back to the declaring
+ // host and one for the resolver with HostedCapabilities
+ // pointing back to the wrapped host. We ask the
+ // ResolveContext to insert its appropriate HostedCapability
+ // into its list, then we mirror the insert into a
+ // shadow list with the resolver's HostedCapability.
+ // We only need to ask the ResolveContext to find
+ // the insert position for fragment caps since these
+ // were synthesized and we don't know their priority.
+ // However, in the resolver's candidate list we need
+ // to replace all caps with the wrapped caps, no
+ // matter if they come from the host or fragment,
+ // since we are completing replacing the declaring
+ // host and fragments with the wrapped host.
+ List<Capability> cands = m_candidateMap.get(r);
+ if (!(cands instanceof ShadowList))
+ {
+ ShadowList<Capability> shadow =
+ new ShadowList<Capability>(cands);
+ m_candidateMap.put(r, shadow);
+ cands = shadow;
+ }
+
+ // If the original capability is from a fragment, then
+ // ask the ResolveContext to insert it and update the
+ // shadow copy of the list accordingly.
+ if (!origCap.getResource().equals(hostResource.getDeclaredResource()))
+ {
+ List<Capability> original = ((ShadowList) cands).getOriginal();
+ int removeIdx = original.indexOf(origCap);
+ if (removeIdx != -1)
+ {
+ original.remove(removeIdx);
+ cands.remove(removeIdx);
+ }
+ int insertIdx = rc.insertHostedCapability(
+ original,
+ new SimpleHostedCapability(
+ hostResource.getDeclaredResource(),
+ origCap));
+ cands.add(insertIdx, c);
+ }
+ // If the original capability is from the host, then
+ // we just need to replace it in the shadow list.
+ else
+ {
+ int idx = cands.indexOf(origCap);
+ cands.set(idx, c);
+ }
+ }
+ }
+ }
+ }
+
+ // Copy candidates for fragment requirements to the host.
+ for (Requirement r : hostResource.getRequirements(null))
+ {
+ Requirement origReq = ((WrappedRequirement) r).getDeclaredRequirement();
+ List<Capability> cands = m_candidateMap.get(origReq);
+ if (cands != null)
+ {
+ m_candidateMap.put(r, new ArrayList<Capability>(cands));
+ for (Capability cand : cands)
+ {
+ Set<Requirement> dependents = m_dependentMap.get(cand);
+ dependents.remove(origReq);
+ dependents.add(r);
+ }
+ }
+ }
+ }
+
+ // Lastly, verify that all mandatory revisions are still
+ // populated, since some might have become unresolved after
+ // selecting fragments/singletons.
+ for (Resource resource : m_mandatoryResources)
+ {
+ if (!isPopulated(resource))
+ {
+ throw getResolveException(resource);
+ }
+ }
+ }
+
+ // Maps a host capability to a map containing its potential fragments;
+ // the fragment map maps a fragment symbolic name to a map that maps
+ // a version to a list of fragments requirements matching that symbolic
+ // name and version.
+ private Map<Capability,
+ Map<String, Map<Version, List<Requirement>>>> populateDependents()
+ {
+ Map<Capability, Map<String, Map<Version, List<Requirement>>>>
+ hostFragments = new HashMap<Capability,
+ Map<String, Map<Version, List<Requirement>>>>();
+ for (Entry<Requirement, List<Capability>> entry : m_candidateMap.entrySet())
+ {
+ Requirement req = entry.getKey();
+ List<Capability> caps = entry.getValue();
+ for (Capability cap : caps)
+ {
+ // Record the requirement as dependent on the capability.
+ Set<Requirement> dependents = m_dependentMap.get(cap);
+ if (dependents == null)
+ {
+ dependents = new HashSet<Requirement>();
+ m_dependentMap.put(cap, dependents);
+ }
+ dependents.add(req);
+
+ // Keep track of hosts and associated fragments.
+ if (req.getNamespace().equals(HostNamespace.HOST_NAMESPACE))
+ {
+ String resSymName = Util.getSymbolicName(req.getResource());
+ Version resVersion = Util.getVersion(req.getResource());
+
+ Map<String, Map<Version, List<Requirement>>>
+ fragments = hostFragments.get(cap);
+ if (fragments == null)
+ {
+ fragments = new HashMap<String, Map<Version, List<Requirement>>>();
+ hostFragments.put(cap, fragments);
+ }
+ Map<Version, List<Requirement>> fragmentVersions = fragments.get(resSymName);
+ if (fragmentVersions == null)
+ {
+ fragmentVersions =
+ new TreeMap<Version, List<Requirement>>(Collections.reverseOrder());
+ fragments.put(resSymName, fragmentVersions);
+ }
+ List<Requirement> actual = fragmentVersions.get(resVersion);
+ if (actual == null)
+ {
+ actual = new ArrayList<Requirement>();
+ fragmentVersions.put(resVersion, actual);
+ }
+ actual.add(req);
+ }
+ }
+ }
+
+ return hostFragments;
+ }
+
+ /**
+ * Removes a module from the internal data structures if it wasn't selected
+ * as a fragment or a singleton. This process may cause other modules to
+ * become unresolved if they depended on the module's capabilities and there
+ * is no other candidate.
+ * @param revision the module to remove.
+ * @throws ResolveException if removing the module caused the resolve to fail.
+ **/
+ private void removeResource(Resource resource, ResolutionException ex)
+ throws ResolutionException
+ {
+ // Add removal reason to result cache.
+ m_populateResultCache.put(resource, ex);
+ // Remove from dependents.
+ Set<Resource> unresolvedResources = new HashSet<Resource>();
+ remove(resource, unresolvedResources);
+ // Remove dependents that failed as a result of removing revision.
+ while (!unresolvedResources.isEmpty())
+ {
+ Iterator<Resource> it = unresolvedResources.iterator();
+ resource = it.next();
+ it.remove();
+ remove(resource, unresolvedResources);
+ }
+ }
+
+ /**
+ * Removes the specified module from the internal data structures, which
+ * involves removing its requirements and its capabilities. This may cause
+ * other modules to become unresolved as a result.
+ * @param br the module to remove.
+ * @param unresolvedRevisions a list to containing any additional modules that
+ * that became unresolved as a result of removing this module and will
+ * also need to be removed.
+ * @throws ResolveException if removing the module caused the resolve to fail.
+ **/
+ private void remove(Resource resource, Set<Resource> unresolvedResources)
+ throws ResolutionException
+ {
+ for (Requirement r : resource.getRequirements(null))
+ {
+ remove(r);
+ }
+
+ for (Capability c : resource.getCapabilities(null))
+ {
+ remove(c, unresolvedResources);
+ }
+ }
+
+ /**
+ * Removes a requirement from the internal data structures.
+ * @param req the requirement to remove.
+ **/
+ private void remove(Requirement req)
+ {
+ boolean isFragment = req.getNamespace().equals(HostNamespace.HOST_NAMESPACE);
+
+ List<Capability> candidates = m_candidateMap.remove(req);
+ if (candidates != null)
+ {
+ for (Capability cap : candidates)
+ {
+ Set<Requirement> dependents = m_dependentMap.get(cap);
+ if (dependents != null)
+ {
+ dependents.remove(req);
+ }
+ }
+ }
+ }
+
+ /**
+ * Removes a capability from the internal data structures. This may cause
+ * other modules to become unresolved as a result.
+ * @param c the capability to remove.
+ * @param unresolvedRevisions a list to containing any additional modules that
+ * that became unresolved as a result of removing this module and will
+ * also need to be removed.
+ * @throws ResolveException if removing the module caused the resolve to fail.
+ **/
+ private void remove(Capability c, Set<Resource> unresolvedResources)
+ throws ResolutionException
+ {
+ Set<Requirement> dependents = m_dependentMap.remove(c);
+ if (dependents != null)
+ {
+ for (Requirement r : dependents)
+ {
+ List<Capability> candidates = m_candidateMap.get(r);
+ candidates.remove(c);
+ if (candidates.isEmpty())
+ {
+ m_candidateMap.remove(r);
+ if (!Util.isOptional(r))
+ {
+ String msg = "Unable to resolve " + r.getResource()
+ + ": missing requirement " + r;
+ m_populateResultCache.put(
+ r.getResource(),
+ new ResolutionException(msg, null, Collections.singleton(r)));
+ unresolvedResources.add(r.getResource());
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates a copy of the Candidates object. This is used for creating
+ * permutations when package space conflicts are discovered.
+ * @return copy of this Candidates object.
+ **/
+ public Candidates copy()
+ {
+ Map<Capability, Set<Requirement>> dependentMap =
+ new HashMap<Capability, Set<Requirement>>();
+ for (Entry<Capability, Set<Requirement>> entry : m_dependentMap.entrySet())
+ {
+ Set<Requirement> dependents = new HashSet<Requirement>(entry.getValue());
+ dependentMap.put(entry.getKey(), dependents);
+ }
+
+ Map<Requirement, List<Capability>> candidateMap =
+ new HashMap<Requirement, List<Capability>>();
+ for (Entry<Requirement, List<Capability>> entry
+ : m_candidateMap.entrySet())
+ {
+ List<Capability> candidates =
+ new ArrayList<Capability>(entry.getValue());
+ candidateMap.put(entry.getKey(), candidates);
+ }
+
+ return new Candidates(
+ m_mandatoryResources, dependentMap, candidateMap,
+ m_allWrappedHosts, m_populateResultCache, m_fragmentsPresent);
+ }
+
+ public void dump(ResolveContext rc)
+ {
+ // Create set of all revisions from requirements.
+ Set<Resource> resources = new HashSet<Resource>();
+ for (Entry<Requirement, List<Capability>> entry
+ : m_candidateMap.entrySet())
+ {
+ resources.add(entry.getKey().getResource());
+ }
+ // Now dump the revisions.
+ System.out.println("=== BEGIN CANDIDATE MAP ===");
+ for (Resource resource : resources)
+ {
+ Wiring wiring = rc.getWirings().get(resource);
+ System.out.println(" " + resource
+ + " (" + ((wiring != null) ? "RESOLVED)" : "UNRESOLVED)"));
+ List<Requirement> reqs = (wiring != null)
+ ? wiring.getResourceRequirements(null)
+ : resource.getRequirements(null);
+ for (Requirement req : reqs)
+ {
+ List<Capability> candidates = m_candidateMap.get(req);
+ if ((candidates != null) && (candidates.size() > 0))
+ {
+ System.out.println(" " + req + ": " + candidates);
+ }
+ }
+ reqs = (wiring != null)
+ ? Util.getDynamicRequirements(wiring.getResourceRequirements(null))
+ : Util.getDynamicRequirements(resource.getRequirements(null));
+ for (Requirement req : reqs)
+ {
+ List<Capability> candidates = m_candidateMap.get(req);
+ if ((candidates != null) && (candidates.size() > 0))
+ {
+ System.out.println(" " + req + ": " + candidates);
+ }
+ }
+ }
+ System.out.println("=== END CANDIDATE MAP ===");
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.osgi/resolver/src/org/apache/felix/resolver/Logger.java b/bundles/org.eclipse.osgi/resolver/src/org/apache/felix/resolver/Logger.java
new file mode 100644
index 000000000..9eaa21212
--- /dev/null
+++ b/bundles/org.eclipse.osgi/resolver/src/org/apache/felix/resolver/Logger.java
@@ -0,0 +1,122 @@
+/*
+ * 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;
+
+/**
+ * <p>
+ * This class mimics the standard OSGi <tt>LogService</tt> interface. An
+ * instance of this class is used by the framework for all logging. By default
+ * this class logs messages to standard out. The log level can be set to
+ * control the amount of logging performed, where a higher number results in
+ * more logging. A log level of zero turns off logging completely.
+ * </p>
+ * <p>
+ * The log levels match those specified in the OSGi Log Service (i.e., 1 = error,
+ * 2 = warning, 3 = information, and 4 = debug). The default value is 1.
+ * </p>
+ * <p>
+ * This class also uses the System Bundle's context to track log services
+ * and will use the highest ranking log service, if present, as a back end
+ * instead of printing to standard out. The class uses reflection to invoking
+ * the log service's method to avoid a dependency on the log interface.
+ * </p>
+**/
+public class Logger
+{
+ public static final int LOG_ERROR = 1;
+ public static final int LOG_WARNING = 2;
+ public static final int LOG_INFO = 3;
+ public static final int LOG_DEBUG = 4;
+
+ private int m_logLevel = 1;
+
+ private final static int LOGGER_OBJECT_IDX = 0;
+ private final static int LOGGER_METHOD_IDX = 1;
+ private Object[] m_logger = null;
+
+ public Logger(int i)
+ {
+ m_logLevel = i;
+ }
+
+ public final synchronized void setLogLevel(int i)
+ {
+ m_logLevel = i;
+ }
+
+ public final synchronized int getLogLevel()
+ {
+ return m_logLevel;
+ }
+
+ public final void log(int level, String msg)
+ {
+ _log(level, msg, null);
+ }
+
+ public final void log(int level, String msg, Throwable throwable)
+ {
+ _log(level, msg, throwable);
+ }
+
+ protected void doLog(int level, String msg, Throwable throwable)
+ {
+ String s = "";
+ s = s + msg;
+ if (throwable != null)
+ {
+ s = s + " (" + throwable + ")";
+ }
+ switch (level)
+ {
+ case LOG_DEBUG:
+ System.out.println("DEBUG: " + s);
+ break;
+ case LOG_ERROR:
+ System.out.println("ERROR: " + s);
+ if (throwable != null)
+ {
+ throwable.printStackTrace();
+ }
+ break;
+ case LOG_INFO:
+ System.out.println("INFO: " + s);
+ break;
+ case LOG_WARNING:
+ System.out.println("WARNING: " + s);
+ break;
+ default:
+ System.out.println("UNKNOWN[" + level + "]: " + s);
+ }
+ }
+
+ private void _log(
+ int level,
+ String msg, Throwable throwable)
+ {
+ // Save our own copy just in case it changes. We could try to do
+ // more conservative locking here, but let's be optimistic.
+ Object[] logger = m_logger;
+
+ if (m_logLevel >= level)
+ {
+ doLog(level, msg, throwable);
+ }
+ }
+}
diff --git a/bundles/org.eclipse.osgi/resolver/src/org/apache/felix/resolver/ResolverImpl.java b/bundles/org.eclipse.osgi/resolver/src/org/apache/felix/resolver/ResolverImpl.java
new file mode 100644
index 000000000..754d2865d
--- /dev/null
+++ b/bundles/org.eclipse.osgi/resolver/src/org/apache/felix/resolver/ResolverImpl.java
@@ -0,0 +1,1883 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+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.StringTokenizer;
+import org.osgi.framework.namespace.BundleNamespace;
+import org.osgi.framework.namespace.ExecutionEnvironmentNamespace;
+import org.osgi.framework.namespace.HostNamespace;
+import org.osgi.framework.namespace.PackageNamespace;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Namespace;
+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 ResolverImpl implements Resolver
+{
+ private final Logger m_logger;
+
+ // Holds candidate permutations based on permutating "uses" chains.
+ // These permutations are given higher priority.
+ private final List<Candidates> m_usesPermutations = new ArrayList<Candidates>();
+ // Holds candidate permutations based on permutating requirement candidates.
+ // These permutations represent backtracking on previous decisions.
+ private final List<Candidates> m_importPermutations = new ArrayList<Candidates>();
+
+ public ResolverImpl(Logger logger)
+ {
+ m_logger = logger;
+ }
+
+ public Map<Resource, List<Wire>> resolve(ResolveContext rc) throws ResolutionException
+ {
+ Map<Resource, List<Wire>> wireMap =
+ new HashMap<Resource, List<Wire>>();
+ Map<Resource, Packages> resourcePkgMap =
+ new HashMap<Resource, Packages>();
+
+ // Make copies of arguments in case we want to modify them.
+ Collection<Resource> mandatoryResources = new ArrayList(rc.getMandatoryResources());
+ Collection<Resource> optionalResources = new ArrayList(rc.getOptionalResources());
+// TODO: RFC-112 - Need impl-specific type.
+// Collection<Resource> ondemandFragments = (rc instanceof ResolveContextImpl)
+// ? ((ResolveContextImpl) rc).getOndemandResources() : Collections.EMPTY_LIST;
+ Collection<Resource> ondemandFragments = Collections.EMPTY_LIST;
+
+ boolean retry;
+ do
+ {
+ retry = false;
+
+ try
+ {
+ // Create object to hold all candidates.
+ Candidates allCandidates = new Candidates();
+
+ // Populate mandatory resources; since these are mandatory
+ // resources, failure throws a resolve exception.
+ for (Iterator<Resource> it = mandatoryResources.iterator();
+ it.hasNext(); )
+ {
+ Resource resource = it.next();
+ if (Util.isFragment(resource) || (rc.getWirings().get(resource) == null))
+ {
+ allCandidates.populate(rc, resource, Candidates.MANDATORY);
+ }
+ else
+ {
+ it.remove();
+ }
+ }
+
+ // Populate optional resources; since these are optional
+ // resources, failure does not throw a resolve exception.
+ for (Resource resource : optionalResources)
+ {
+ boolean isFragment = Util.isFragment(resource);
+ if (isFragment || (rc.getWirings().get(resource) == null))
+ {
+ allCandidates.populate(rc, resource, Candidates.OPTIONAL);
+ }
+ }
+
+ // Populate ondemand fragments; since these are optional
+ // resources, failure does not throw a resolve exception.
+ for (Resource resource : ondemandFragments)
+ {
+ boolean isFragment = Util.isFragment(resource);
+ if (isFragment)
+ {
+ allCandidates.populate(rc, resource, Candidates.ON_DEMAND);
+ }
+ }
+
+ // Merge any fragments into hosts.
+ allCandidates.prepare(rc);
+
+ // Create a combined list of populated resources; for
+ // optional resources. We do not need to consider ondemand
+ // fragments, since they will only be pulled in if their
+ // host is already present.
+ Set<Resource> allResources =
+ new HashSet<Resource>(mandatoryResources);
+ for (Resource resource : optionalResources)
+ {
+ if (allCandidates.isPopulated(resource))
+ {
+ allResources.add(resource);
+ }
+ }
+
+ // Record the initial candidate permutation.
+ m_usesPermutations.add(allCandidates);
+
+ ResolutionException rethrow = null;
+
+ // If a populated resource is a fragment, then its host
+ // must ultimately be verified, so store its host requirement
+ // to use for package space calculation.
+ Map<Resource, List<Requirement>> hostReqs =
+ new HashMap<Resource, List<Requirement>>();
+ for (Resource resource : allResources)
+ {
+ if (Util.isFragment(resource))
+ {
+ hostReqs.put(
+ resource,
+ resource.getRequirements(HostNamespace.HOST_NAMESPACE));
+ }
+ }
+
+ do
+ {
+ rethrow = null;
+
+ resourcePkgMap.clear();
+ m_packageSourcesCache.clear();
+
+ allCandidates = (m_usesPermutations.size() > 0)
+ ? m_usesPermutations.remove(0)
+ : m_importPermutations.remove(0);
+//allCandidates.dump();
+
+ for (Resource resource : allResources)
+ {
+ Resource target = resource;
+
+ // If we are resolving a fragment, then get its
+ // host candidate and verify it instead.
+ List<Requirement> hostReq = hostReqs.get(resource);
+ if (hostReq != null)
+ {
+ target = allCandidates.getCandidates(hostReq.get(0))
+ .iterator().next().getResource();
+ }
+
+ calculatePackageSpaces(
+ rc, allCandidates.getWrappedHost(target), allCandidates,
+ resourcePkgMap, new HashMap(), new HashSet());
+//System.out.println("+++ PACKAGE SPACES START +++");
+//dumpResourcePkgMap(resourcePkgMap);
+//System.out.println("+++ PACKAGE SPACES END +++");
+
+ try
+ {
+ checkPackageSpaceConsistency(
+ rc, false, allCandidates.getWrappedHost(target),
+ allCandidates, resourcePkgMap, new HashMap());
+ }
+ catch (ResolutionException ex)
+ {
+ rethrow = ex;
+ }
+ }
+ }
+ while ((rethrow != null)
+ && ((m_usesPermutations.size() > 0) || (m_importPermutations.size() > 0)));
+
+ // If there is a resolve exception, then determine if an
+ // optionally resolved resource is to blame (typically a fragment).
+ // If so, then remove the optionally resolved resolved and try
+ // again; otherwise, rethrow the resolve exception.
+ if (rethrow != null)
+ {
+ Collection<Requirement> exReqs = rethrow.getUnresolvedRequirements();
+ Requirement faultyReq = ((exReqs == null) || (exReqs.isEmpty()))
+ ? null : exReqs.iterator().next();
+ Resource faultyResource = (faultyReq == null)
+ ? null : getDeclaredResource(faultyReq.getResource());
+ // If the faulty requirement is wrapped, then it may
+ // be from a fragment, so consider the fragment faulty
+ // instead of the host.
+ if (faultyReq instanceof WrappedRequirement)
+ {
+ faultyResource =
+ ((WrappedRequirement) faultyReq)
+ .getDeclaredRequirement().getResource();
+ }
+ // Try to ignore the faulty resource if it is not mandatory.
+ if (optionalResources.remove(faultyResource))
+ {
+ retry = true;
+ }
+ else if (ondemandFragments.remove(faultyResource))
+ {
+ retry = true;
+ }
+ else
+ {
+ throw rethrow;
+ }
+ }
+ // If there is no exception to rethrow, then this was a clean
+ // resolve, so populate the wire map.
+ else
+ {
+ for (Resource resource : allResources)
+ {
+ Resource target = resource;
+
+ // If we are resolving a fragment, then we
+ // actually want to populate its host's wires.
+ List<Requirement> hostReq = hostReqs.get(resource);
+ if (hostReq != null)
+ {
+ target = allCandidates.getCandidates(hostReq.get(0))
+ .iterator().next().getResource();
+ }
+
+ if (allCandidates.isPopulated(target))
+ {
+ wireMap =
+ populateWireMap(
+ rc, allCandidates.getWrappedHost(target),
+ resourcePkgMap, wireMap, allCandidates);
+ }
+ }
+ }
+ }
+ finally
+ {
+ // Always clear the state.
+ m_usesPermutations.clear();
+ m_importPermutations.clear();
+ }
+ }
+ while (retry);
+
+ return wireMap;
+ }
+
+ /**
+ * Resolves a dynamic requirement for the specified host resource using the specified
+ * {@link ResolveContext}. The dynamic requirement may contain wild cards in its filter
+ * for the package name. The matching candidates are used to resolve the requirement and
+ * the resolve context is not asked to find providers for the dynamic requirement.
+ * The host resource is expected to not be a fragment, to already be resolved and
+ * have an existing wiring provided by the resolve context.
+ * <p>
+ * This operation may resolve additional resources in order to resolve the dynamic
+ * requirement. The returned map will contain entries for each resource that got resolved
+ * in addition to the specified host resource. The wire list for the host resource
+ * will only contain a single wire which is for the dynamic requirement.
+ * @param rc the resolve context
+ * @param host the hosting resource
+ * @param dynamicReq the dynamic requirement
+ * @param matching a list of matching capabilities
+ * @param ondemandFragments collection of on demand fragments that will attach to any host that is a candidate
+ * @return The new resources and wires required to satisfy the specified
+ * dynamic requirement. The returned map is the property of the caller
+ * and can be modified by the caller.
+ * @throws ResolutionException
+ */
+ public Map<Resource, List<Wire>> resolve(ResolveContext rc, Resource host, Requirement dynamicReq, List<Capability> matching, Collection<Resource> ondemandFragments) throws ResolutionException {
+ Map<Resource, List<Wire>> wireMap = new HashMap<Resource, List<Wire>>();
+
+ // We can only create a dynamic import if the following
+ // conditions are met:
+ // 1. The specified resource is resolved.
+ // 2. The package in question is not already imported.
+ // 3. The package in question is not accessible via require-bundle.
+ // 4. The package in question is not exported by the resource.
+ // 5. The package in question matches a dynamic import of the resource.
+ Candidates allCandidates = getDynamicImportCandidates(rc, host, dynamicReq, matching);
+ if (allCandidates != null)
+ {
+ Map<Resource, Packages> resourcePkgMap = new HashMap<Resource, Packages>();
+
+
+ ondemandFragments = new ArrayList<Resource>(ondemandFragments);
+
+ boolean retry;
+ do
+ {
+ retry = false;
+
+ try
+ {
+ // Try to populate optional fragments.
+ for (Resource r : ondemandFragments)
+ {
+ if (Util.isFragment(r))
+ {
+ allCandidates.populate(rc, r, Candidates.ON_DEMAND);
+ }
+ }
+
+ // Merge any fragments into hosts.
+ allCandidates.prepare(rc);
+
+ // Record the initial candidate permutation.
+ m_usesPermutations.add(allCandidates);
+
+ ResolutionException rethrow = null;
+
+ do
+ {
+ rethrow = null;
+
+ resourcePkgMap.clear();
+ m_packageSourcesCache.clear();
+
+ allCandidates = (m_usesPermutations.size() > 0)
+ ? m_usesPermutations.remove(0)
+ : m_importPermutations.remove(0);
+ //allCandidates.dump();
+
+ // For a dynamic import, the instigating resource
+ // will never be a fragment since fragments never
+ // execute code, so we don't need to check for
+ // this case like we do for a normal resolve.
+
+ calculatePackageSpaces(rc,
+ allCandidates.getWrappedHost(host), allCandidates, resourcePkgMap,
+ new HashMap(), new HashSet());
+ //System.out.println("+++ PACKAGE SPACES START +++");
+ //dumpResourcePkgMap(resourcePkgMap);
+ //System.out.println("+++ PACKAGE SPACES END +++");
+
+ try
+ {
+ checkPackageSpaceConsistency(rc,
+ true, allCandidates.getWrappedHost(host),
+ allCandidates, resourcePkgMap, new HashMap());
+ }
+ catch (ResolutionException ex)
+ {
+ rethrow = ex;
+ }
+ }
+ while ((rethrow != null)
+ && ((m_usesPermutations.size() > 0) || (m_importPermutations.size() > 0)));
+
+ // If there is a resolve exception, then determine if an
+ // optionally resolved resource is to blame (typically a fragment).
+ // If so, then remove the optionally resolved resource and try
+ // again; otherwise, rethrow the resolve exception.
+ if (rethrow != null)
+ {
+ Collection<Requirement> exReqs = rethrow.getUnresolvedRequirements();
+ Requirement faultyReq = ((exReqs == null) || (exReqs.isEmpty()))
+ ? null : exReqs.iterator().next();
+ Resource faultyResource = (faultyReq == null)
+ ? null : getDeclaredResource(faultyReq.getResource());
+ // If the faulty requirement is wrapped, then it may
+ // be from a fragment, so consider the fragment faulty
+ // instead of the host.
+ if (faultyReq instanceof WrappedRequirement)
+ {
+ faultyResource =
+ ((WrappedRequirement) faultyReq)
+ .getDeclaredRequirement().getResource();
+ }
+ // Try to ignore the faulty resource if it is not mandatory.
+ if (ondemandFragments.remove(faultyResource))
+ {
+ retry = true;
+ }
+ else
+ {
+ throw rethrow;
+ }
+ }
+ // If there is no exception to rethrow, then this was a clean
+ // resolve, so populate the wire map.
+ else
+ {
+ wireMap = populateDynamicWireMap(rc,
+ host, dynamicReq, resourcePkgMap, wireMap, allCandidates);
+ return wireMap;
+ }
+ }
+ finally
+ {
+ // Always clear the state.
+ m_usesPermutations.clear();
+ m_importPermutations.clear();
+ }
+ }
+ while (retry);
+ }
+
+ return wireMap;
+ }
+
+ private static Candidates getDynamicImportCandidates(
+ ResolveContext rc, Resource host, Requirement dynamicReq, List<Capability> matching) throws ResolutionException
+ {
+ if (matching.isEmpty())
+ {
+ return null;
+ }
+
+ Wiring resolvedWiring = rc.getWirings().get(host);
+ // Unresolved resources cannot dynamically import
+ if (resolvedWiring == null)
+ {
+ return null;
+ }
+
+ // Assume the first capability has the osgi.wiring.package attribute
+ Object pkgName = matching.get(0).getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE);
+ if (pkgName == null)
+ {
+ throw new IllegalArgumentException("Matching candidate does not provide a package name.");
+ }
+
+ // If the resource exports this package, then we cannot
+ // attempt to dynamically import it.
+ for (Capability cap : resolvedWiring.getResourceCapabilities(null))
+ {
+ if (cap.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE)
+ && cap.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE).equals(pkgName))
+ {
+ return null;
+ }
+ }
+
+ // If this resource already imports or requires this package, then
+ // we cannot dynamically import it.
+ // TODO perhaps this should be done outside the resolver before calling resolve?
+// if (((WiringImpl) resource.getWiring()).hasPackageSource(pkgName))
+// {
+// return null;
+// }
+
+ Candidates allCandidates = new Candidates();
+ allCandidates.populateDynamic(rc, host, dynamicReq, matching);
+
+ return allCandidates;
+ }
+
+ private void calculatePackageSpaces(
+ ResolveContext rc,
+ Resource resource,
+ Candidates allCandidates,
+ Map<Resource, Packages> resourcePkgMap,
+ Map<Capability, List<Resource>> usesCycleMap,
+ Set<Resource> cycle)
+ {
+ if (cycle.contains(resource))
+ {
+ return;
+ }
+ cycle.add(resource);
+
+ // Make sure package space hasn't already been calculated.
+ Packages resourcePkgs = resourcePkgMap.get(resource);
+ if (resourcePkgs != null)
+ {
+ if (resourcePkgs.m_isCalculated)
+ {
+ return;
+ }
+ else
+ {
+ resourcePkgs.m_isCalculated = true;
+ }
+ }
+
+ // Create parallel arrays for requirement and proposed candidate
+ // capability or actual capability if resource is resolved or not.
+ List<Requirement> reqs = new ArrayList();
+ List<Capability> caps = new ArrayList();
+ boolean isDynamicImporting = false;
+ Wiring wiring = rc.getWirings().get(resource);
+ if (wiring != null)
+ {
+ // Use wires to get actual requirements and satisfying capabilities.
+ for (Wire wire : wiring.getRequiredResourceWires(null))
+ {
+ // Wrap the requirement as a hosted requirement if it comes
+ // from a fragment, since we will need to know the host. We
+ // also need to wrap if the requirement is a dynamic import,
+ // since that requirement will be shared with any other
+ // matching dynamic imports.
+ Requirement r = wire.getRequirement();
+ if (!r.getResource().equals(wire.getRequirer())
+ || ((r.getDirectives()
+ .get(PackageNamespace.REQUIREMENT_RESOLUTION_DIRECTIVE) != null)
+ && r.getDirectives()
+ .get(PackageNamespace.REQUIREMENT_RESOLUTION_DIRECTIVE)
+ .equals(PackageNamespace.RESOLUTION_DYNAMIC)))
+ {
+ r = new WrappedRequirement(wire.getRequirer(), r);
+ }
+ // Wrap the capability as a hosted capability if it comes
+ // from a fragment, since we will need to know the host.
+ Capability c = wire.getCapability();
+ if (!c.getResource().equals(wire.getProvider()))
+ {
+ c = new WrappedCapability(wire.getProvider(), c);
+ }
+ reqs.add(r);
+ caps.add(c);
+ }
+
+ // Since the resource is resolved, it could be dynamically importing,
+ // so check to see if there are candidates for any of its dynamic
+ // imports.
+ for (Requirement req
+ : Util.getDynamicRequirements(wiring.getResourceRequirements(null)))
+ {
+ // Get the candidates for the current requirement.
+ List<Capability> candCaps = allCandidates.getCandidates(req);
+ // Optional requirements may not have any candidates.
+ if (candCaps == null)
+ {
+ continue;
+ }
+
+ // Grab first (i.e., highest priority) candidate.
+ Capability cap = candCaps.get(0);
+ reqs.add(req);
+ caps.add(cap);
+ isDynamicImporting = true;
+ // Can only dynamically import one at a time, so break
+ // out of the loop after the first.
+ break;
+ }
+ }
+ else
+ {
+ for (Requirement req : resource.getRequirements(null))
+ {
+ String resolution = req.getDirectives()
+ .get(PackageNamespace.REQUIREMENT_RESOLUTION_DIRECTIVE);
+ if ((resolution == null)
+ || !resolution.equals(PackageNamespace.RESOLUTION_DYNAMIC))
+ {
+ // Get the candidates for the current requirement.
+ List<Capability> candCaps = allCandidates.getCandidates(req);
+ // Optional requirements may not have any candidates.
+ if (candCaps == null)
+ {
+ continue;
+ }
+
+ // Grab first (i.e., highest priority) candidate.
+ Capability cap = candCaps.get(0);
+ reqs.add(req);
+ caps.add(cap);
+ }
+ }
+ }
+
+ // First, add all exported packages to the target resource's package space.
+ calculateExportedPackages(rc, resource, allCandidates, resourcePkgMap);
+ resourcePkgs = resourcePkgMap.get(resource);
+
+ // Second, add all imported packages to the target resource's package space.
+ for (int i = 0; i < reqs.size(); i++)
+ {
+ Requirement req = reqs.get(i);
+ Capability cap = caps.get(i);
+ calculateExportedPackages(rc, cap.getResource(), allCandidates, resourcePkgMap);
+ mergeCandidatePackages(
+ rc, resource, req, cap, resourcePkgMap, allCandidates,
+ new HashMap<Resource, List<Capability>>());
+ }
+
+ // Third, have all candidates to calculate their package spaces.
+ for (int i = 0; i < caps.size(); i++)
+ {
+ calculatePackageSpaces(
+ rc, caps.get(i).getResource(), allCandidates, resourcePkgMap,
+ usesCycleMap, cycle);
+ }
+
+ // Fourth, if the target resource is unresolved or is dynamically importing,
+ // then add all the uses constraints implied by its imported and required
+ // packages to its package space.
+ // NOTE: We do not need to do this for resolved resources because their
+ // package space is consistent by definition and these uses constraints
+ // are only needed to verify the consistency of a resolving resource. The
+ // only exception is if a resolved resource is dynamically importing, then
+ // we need to calculate its uses constraints again to make sure the new
+ // import is consistent with the existing package space.
+ if ((wiring == null) || isDynamicImporting)
+ {
+ // Merge uses constraints from required capabilities.
+ for (int i = 0; i < reqs.size(); i++)
+ {
+ Requirement req = reqs.get(i);
+ Capability cap = caps.get(i);
+ // Ignore bundle/package requirements, since they are
+ // considered below.
+ if (!req.getNamespace().equals(BundleNamespace.BUNDLE_NAMESPACE)
+ && !req.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE))
+ {
+ List<Requirement> blameReqs = new ArrayList<Requirement>();
+ blameReqs.add(req);
+
+ mergeUses(
+ rc,
+ resource,
+ resourcePkgs,
+ cap,
+ blameReqs,
+ resourcePkgMap,
+ allCandidates,
+ usesCycleMap);
+ }
+ }
+ // Merge uses constraints from imported packages.
+ for (Entry<String, List<Blame>> entry : resourcePkgs.m_importedPkgs.entrySet())
+ {
+ for (Blame blame : entry.getValue())
+ {
+ // Ignore resources that import from themselves.
+ if (!blame.m_cap.getResource().equals(resource))
+ {
+ List<Requirement> blameReqs = new ArrayList();
+ blameReqs.add(blame.m_reqs.get(0));
+
+ mergeUses(
+ rc,
+ resource,
+ resourcePkgs,
+ blame.m_cap,
+ blameReqs,
+ resourcePkgMap,
+ allCandidates,
+ usesCycleMap);
+ }
+ }
+ }
+ // Merge uses constraints from required bundles.
+ for (Entry<String, List<Blame>> entry : resourcePkgs.m_requiredPkgs.entrySet())
+ {
+ for (Blame blame : entry.getValue())
+ {
+ List<Requirement> blameReqs = new ArrayList();
+ blameReqs.add(blame.m_reqs.get(0));
+
+ mergeUses(
+ rc,
+ resource,
+ resourcePkgs,
+ blame.m_cap,
+ blameReqs,
+ resourcePkgMap,
+ allCandidates,
+ usesCycleMap);
+ }
+ }
+ }
+ }
+
+ private void mergeCandidatePackages(
+ ResolveContext rc, Resource current, Requirement currentReq,
+ Capability candCap, Map<Resource, Packages> resourcePkgMap,
+ Candidates allCandidates, Map<Resource, List<Capability>> cycles)
+ {
+ List<Capability> cycleCaps = cycles.get(current);
+ if (cycleCaps == null)
+ {
+ cycleCaps = new ArrayList<Capability>();
+ cycles.put(current, cycleCaps);
+ }
+ if (cycleCaps.contains(candCap))
+ {
+ return;
+ }
+ cycleCaps.add(candCap);
+
+ if (candCap.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE))
+ {
+ mergeCandidatePackage(
+ current, false, currentReq, candCap, resourcePkgMap);
+ }
+ else if (candCap.getNamespace().equals(BundleNamespace.BUNDLE_NAMESPACE))
+ {
+// TODO: FELIX3 - THIS NEXT LINE IS A HACK. IMPROVE HOW/WHEN WE CALCULATE EXPORTS.
+ calculateExportedPackages(
+ rc, candCap.getResource(), allCandidates, resourcePkgMap);
+
+ // Get the candidate's package space to determine which packages
+ // will be visible to the current resource.
+ Packages candPkgs = resourcePkgMap.get(candCap.getResource());
+
+ // We have to merge all exported packages from the candidate,
+ // since the current resource requires it.
+ for (Entry<String, Blame> entry : candPkgs.m_exportedPkgs.entrySet())
+ {
+ mergeCandidatePackage(
+ current,
+ true,
+ currentReq,
+ entry.getValue().m_cap,
+ resourcePkgMap);
+ }
+
+ // If the candidate requires any other bundles with reexport visibility,
+ // then we also need to merge their packages too.
+ Wiring candWiring = rc.getWirings().get(candCap.getResource());
+ if (candWiring != null)
+ {
+ for (Wire w : candWiring.getRequiredResourceWires(null))
+ {
+ if (w.getRequirement().getNamespace()
+ .equals(BundleNamespace.BUNDLE_NAMESPACE))
+ {
+ String value = w.getRequirement()
+ .getDirectives()
+ .get(BundleNamespace.REQUIREMENT_VISIBILITY_DIRECTIVE);
+ if ((value != null)
+ && value.equals(BundleNamespace.VISIBILITY_REEXPORT))
+ {
+ mergeCandidatePackages(
+ rc,
+ current,
+ currentReq,
+ w.getCapability(),
+ resourcePkgMap,
+ allCandidates,
+ cycles);
+ }
+ }
+ }
+ }
+ else
+ {
+ for (Requirement req : candCap.getResource().getRequirements(null))
+ {
+ if (req.getNamespace().equals(BundleNamespace.BUNDLE_NAMESPACE))
+ {
+ String value =
+ req.getDirectives()
+ .get(BundleNamespace.REQUIREMENT_VISIBILITY_DIRECTIVE);
+ if ((value != null)
+ && value.equals(BundleNamespace.VISIBILITY_REEXPORT)
+ && (allCandidates.getCandidates(req) != null))
+ {
+ mergeCandidatePackages(
+ rc,
+ current,
+ currentReq,
+ allCandidates.getCandidates(req).iterator().next(),
+ resourcePkgMap,
+ allCandidates,
+ cycles);
+ }
+ }
+ }
+ }
+ }
+
+ cycles.remove(current);
+ }
+
+ private void mergeCandidatePackage(
+ Resource current, boolean requires,
+ Requirement currentReq, Capability candCap,
+ Map<Resource, Packages> resourcePkgMap)
+ {
+ if (candCap.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE))
+ {
+ // Merge the candidate capability into the resource's package space
+ // for imported or required packages, appropriately.
+
+ String pkgName = (String)
+ candCap.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE);
+
+ List blameReqs = new ArrayList();
+ blameReqs.add(currentReq);
+
+ Packages currentPkgs = resourcePkgMap.get(current);
+
+ Map<String, List<Blame>> packages = (requires)
+ ? currentPkgs.m_requiredPkgs
+ : currentPkgs.m_importedPkgs;
+ List<Blame> blames = packages.get(pkgName);
+ if (blames == null)
+ {
+ blames = new ArrayList<Blame>();
+ packages.put(pkgName, blames);
+ }
+ blames.add(new Blame(candCap, blameReqs));
+
+//dumpResourcePkgs(current, currentPkgs);
+ }
+ }
+
+ private void mergeUses(
+ ResolveContext rc, Resource current, Packages currentPkgs,
+ Capability mergeCap, List<Requirement> blameReqs,
+ Map<Resource, Packages> resourcePkgMap,
+ Candidates allCandidates,
+ Map<Capability, List<Resource>> cycleMap)
+ {
+ // If there are no uses, then just return.
+ // If the candidate resource is the same as the current resource,
+ // then we don't need to verify and merge the uses constraints
+ // since this will happen as we build up the package space.
+ if (current.equals(mergeCap.getResource()))
+ {
+ return;
+ }
+
+ // Check for cycles.
+ List<Resource> list = cycleMap.get(mergeCap);
+ if ((list != null) && list.contains(current))
+ {
+ return;
+ }
+ list = (list == null) ? new ArrayList<Resource>() : list;
+ list.add(current);
+ cycleMap.put(mergeCap, list);
+
+ for (Capability candSourceCap : getPackageSources(rc, mergeCap, resourcePkgMap))
+ {
+ List<String> uses;
+// TODO: RFC-112 - Need impl-specific type
+// if (candSourceCap instanceof FelixCapability)
+// {
+// uses = ((FelixCapability) candSourceCap).getUses();
+// }
+// else
+ {
+ uses = Collections.EMPTY_LIST;
+ String s = candSourceCap.getDirectives()
+ .get(Namespace.CAPABILITY_USES_DIRECTIVE);
+ if (s != null)
+ {
+ // Parse these uses directive.
+ StringTokenizer tok = new StringTokenizer(s, ",");
+ uses = new ArrayList(tok.countTokens());
+ while (tok.hasMoreTokens())
+ {
+ uses.add(tok.nextToken().trim());
+ }
+ }
+ }
+ for (String usedPkgName : uses)
+ {
+ Packages candSourcePkgs = resourcePkgMap.get(candSourceCap.getResource());
+ List<Blame> candSourceBlames;
+ // Check to see if the used package is exported.
+ Blame candExportedBlame = candSourcePkgs.m_exportedPkgs.get(usedPkgName);
+ if (candExportedBlame != null)
+ {
+ candSourceBlames = new ArrayList(1);
+ candSourceBlames.add(candExportedBlame);
+ }
+ else
+ {
+ // If the used package is not exported, check to see if it
+ // is required.
+ candSourceBlames = candSourcePkgs.m_requiredPkgs.get(usedPkgName);
+ // Lastly, if the used package is not required, check to see if it
+ // is imported.
+ candSourceBlames = (candSourceBlames != null)
+ ? candSourceBlames : candSourcePkgs.m_importedPkgs.get(usedPkgName);
+ }
+
+ // If the used package cannot be found, then just ignore it
+ // since it has no impact.
+ if (candSourceBlames == null)
+ {
+ continue;
+ }
+
+ List<Blame> usedCaps = currentPkgs.m_usedPkgs.get(usedPkgName);
+ if (usedCaps == null)
+ {
+ usedCaps = new ArrayList<Blame>();
+ currentPkgs.m_usedPkgs.put(usedPkgName, usedCaps);
+ }
+ for (Blame blame : candSourceBlames)
+ {
+ if (blame.m_reqs != null)
+ {
+ List<Requirement> blameReqs2 = new ArrayList(blameReqs);
+ blameReqs2.add(blame.m_reqs.get(blame.m_reqs.size() - 1));
+ usedCaps.add(new Blame(blame.m_cap, blameReqs2));
+ mergeUses(rc, current, currentPkgs, blame.m_cap, blameReqs2,
+ resourcePkgMap, allCandidates, cycleMap);
+ }
+ else
+ {
+ usedCaps.add(new Blame(blame.m_cap, blameReqs));
+ mergeUses(rc, current, currentPkgs, blame.m_cap, blameReqs,
+ resourcePkgMap, allCandidates, cycleMap);
+ }
+ }
+ }
+ }
+ }
+
+ private void checkPackageSpaceConsistency(
+ ResolveContext rc,
+ boolean isDynamicImporting,
+ Resource resource,
+ Candidates allCandidates,
+ Map<Resource, Packages> resourcePkgMap,
+ Map<Resource, Object> resultCache) throws ResolutionException
+ {
+ if (rc.getWirings().containsKey(resource) && !isDynamicImporting)
+ {
+ return;
+ }
+ else if(resultCache.containsKey(resource))
+ {
+ return;
+ }
+
+ Packages pkgs = resourcePkgMap.get(resource);
+
+ ResolutionException rethrow = null;
+ Candidates permutation = null;
+ Set<Requirement> mutated = null;
+
+ // Check for conflicting imports from fragments.
+ for (Entry<String, List<Blame>> entry : pkgs.m_importedPkgs.entrySet())
+ {
+ if (entry.getValue().size() > 1)
+ {
+ Blame sourceBlame = null;
+ for (Blame blame : entry.getValue())
+ {
+ if (sourceBlame == null)
+ {
+ sourceBlame = blame;
+ }
+ else if (!sourceBlame.m_cap.getResource().equals(blame.m_cap.getResource()))
+ {
+ // Try to permutate the conflicting requirement.
+ permutate(allCandidates, blame.m_reqs.get(0), m_importPermutations);
+ // Try to permutate the source requirement.
+ permutate(allCandidates, sourceBlame.m_reqs.get(0), m_importPermutations);
+ // Report conflict.
+ ResolutionException ex = new ResolutionException(
+ "Uses constraint violation. Unable to resolve resource "
+ + Util.getSymbolicName(resource)
+ + " [" + resource
+ + "] because it is exposed to package '"
+ + entry.getKey()
+ + "' from resources "
+ + Util.getSymbolicName(sourceBlame.m_cap.getResource())
+ + " [" + sourceBlame.m_cap.getResource()
+ + "] and "
+ + Util.getSymbolicName(blame.m_cap.getResource())
+ + " [" + blame.m_cap.getResource()
+ + "] via two dependency chains.\n\nChain 1:\n"
+ + toStringBlame(rc, allCandidates, sourceBlame)
+ + "\n\nChain 2:\n"
+ + toStringBlame(rc, allCandidates, blame),
+ null,
+ Collections.singleton(blame.m_reqs.get(0)));
+ m_logger.log(
+ Logger.LOG_DEBUG,
+ "Candidate permutation failed due to a conflict with a "
+ + "fragment import; will try another if possible.",
+ ex);
+ throw ex;
+ }
+ }
+ }
+ }
+
+ // Check if there are any uses conflicts with exported packages.
+ for (Entry<String, Blame> entry : pkgs.m_exportedPkgs.entrySet())
+ {
+ String pkgName = entry.getKey();
+ Blame exportBlame = entry.getValue();
+ if (!pkgs.m_usedPkgs.containsKey(pkgName))
+ {
+ continue;
+ }
+ for (Blame usedBlame : pkgs.m_usedPkgs.get(pkgName))
+ {
+ if (!isCompatible(rc, exportBlame.m_cap, usedBlame.m_cap, resourcePkgMap))
+ {
+ // Create a candidate permutation that eliminates all candidates
+ // that conflict with existing selected candidates.
+ permutation = (permutation != null)
+ ? permutation
+ : allCandidates.copy();
+ rethrow = (rethrow != null)
+ ? rethrow
+ : new ResolutionException(
+ "Uses constraint violation. Unable to resolve resource "
+ + Util.getSymbolicName(resource)
+ + " [" + resource
+ + "] because it exports package '"
+ + pkgName
+ + "' and is also exposed to it from resource "
+ + Util.getSymbolicName(usedBlame.m_cap.getResource())
+ + " [" + usedBlame.m_cap.getResource()
+ + "] via the following dependency chain:\n\n"
+ + toStringBlame(rc, allCandidates, usedBlame),
+ null,
+ null);
+
+ mutated = (mutated != null)
+ ? mutated
+ : new HashSet<Requirement>();
+
+ for (int reqIdx = usedBlame.m_reqs.size() - 1; reqIdx >= 0; reqIdx--)
+ {
+ Requirement req = usedBlame.m_reqs.get(reqIdx);
+
+ // If we've already permutated this requirement in another
+ // uses constraint, don't permutate it again just continue
+ // with the next uses constraint.
+ if (mutated.contains(req))
+ {
+ break;
+ }
+
+ // See if we can permutate the candidates for blamed
+ // requirement; there may be no candidates if the resource
+ // associated with the requirement is already resolved.
+ List<Capability> candidates = permutation.getCandidates(req);
+ if ((candidates != null) && (candidates.size() > 1))
+ {
+ mutated.add(req);
+ // Remove the conflicting candidate.
+ candidates.remove(0);
+ // Continue with the next uses constraint.
+ break;
+ }
+ }
+ }
+ }
+
+ if (rethrow != null)
+ {
+ if (mutated.size() > 0)
+ {
+ m_usesPermutations.add(permutation);
+ }
+ m_logger.log(
+ Logger.LOG_DEBUG,
+ "Candidate permutation failed due to a conflict between "
+ + "an export and import; will try another if possible.",
+ rethrow);
+ throw rethrow;
+ }
+ }
+
+ // Check if there are any uses conflicts with imported packages.
+ for (Entry<String, List<Blame>> entry : pkgs.m_importedPkgs.entrySet())
+ {
+ for (Blame importBlame : entry.getValue())
+ {
+ String pkgName = entry.getKey();
+ if (!pkgs.m_usedPkgs.containsKey(pkgName))
+ {
+ continue;
+ }
+ for (Blame usedBlame : pkgs.m_usedPkgs.get(pkgName))
+ {
+ if (!isCompatible(rc, importBlame.m_cap, usedBlame.m_cap, resourcePkgMap))
+ {
+ // Create a candidate permutation that eliminates any candidates
+ // that conflict with existing selected candidates.
+ permutation = (permutation != null)
+ ? permutation
+ : allCandidates.copy();
+ rethrow = (rethrow != null)
+ ? rethrow
+ : new ResolutionException(
+ "Uses constraint violation. Unable to resolve resource "
+ + Util.getSymbolicName(resource)
+ + " [" + resource
+ + "] because it is exposed to package '"
+ + pkgName
+ + "' from resources "
+ + Util.getSymbolicName(importBlame.m_cap.getResource())
+ + " [" + importBlame.m_cap.getResource()
+ + "] and "
+ + Util.getSymbolicName(usedBlame.m_cap.getResource())
+ + " [" + usedBlame.m_cap.getResource()
+ + "] via two dependency chains.\n\nChain 1:\n"
+ + toStringBlame(rc, allCandidates, importBlame)
+ + "\n\nChain 2:\n"
+ + toStringBlame(rc, allCandidates, usedBlame),
+ null,
+ null);
+
+ mutated = (mutated != null)
+ ? mutated
+ : new HashSet();
+
+ for (int reqIdx = usedBlame.m_reqs.size() - 1; reqIdx >= 0; reqIdx--)
+ {
+ Requirement req = usedBlame.m_reqs.get(reqIdx);
+
+ // If we've already permutated this requirement in another
+ // uses constraint, don't permutate it again just continue
+ // with the next uses constraint.
+ if (mutated.contains(req))
+ {
+ break;
+ }
+
+ // See if we can permutate the candidates for blamed
+ // requirement; there may be no candidates if the resource
+ // associated with the requirement is already resolved.
+ List<Capability> candidates = permutation.getCandidates(req);
+ if ((candidates != null) && (candidates.size() > 1))
+ {
+ mutated.add(req);
+ // Remove the conflicting candidate.
+ candidates.remove(0);
+ // Continue with the next uses constraint.
+ break;
+ }
+ }
+ }
+ }
+
+ // If there was a uses conflict, then we should add a uses
+ // permutation if we were able to permutate any candidates.
+ // Additionally, we should try to push an import permutation
+ // for the original import to force a backtracking on the
+ // original candidate decision if no viable candidate is found
+ // for the conflicting uses constraint.
+ if (rethrow != null)
+ {
+ // Add uses permutation if we mutated any candidates.
+ if (mutated.size() > 0)
+ {
+ m_usesPermutations.add(permutation);
+ }
+
+ // Try to permutate the candidate for the original
+ // import requirement; only permutate it if we haven't
+ // done so already.
+ Requirement req = importBlame.m_reqs.get(0);
+ if (!mutated.contains(req))
+ {
+ // Since there may be lots of uses constraint violations
+ // with existing import decisions, we may end up trying
+ // to permutate the same import a lot of times, so we should
+ // try to check if that the case and only permutate it once.
+ permutateIfNeeded(allCandidates, req, m_importPermutations);
+ }
+
+ m_logger.log(
+ Logger.LOG_DEBUG,
+ "Candidate permutation failed due to a conflict between "
+ + "imports; will try another if possible.",
+ rethrow);
+ throw rethrow;
+ }
+ }
+ }
+
+ resultCache.put(resource, Boolean.TRUE);
+
+ // Now check the consistency of all resources on which the
+ // current resource depends. Keep track of the current number
+ // of permutations so we know if the lower level check was
+ // able to create a permutation or not in the case of failure.
+ int permCount = m_usesPermutations.size() + m_importPermutations.size();
+ for (Entry<String, List<Blame>> entry : pkgs.m_importedPkgs.entrySet())
+ {
+ for (Blame importBlame : entry.getValue())
+ {
+ if (!resource.equals(importBlame.m_cap.getResource()))
+ {
+ try
+ {
+ checkPackageSpaceConsistency(
+ rc, false, importBlame.m_cap.getResource(),
+ allCandidates, resourcePkgMap, resultCache);
+ }
+ catch (ResolutionException ex)
+ {
+ // If the lower level check didn't create any permutations,
+ // then we should create an import permutation for the
+ // requirement with the dependency on the failing resource
+ // to backtrack on our current candidate selection.
+ if (permCount == (m_usesPermutations.size() + m_importPermutations.size()))
+ {
+ Requirement req = importBlame.m_reqs.get(0);
+ permutate(allCandidates, req, m_importPermutations);
+ }
+ throw ex;
+ }
+ }
+ }
+ }
+ }
+
+ private static void permutate(
+ Candidates allCandidates, Requirement req, List<Candidates> permutations)
+ {
+ List<Capability> candidates = allCandidates.getCandidates(req);
+ if (candidates != null && candidates.size() > 1)
+ {
+ Candidates perm = allCandidates.copy();
+ candidates = perm.getCandidates(req);
+ candidates.remove(0);
+ permutations.add(perm);
+ }
+ }
+
+ private static void permutateIfNeeded(
+ Candidates allCandidates, Requirement req, List<Candidates> permutations)
+ {
+ List<Capability> candidates = allCandidates.getCandidates(req);
+ if (candidates != null && candidates.size() > 1)
+ {
+ // Check existing permutations to make sure we haven't
+ // already permutated this requirement. This check for
+ // duplicate permutations is simplistic. It assumes if
+ // there is any permutation that contains a different
+ // initial candidate for the requirement in question,
+ // then it has already been permutated.
+ boolean permutated = false;
+ for (Candidates existingPerm : permutations)
+ {
+ List<Capability> existingPermCands = existingPerm.getCandidates(req);
+ if (!existingPermCands.get(0).equals(candidates.get(0)))
+ {
+ permutated = true;
+ }
+ }
+ // If we haven't already permutated the existing
+ // import, do so now.
+ if (!permutated)
+ {
+ permutate(allCandidates, req, permutations);
+ }
+ }
+ }
+
+ private static void calculateExportedPackages(
+ ResolveContext rc,
+ Resource resource,
+ Candidates allCandidates,
+ Map<Resource, Packages> resourcePkgMap)
+ {
+ Packages packages = resourcePkgMap.get(resource);
+ if (packages != null)
+ {
+ return;
+ }
+ packages = new Packages(resource);
+
+ // Get all exported packages.
+ Wiring wiring = rc.getWirings().get(resource);
+ List<Capability> caps = (wiring != null)
+ ? wiring.getResourceCapabilities(null)
+ : resource.getCapabilities(null);
+ Map<String, Capability> exports = new HashMap<String, Capability>(caps.size());
+ for (Capability cap : caps)
+ {
+ if (cap.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE))
+ {
+ if (!cap.getResource().equals(resource))
+ {
+ cap = new WrappedCapability(resource, cap);
+ }
+ exports.put(
+ (String) cap.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE),
+ cap);
+ }
+ }
+ // Remove substitutable exports that were imported.
+ // For resolved resources Wiring.getCapabilities()
+ // already excludes imported substitutable exports, but
+ // for resolving resources we must look in the candidate
+ // map to determine which exports are substitutable.
+ if (!exports.isEmpty())
+ {
+ if (wiring == null)
+ {
+ for (Requirement req : resource.getRequirements(null))
+ {
+ if (req.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE))
+ {
+ List<Capability> cands = allCandidates.getCandidates(req);
+ if ((cands != null) && !cands.isEmpty())
+ {
+ String pkgName = (String) cands.get(0)
+ .getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE);
+ exports.remove(pkgName);
+ }
+ }
+ }
+ }
+
+ // Add all non-substituted exports to the resources's package space.
+ for (Entry<String, Capability> entry : exports.entrySet())
+ {
+ packages.m_exportedPkgs.put(
+ entry.getKey(), new Blame(entry.getValue(), null));
+ }
+ }
+
+ resourcePkgMap.put(resource, packages);
+ }
+
+ private boolean isCompatible(
+ ResolveContext rc, Capability currentCap, Capability candCap,
+ Map<Resource, Packages> resourcePkgMap)
+ {
+ if ((currentCap != null) && (candCap != null))
+ {
+ if (currentCap.equals(candCap))
+ {
+ return true;
+ }
+
+ List<Capability> currentSources =
+ getPackageSources(
+ rc,
+ currentCap,
+ resourcePkgMap);
+ List<Capability> candSources =
+ getPackageSources(
+ rc,
+ candCap,
+ resourcePkgMap);
+
+ return currentSources.containsAll(candSources)
+ || candSources.containsAll(currentSources);
+ }
+ return true;
+ }
+
+ private Map<Capability, List<Capability>> m_packageSourcesCache = new HashMap();
+
+ private List<Capability> getPackageSources(
+ ResolveContext rc, Capability cap, Map<Resource, Packages> resourcePkgMap)
+ {
+ // If it is a package, then calculate sources for it.
+ if (cap.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE))
+ {
+ List<Capability> sources = m_packageSourcesCache.get(cap);
+ if (sources == null)
+ {
+ sources = getPackageSourcesInternal(
+ rc, cap, resourcePkgMap, new ArrayList(), new HashSet());
+ m_packageSourcesCache.put(cap, sources);
+ }
+ return sources;
+ }
+
+ // Otherwise, need to return generic capabilies that have
+ // uses constraints so they are included for consistency
+ // checking.
+ String uses = cap.getDirectives().get(Namespace.CAPABILITY_USES_DIRECTIVE);
+ if ((uses != null) && (uses.length() > 0))
+ {
+ return Collections.singletonList(cap);
+ }
+
+ return Collections.EMPTY_LIST;
+ }
+
+ private static List<Capability> getPackageSourcesInternal(
+ ResolveContext rc, Capability cap, Map<Resource, Packages> resourcePkgMap,
+ List<Capability> sources, Set<Capability> cycleMap)
+ {
+ if (cap.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE))
+ {
+ if (cycleMap.contains(cap))
+ {
+ return sources;
+ }
+ cycleMap.add(cap);
+
+ // Get the package name associated with the capability.
+ String pkgName = cap.getAttributes()
+ .get(PackageNamespace.PACKAGE_NAMESPACE).toString();
+
+ // Since a resource can export the same package more than once, get
+ // all package capabilities for the specified package name.
+ Wiring wiring = rc.getWirings().get(cap.getResource());
+ List<Capability> caps = (wiring != null)
+ ? wiring.getResourceCapabilities(null)
+ : cap.getResource().getCapabilities(null);
+ for (Capability sourceCap : caps)
+ {
+ if (sourceCap.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE)
+ && sourceCap.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE).equals(pkgName))
+ {
+ // Since capabilities may come from fragments, we need to check
+ // for that case and wrap them.
+ if (!cap.getResource().equals(sourceCap.getResource()))
+ {
+ sources.add(new WrappedCapability(cap.getResource(), sourceCap));
+ }
+ else
+ {
+ sources.add(sourceCap);
+ }
+ }
+ }
+
+ // Then get any addition sources for the package from required bundles.
+ Packages pkgs = resourcePkgMap.get(cap.getResource());
+ List<Blame> required = pkgs.m_requiredPkgs.get(pkgName);
+ if (required != null)
+ {
+ for (Blame blame : required)
+ {
+ getPackageSourcesInternal(rc, blame.m_cap, resourcePkgMap, sources, cycleMap);
+ }
+ }
+ }
+
+ return sources;
+ }
+
+ private static Resource getDeclaredResource(Resource resource)
+ {
+ if (resource instanceof WrappedResource)
+ {
+ return ((WrappedResource) resource).getDeclaredResource();
+ }
+ return resource;
+ }
+
+ private static Capability getDeclaredCapability(Capability c)
+ {
+ if (c instanceof HostedCapability)
+ {
+ return ((HostedCapability) c).getDeclaredCapability();
+ }
+ return c;
+ }
+
+ private static Requirement getDeclaredRequirement(Requirement r)
+ {
+ if (r instanceof WrappedRequirement)
+ {
+ return ((WrappedRequirement) r).getDeclaredRequirement();
+ }
+ return r;
+ }
+
+ private static Map<Resource, List<Wire>> populateWireMap(
+ ResolveContext rc, Resource resource, Map<Resource, Packages> resourcePkgMap,
+ Map<Resource, List<Wire>> wireMap, Candidates allCandidates)
+ {
+ Resource unwrappedResource = getDeclaredResource(resource);
+ if (!rc.getWirings().containsKey(unwrappedResource)
+ && !wireMap.containsKey(unwrappedResource))
+ {
+ wireMap.put(unwrappedResource, (List<Wire>) Collections.EMPTY_LIST);
+
+ List<Wire> packageWires = new ArrayList<Wire>();
+ List<Wire> bundleWires = new ArrayList<Wire>();
+ List<Wire> capabilityWires = new ArrayList<Wire>();
+
+ for (Requirement req : resource.getRequirements(null))
+ {
+ List<Capability> cands = allCandidates.getCandidates(req);
+ if ((cands != null) && (cands.size() > 0))
+ {
+ Capability cand = cands.get(0);
+ // Do not create wires for the osgi.wiring.* namespaces
+ // if the provider and requirer are the same resource;
+ // allow such wires for non-OSGi wiring namespaces.
+ if (!cand.getNamespace().startsWith("osgi.wiring.")
+ || !resource.equals(cand.getResource()))
+ {
+ if (!rc.getWirings().containsKey(cand.getResource()))
+ {
+ populateWireMap(rc, cand.getResource(),
+ resourcePkgMap, wireMap, allCandidates);
+ }
+ Wire wire = new WireImpl(
+ unwrappedResource,
+ getDeclaredRequirement(req),
+ getDeclaredResource(cand.getResource()),
+ getDeclaredCapability(cand));
+ if (req.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE))
+ {
+ packageWires.add(wire);
+ }
+ else if (req.getNamespace().equals(BundleNamespace.BUNDLE_NAMESPACE))
+ {
+ bundleWires.add(wire);
+ }
+ else
+ {
+ capabilityWires.add(wire);
+ }
+ }
+ }
+ }
+
+ // Combine package wires with require wires last.
+ packageWires.addAll(bundleWires);
+ packageWires.addAll(capabilityWires);
+ wireMap.put(unwrappedResource, packageWires);
+
+ // Add host wire for any fragments.
+ if (resource instanceof WrappedResource)
+ {
+ List<Resource> fragments = ((WrappedResource) resource).getFragments();
+ for (Resource fragment : fragments)
+ {
+ // Get wire list for the fragment from the wire map.
+ // If there isn't one, then create one. Note that we won't
+ // add the wire list to the wire map until the end, so
+ // we can determine below if this is the first time we've
+ // seen the fragment while populating wires to avoid
+ // creating duplicate non-payload wires if the fragment
+ // is attached to more than one host.
+ List<Wire> fragmentWires = wireMap.get(fragment);
+ fragmentWires = (fragmentWires == null)
+ ? new ArrayList<Wire>() : fragmentWires;
+
+ // Loop through all of the fragment's requirements and create
+ // any necessary wires for non-payload requirements.
+ for (Requirement req : fragment.getRequirements(null))
+ {
+ // Only look at non-payload requirements.
+ if (!isPayload(req))
+ {
+ // If this is the host requirement, then always create
+ // a wire for it to the current resource.
+ if (req.getNamespace().equals(HostNamespace.HOST_NAMESPACE))
+ {
+ fragmentWires.add(
+ new WireImpl(
+ getDeclaredResource(fragment),
+ req,
+ unwrappedResource,
+ unwrappedResource.getCapabilities(
+ HostNamespace.HOST_NAMESPACE).get(0)));
+ }
+ // Otherwise, if the fragment isn't already resolved and
+ // this is the first time we are seeing it, then create
+ // a wire for the non-payload requirement.
+ else if (!rc.getWirings().containsKey(fragment)
+ && !wireMap.containsKey(fragment))
+ {
+ Wire wire = createWire(req, allCandidates);
+ if (wire != null)
+ {
+ fragmentWires.add(wire);
+ }
+ }
+ }
+ }
+
+ // Finally, add the fragment's wire list to the wire map.
+ wireMap.put(fragment, fragmentWires);
+ }
+ }
+ }
+
+ return wireMap;
+ }
+
+ private static Wire createWire(Requirement requirement, Candidates allCandidates)
+ {
+ List<Capability> candidates = allCandidates.getCandidates(requirement);
+ if (candidates == null || candidates.isEmpty())
+ {
+ return null;
+ }
+ Capability cand = candidates.get(0);
+ return new WireImpl(
+ getDeclaredResource(requirement.getResource()),
+ getDeclaredRequirement(requirement),
+ getDeclaredResource(cand.getResource()),
+ getDeclaredCapability(cand));
+ }
+
+ private static boolean isPayload(Requirement fragmentReq)
+ {
+ // this is where we would add other non-payload namespaces
+ if (ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE
+ .equals(fragmentReq.getNamespace()))
+ {
+ return false;
+ }
+ if (HostNamespace.HOST_NAMESPACE.equals(fragmentReq.getNamespace()))
+ {
+ return false;
+ }
+ return true;
+ }
+
+ private static Map<Resource, List<Wire>> populateDynamicWireMap(
+ ResolveContext rc, Resource resource, Requirement dynReq,
+ Map<Resource, Packages> resourcePkgMap,
+ Map<Resource, List<Wire>> wireMap, Candidates allCandidates)
+ {
+ wireMap.put(resource, (List<Wire>) Collections.EMPTY_LIST);
+
+ List<Wire> packageWires = new ArrayList<Wire>();
+
+ // Get the candidates for the current dynamic requirement.
+ List<Capability> candCaps = allCandidates.getCandidates(dynReq);
+ // Record the dynamic candidate.
+ Capability dynCand = candCaps.get(0);
+
+ if (!rc.getWirings().containsKey(dynCand.getResource()))
+ {
+ populateWireMap(rc, dynCand.getResource(), resourcePkgMap,
+ wireMap, allCandidates);
+ }
+
+ packageWires.add(
+ new WireImpl(
+ resource,
+ dynReq,
+ getDeclaredResource(dynCand.getResource()),
+ getDeclaredCapability(dynCand)));
+
+ wireMap.put(resource, packageWires);
+
+ return wireMap;
+ }
+
+ private static void dumpResourcePkgMap(
+ ResolveContext rc, Map<Resource, Packages> resourcePkgMap)
+ {
+ System.out.println("+++RESOURCE PKG MAP+++");
+ for (Entry<Resource, Packages> entry : resourcePkgMap.entrySet())
+ {
+ dumpResourcePkgs(rc, entry.getKey(), entry.getValue());
+ }
+ }
+
+ private static void dumpResourcePkgs(
+ ResolveContext rc, Resource resource, Packages packages)
+ {
+ Wiring wiring = rc.getWirings().get(resource);
+ System.out.println(resource
+ + " (" + ((wiring != null) ? "RESOLVED)" : "UNRESOLVED)"));
+ System.out.println(" EXPORTED");
+ for (Entry<String, Blame> entry : packages.m_exportedPkgs.entrySet())
+ {
+ System.out.println(" " + entry.getKey() + " - " + entry.getValue());
+ }
+ System.out.println(" IMPORTED");
+ for (Entry<String, List<Blame>> entry : packages.m_importedPkgs.entrySet())
+ {
+ System.out.println(" " + entry.getKey() + " - " + entry.getValue());
+ }
+ System.out.println(" REQUIRED");
+ for (Entry<String, List<Blame>> entry : packages.m_requiredPkgs.entrySet())
+ {
+ System.out.println(" " + entry.getKey() + " - " + entry.getValue());
+ }
+ System.out.println(" USED");
+ for (Entry<String, List<Blame>> entry : packages.m_usedPkgs.entrySet())
+ {
+ System.out.println(" " + entry.getKey() + " - " + entry.getValue());
+ }
+ }
+
+ private static String toStringBlame(
+ ResolveContext rc, Candidates allCandidates, Blame blame)
+ {
+ StringBuffer sb = new StringBuffer();
+ if ((blame.m_reqs != null) && !blame.m_reqs.isEmpty())
+ {
+ for (int i = 0; i < blame.m_reqs.size(); i++)
+ {
+ Requirement req = blame.m_reqs.get(i);
+ sb.append(" ");
+ sb.append(Util.getSymbolicName(req.getResource()));
+ sb.append(" [");
+ sb.append(req.getResource().toString());
+ sb.append("]\n");
+ if (req.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE))
+ {
+ sb.append(" import: ");
+ }
+ else
+ {
+ sb.append(" require: ");
+ }
+ sb.append(req.getDirectives().get(Namespace.REQUIREMENT_FILTER_DIRECTIVE));
+ sb.append("\n |");
+ if (req.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE))
+ {
+ sb.append("\n export: ");
+ }
+ else
+ {
+ sb.append("\n provide: ");
+ }
+ if ((i + 1) < blame.m_reqs.size())
+ {
+ Capability cap = getSatisfyingCapability(
+ rc,
+ allCandidates,
+ blame.m_reqs.get(i));
+ if (cap.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE))
+ {
+ sb.append(PackageNamespace.PACKAGE_NAMESPACE);
+ sb.append("=");
+ sb.append(cap.getAttributes()
+ .get(PackageNamespace.PACKAGE_NAMESPACE).toString());
+ Capability usedCap =
+ getSatisfyingCapability(
+ rc,
+ allCandidates,
+ blame.m_reqs.get(i + 1));
+ sb.append("; uses:=");
+ sb.append(usedCap.getAttributes()
+ .get(PackageNamespace.PACKAGE_NAMESPACE));
+ }
+ else
+ {
+ sb.append(cap);
+ }
+ sb.append("\n");
+ }
+ else
+ {
+ Capability export = getSatisfyingCapability(
+ rc,
+ allCandidates,
+ blame.m_reqs.get(i));
+ sb.append(export.getNamespace());
+ sb.append("=");
+ sb.append(export.getAttributes().get(export.getNamespace()).toString());
+ if (export.getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE)
+ && !export.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE)
+ .equals(blame.m_cap.getAttributes().get(
+ PackageNamespace.PACKAGE_NAMESPACE)))
+ {
+ sb.append("; uses:=");
+ sb.append(blame.m_cap.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE));
+ sb.append("\n export: ");
+ sb.append(PackageNamespace.PACKAGE_NAMESPACE);
+ sb.append("=");
+ sb.append(blame.m_cap.getAttributes()
+ .get(PackageNamespace.PACKAGE_NAMESPACE).toString());
+ }
+ sb.append("\n ");
+ sb.append(Util.getSymbolicName(blame.m_cap.getResource()));
+ sb.append(" [");
+ sb.append(blame.m_cap.getResource().toString());
+ sb.append("]");
+ }
+ }
+ }
+ else
+ {
+ sb.append(blame.m_cap.getResource().toString());
+ }
+ return sb.toString();
+ }
+
+ private static Capability getSatisfyingCapability(
+ ResolveContext rc, Candidates allCandidates, Requirement req)
+ {
+ Capability cap = null;
+
+ // If the requiring revision is not resolved, then check in the
+ // candidate map for its matching candidate.
+ List<Capability> cands = allCandidates.getCandidates(req);
+ if (cands != null)
+ {
+ cap = cands.get(0);
+ }
+ // Otherwise, if the requiring revision is resolved then check
+ // in its wires for the capability satisfying the requirement.
+ else if (rc.getWirings().containsKey(req.getResource()))
+ {
+ List<Wire> wires =
+ rc.getWirings().get(req.getResource()).getRequiredResourceWires(null);
+ req = getDeclaredRequirement(req);
+ for (Wire w : wires)
+ {
+ if (w.getRequirement().equals(req))
+ {
+// TODO: RESOLVER - This is not 100% correct, since requirements for
+// dynamic imports with wildcards will reside on many wires and
+// this code only finds the first one, not necessarily the correct
+// one. This is only used for the diagnostic message, but it still
+// could confuse the user.
+ cap = w.getCapability();
+ break;
+ }
+ }
+ }
+
+ return cap;
+ }
+
+ private static class Packages
+ {
+ private final Resource m_resource;
+ public final Map<String, Blame> m_exportedPkgs = new HashMap();
+ public final Map<String, List<Blame>> m_importedPkgs = new HashMap();
+ public final Map<String, List<Blame>> m_requiredPkgs = new HashMap();
+ public final Map<String, List<Blame>> m_usedPkgs = new HashMap();
+ public boolean m_isCalculated = false;
+
+ public Packages(Resource resource)
+ {
+ m_resource = resource;
+ }
+ }
+
+ private static class Blame
+ {
+ public final Capability m_cap;
+ public final List<Requirement> m_reqs;
+
+ public Blame(Capability cap, List<Requirement> reqs)
+ {
+ m_cap = cap;
+ m_reqs = reqs;
+ }
+
+ @Override
+ public String toString()
+ {
+ return m_cap.getResource()
+ + "." + m_cap.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE)
+ + (((m_reqs == null) || m_reqs.isEmpty())
+ ? " NO BLAME"
+ : " BLAMED ON " + m_reqs);
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ return (o instanceof Blame) && m_reqs.equals(((Blame) o).m_reqs)
+ && m_cap.equals(((Blame) o).m_cap);
+ }
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.osgi/resolver/src/org/apache/felix/resolver/ShadowList.java b/bundles/org.eclipse.osgi/resolver/src/org/apache/felix/resolver/ShadowList.java
new file mode 100644
index 000000000..05734da46
--- /dev/null
+++ b/bundles/org.eclipse.osgi/resolver/src/org/apache/felix/resolver/ShadowList.java
@@ -0,0 +1,157 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
+public class ShadowList<T> implements List<T>
+{
+ private final List<T> m_original;
+ private final List<T> m_shadow;
+
+ public ShadowList(List<T> original)
+ {
+ m_original = original;
+ m_shadow = new ArrayList<T>(original);
+ }
+
+ public List<T> getOriginal()
+ {
+ return m_original;
+ }
+
+ public int size()
+ {
+ return m_shadow.size();
+ }
+
+ public boolean isEmpty()
+ {
+ return m_shadow.isEmpty();
+ }
+
+ public boolean contains(Object o)
+ {
+ return m_shadow.contains(o);
+ }
+
+ public Iterator<T> iterator()
+ {
+ return m_shadow.iterator();
+ }
+
+ public Object[] toArray()
+ {
+ return m_shadow.toArray();
+ }
+
+ public <T> T[] toArray(T[] ts)
+ {
+ return m_shadow.toArray(ts);
+ }
+
+ public boolean add(T e)
+ {
+ return m_shadow.add(e);
+ }
+
+ public boolean remove(Object o)
+ {
+ return m_shadow.remove(o);
+ }
+
+ public boolean containsAll(Collection<?> clctn)
+ {
+ return m_shadow.containsAll(clctn);
+ }
+
+ public boolean addAll(Collection<? extends T> clctn)
+ {
+ return m_shadow.addAll(clctn);
+ }
+
+ public boolean addAll(int i, Collection<? extends T> clctn)
+ {
+ return m_shadow.addAll(i, clctn);
+ }
+
+ public boolean removeAll(Collection<?> clctn)
+ {
+ return m_shadow.removeAll(clctn);
+ }
+
+ public boolean retainAll(Collection<?> clctn)
+ {
+ return m_shadow.retainAll(clctn);
+ }
+
+ public void clear()
+ {
+ m_shadow.clear();
+ }
+
+ public T get(int i)
+ {
+ return m_shadow.get(i);
+ }
+
+ public T set(int i, T e)
+ {
+ return m_shadow.set(i, e);
+ }
+
+ public void add(int i, T e)
+ {
+ m_shadow.add(i, e);
+ }
+
+ public T remove(int i)
+ {
+ return m_shadow.remove(i);
+ }
+
+ public int indexOf(Object o)
+ {
+ return m_shadow.indexOf(o);
+ }
+
+ public int lastIndexOf(Object o)
+ {
+ return m_shadow.lastIndexOf(o);
+ }
+
+ public ListIterator<T> listIterator()
+ {
+ return m_shadow.listIterator();
+ }
+
+ public ListIterator<T> listIterator(int i)
+ {
+ return m_shadow.listIterator(i);
+ }
+
+ public List<T> subList(int i, int i1)
+ {
+ return m_shadow.subList(i, i1);
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.osgi/resolver/src/org/apache/felix/resolver/SimpleHostedCapability.java b/bundles/org.eclipse.osgi/resolver/src/org/apache/felix/resolver/SimpleHostedCapability.java
new file mode 100644
index 000000000..98e2d4f3d
--- /dev/null
+++ b/bundles/org.eclipse.osgi/resolver/src/org/apache/felix/resolver/SimpleHostedCapability.java
@@ -0,0 +1,61 @@
+/*
+ * 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;
+
+import java.util.Map;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Resource;
+import org.osgi.service.resolver.HostedCapability;
+
+class SimpleHostedCapability implements HostedCapability
+{
+ private final Resource m_host;
+ private final Capability m_cap;
+
+ SimpleHostedCapability(Resource host, Capability cap)
+ {
+ m_host = host;
+ m_cap = cap;
+ }
+
+ public Resource getResource()
+ {
+ return m_host;
+ }
+
+ public Capability getDeclaredCapability()
+ {
+ return m_cap;
+ }
+
+ public String getNamespace()
+ {
+ return m_cap.getNamespace();
+ }
+
+ public Map<String, String> getDirectives()
+ {
+ return m_cap.getDirectives();
+ }
+
+ public Map<String, Object> getAttributes()
+ {
+ return m_cap.getAttributes();
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.osgi/resolver/src/org/apache/felix/resolver/Util.java b/bundles/org.eclipse.osgi/resolver/src/org/apache/felix/resolver/Util.java
new file mode 100644
index 000000000..dc5a10003
--- /dev/null
+++ b/bundles/org.eclipse.osgi/resolver/src/org/apache/felix/resolver/Util.java
@@ -0,0 +1,99 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.osgi.framework.Version;
+import org.osgi.framework.namespace.IdentityNamespace;
+import org.osgi.framework.namespace.PackageNamespace;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Namespace;
+import org.osgi.resource.Requirement;
+import org.osgi.resource.Resource;
+
+public class Util
+{
+ public static String getSymbolicName(Resource resource)
+ {
+ List<Capability> caps = resource.getCapabilities(null);
+ for (Capability cap : caps)
+ {
+ if (cap.getNamespace().equals(IdentityNamespace.IDENTITY_NAMESPACE))
+ {
+ return cap.getAttributes().get(IdentityNamespace.IDENTITY_NAMESPACE).toString();
+ }
+ }
+ return null;
+ }
+
+ public static Version getVersion(Resource resource)
+ {
+ List<Capability> caps = resource.getCapabilities(null);
+ for (Capability cap : caps)
+ {
+ if (cap.getNamespace().equals(IdentityNamespace.IDENTITY_NAMESPACE))
+ {
+ return (Version)
+ cap.getAttributes().get(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE);
+ }
+ }
+ return null;
+ }
+
+ public static boolean isFragment(Resource resource)
+ {
+ List<Capability> caps = resource.getCapabilities(null);
+ for (Capability cap : caps)
+ {
+ if (cap.getNamespace().equals(IdentityNamespace.IDENTITY_NAMESPACE))
+ {
+ String type = (String)
+ cap.getAttributes().get(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE);
+ return (type != null) && type.equals(IdentityNamespace.TYPE_FRAGMENT);
+ }
+ }
+ return false;
+ }
+
+ public static boolean isOptional(Requirement req)
+ {
+ String resolution = req.getDirectives().get(Namespace.REQUIREMENT_RESOLUTION_DIRECTIVE);
+ return Namespace.RESOLUTION_OPTIONAL.equalsIgnoreCase(resolution);
+ }
+
+ public static List<Requirement> getDynamicRequirements(List<Requirement> reqs)
+ {
+ List<Requirement> result = new ArrayList<Requirement>();
+ if (reqs != null)
+ {
+ for (Requirement req : reqs)
+ {
+ String resolution = req.getDirectives()
+ .get(PackageNamespace.REQUIREMENT_RESOLUTION_DIRECTIVE);
+ if ((resolution != null)
+ && resolution.equals(PackageNamespace.RESOLUTION_DYNAMIC))
+ {
+ result.add(req);
+ }
+ }
+ }
+ return result;
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.osgi/resolver/src/org/apache/felix/resolver/WireImpl.java b/bundles/org.eclipse.osgi/resolver/src/org/apache/felix/resolver/WireImpl.java
new file mode 100644
index 000000000..a79ac7114
--- /dev/null
+++ b/bundles/org.eclipse.osgi/resolver/src/org/apache/felix/resolver/WireImpl.java
@@ -0,0 +1,116 @@
+/*
+ * 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;
+
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
+import org.osgi.resource.Resource;
+import org.osgi.resource.Wire;
+
+class WireImpl implements Wire
+{
+ private final Resource m_requirer;
+ private final Requirement m_req;
+ private final Resource m_provider;
+ private final Capability m_cap;
+
+ public WireImpl(
+ Resource requirer, Requirement req,
+ Resource provider, Capability cap)
+ {
+ m_requirer = requirer;
+ m_req = req;
+ m_provider = provider;
+ m_cap = cap;
+ }
+
+ public Resource getRequirer()
+ {
+ return m_requirer;
+ }
+
+ public Requirement getRequirement()
+ {
+ return m_req;
+ }
+
+ public Resource getProvider()
+ {
+ return m_provider;
+ }
+
+ public Capability getCapability()
+ {
+ return m_cap;
+ }
+
+ @Override
+ public String toString()
+ {
+ return m_req
+ + " -> "
+ + "[" + m_provider + "]";
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (obj == null)
+ {
+ return false;
+ }
+ if (!(obj instanceof Wire))
+ {
+ return false;
+ }
+ final Wire other = (Wire) obj;
+ if (this.m_requirer != other.getRequirer()
+ && (this.m_requirer == null || !this.m_requirer.equals(other.getRequirer())))
+ {
+ return false;
+ }
+ if (this.m_req != other.getRequirement()
+ && (this.m_req == null || !this.m_req.equals(other.getRequirement())))
+ {
+ return false;
+ }
+ if (this.m_provider != other.getProvider()
+ && (this.m_provider == null || !this.m_provider.equals(other.getProvider())))
+ {
+ return false;
+ }
+ if (this.m_cap != other.getCapability()
+ && (this.m_cap == null || !this.m_cap.equals(other.getCapability())))
+ {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int hash = 5;
+ hash = 29 * hash + (this.m_requirer != null ? this.m_requirer.hashCode() : 0);
+ hash = 29 * hash + (this.m_req != null ? this.m_req.hashCode() : 0);
+ hash = 29 * hash + (this.m_provider != null ? this.m_provider.hashCode() : 0);
+ hash = 29 * hash + (this.m_cap != null ? this.m_cap.hashCode() : 0);
+ return hash;
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.osgi/resolver/src/org/apache/felix/resolver/WrappedCapability.java b/bundles/org.eclipse.osgi/resolver/src/org/apache/felix/resolver/WrappedCapability.java
new file mode 100644
index 000000000..9bbec36e2
--- /dev/null
+++ b/bundles/org.eclipse.osgi/resolver/src/org/apache/felix/resolver/WrappedCapability.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) OSGi Alliance (2012). All Rights Reserved.
+ *
+ * Licensed 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;
+
+import java.util.Map;
+import org.osgi.framework.namespace.PackageNamespace;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Resource;
+import org.osgi.service.resolver.HostedCapability;
+
+public class WrappedCapability implements HostedCapability
+{
+ private final Resource m_host;
+ private final Capability m_cap;
+
+ public WrappedCapability(Resource host, Capability cap)
+ {
+ m_host = host;
+ m_cap = cap;
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (obj == null)
+ {
+ return false;
+ }
+ if (getClass() != obj.getClass())
+ {
+ return false;
+ }
+ final WrappedCapability other = (WrappedCapability) obj;
+ if (m_host != other.m_host && (m_host == null || !m_host.equals(other.m_host)))
+ {
+ return false;
+ }
+ if (m_cap != other.m_cap && (m_cap == null || !m_cap.equals(other.m_cap)))
+ {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int hash = 7;
+ hash = 37 * hash + (m_host != null ? m_host.hashCode() : 0);
+ hash = 37 * hash + (m_cap != null ? m_cap.hashCode() : 0);
+ return hash;
+ }
+
+ public Capability getDeclaredCapability()
+ {
+ return m_cap;
+ }
+
+ public Resource getResource()
+ {
+ return m_host;
+ }
+
+ public String getNamespace()
+ {
+ return m_cap.getNamespace();
+ }
+
+ public Map<String, String> getDirectives()
+ {
+ return m_cap.getDirectives();
+ }
+
+ public Map<String, Object> getAttributes()
+ {
+ return m_cap.getAttributes();
+ }
+
+// TODO: RFC-112 - Need impl-specific type.
+// public List<String> getUses()
+// {
+// return m_cap.getUses();
+// }
+
+ @Override
+ public String toString()
+ {
+ if (m_host == null)
+ {
+ return getAttributes().toString();
+ }
+ if (getNamespace().equals(PackageNamespace.PACKAGE_NAMESPACE))
+ {
+ return "[" + m_host + "] "
+ + getNamespace()
+ + "; "
+ + getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE);
+ }
+ return "[" + m_host + "] " + getNamespace() + "; " + getAttributes();
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.osgi/resolver/src/org/apache/felix/resolver/WrappedRequirement.java b/bundles/org.eclipse.osgi/resolver/src/org/apache/felix/resolver/WrappedRequirement.java
new file mode 100644
index 000000000..a9d66dfea
--- /dev/null
+++ b/bundles/org.eclipse.osgi/resolver/src/org/apache/felix/resolver/WrappedRequirement.java
@@ -0,0 +1,102 @@
+/*
+ * 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;
+
+import java.util.Map;
+import org.osgi.resource.Namespace;
+import org.osgi.resource.Requirement;
+import org.osgi.resource.Resource;
+
+public class WrappedRequirement implements Requirement
+{
+ private final Resource m_host;
+ private final Requirement m_req;
+
+ public WrappedRequirement(Resource host, Requirement req)
+ {
+ m_host = host;
+ m_req = req;
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (obj == null)
+ {
+ return false;
+ }
+ if (getClass() != obj.getClass())
+ {
+ return false;
+ }
+ final WrappedRequirement other = (WrappedRequirement) obj;
+ if (m_host != other.m_host && (m_host == null || !m_host.equals(other.m_host)))
+ {
+ return false;
+ }
+ if (m_req != other.m_req && (m_req == null || !m_req.equals(other.m_req)))
+ {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int hash = 7;
+ hash = 37 * hash + (m_host != null ? m_host.hashCode() : 0);
+ hash = 37 * hash + (m_req != null ? m_req.hashCode() : 0);
+ return hash;
+ }
+
+ public Requirement getDeclaredRequirement()
+ {
+ return m_req;
+ }
+
+ public Resource getResource()
+ {
+ return m_host;
+ }
+
+ public String getNamespace()
+ {
+ return m_req.getNamespace();
+ }
+
+ public Map<String, String> getDirectives()
+ {
+ return m_req.getDirectives();
+ }
+
+ public Map<String, Object> getAttributes()
+ {
+ return m_req.getAttributes();
+ }
+
+ @Override
+ public String toString()
+ {
+ return "[" + m_host + "] "
+ + getNamespace()
+ + "; "
+ + getDirectives().get(Namespace.REQUIREMENT_FILTER_DIRECTIVE);
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.osgi/resolver/src/org/apache/felix/resolver/WrappedResource.java b/bundles/org.eclipse.osgi/resolver/src/org/apache/felix/resolver/WrappedResource.java
new file mode 100644
index 000000000..693e8bbcd
--- /dev/null
+++ b/bundles/org.eclipse.osgi/resolver/src/org/apache/felix/resolver/WrappedResource.java
@@ -0,0 +1,127 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.osgi.framework.namespace.ExecutionEnvironmentNamespace;
+import org.osgi.framework.namespace.HostNamespace;
+import org.osgi.framework.namespace.IdentityNamespace;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
+import org.osgi.resource.Resource;
+
+class WrappedResource implements Resource
+{
+ private final Resource m_host;
+ private final List<Resource> m_fragments;
+ private List<Capability> m_cachedCapabilities = null;
+ private List<Requirement> m_cachedRequirements = null;
+
+ public WrappedResource(Resource host, List<Resource> fragments)
+ {
+ m_host = host;
+ m_fragments = fragments;
+ }
+
+ public Resource getDeclaredResource()
+ {
+ return m_host;
+ }
+
+ public List<Resource> getFragments()
+ {
+ return m_fragments;
+ }
+
+ public List<Capability> getCapabilities(String namespace)
+ {
+ if (m_cachedCapabilities == null)
+ {
+ List<Capability> caps = new ArrayList<Capability>();
+
+ // Wrap host capabilities.
+ for (Capability cap : m_host.getCapabilities(null))
+ {
+ caps.add(new WrappedCapability(this, cap));
+ }
+
+ // Wrap fragment capabilities.
+ if (m_fragments != null)
+ {
+ for (Resource fragment : m_fragments)
+ {
+ for (Capability cap : fragment.getCapabilities(null))
+ {
+ // Filter out identity capabilities, since they
+ // are not part of the fragment payload.
+ if (!cap.getNamespace()
+ .equals(IdentityNamespace.IDENTITY_NAMESPACE))
+ {
+ caps.add(new WrappedCapability(this, cap));
+ }
+ }
+ }
+ }
+ m_cachedCapabilities = Collections.unmodifiableList(caps);
+ }
+ return m_cachedCapabilities;
+ }
+
+ public List<Requirement> getRequirements(String namespace)
+ {
+ if (m_cachedRequirements == null)
+ {
+ List<Requirement> reqs = new ArrayList<Requirement>();
+
+ // Wrap host requirements.
+ for (Requirement req : m_host.getRequirements(null))
+ {
+ reqs.add(new WrappedRequirement(this, req));
+ }
+
+ // Wrap fragment requirements.
+ if (m_fragments != null)
+ {
+ for (Resource fragment : m_fragments)
+ {
+ for (Requirement req : fragment.getRequirements(null))
+ {
+ // Filter out host and execution environment requirements,
+ // since they are not part of the fragment payload.
+ if (!req.getNamespace().equals(HostNamespace.HOST_NAMESPACE)
+ && !req.getNamespace().equals(
+ ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE))
+ {
+ reqs.add(new WrappedRequirement(this, req));
+ }
+ }
+ }
+ }
+ m_cachedRequirements = Collections.unmodifiableList(reqs);
+ }
+ return m_cachedRequirements;
+ }
+
+ public String toString()
+ {
+ return m_host.toString();
+ }
+} \ No newline at end of file

Back to the top