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/container/src/org
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/container/src/org')
-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
21 files changed, 6394 insertions, 0 deletions
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;
+ }
+ }
+}

Back to the top