Copyright (c) 2012, 2017 IBM Corporation and others. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 which accompanies this distribution, and is available at https://www.eclipse.org/legal/epl-2.0/ SPDX-License-Identifier: EPL-2.0 Contributors: IBM Corporation - initial API and implementation
/******************************************************************************* * Copyright (c) 2012, 2017 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/
package org.eclipse.osgi.container; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.ReentrantReadWriteLock; 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.container.ModuleContainerAdaptor.ContainerEvent; import org.eclipse.osgi.container.ModuleRevisionBuilder.GenericInfo; import org.eclipse.osgi.container.namespaces.EquinoxModuleDataNamespace; import org.eclipse.osgi.framework.util.ObjectPool; import org.eclipse.osgi.internal.container.Capabilities; import org.eclipse.osgi.internal.container.ComputeNodeOrder; import org.eclipse.osgi.internal.framework.EquinoxConfiguration; import org.osgi.framework.BundleException; import org.osgi.framework.Constants; import org.osgi.framework.Version; import org.osgi.framework.namespace.PackageNamespace; import org.osgi.framework.wiring.BundleRevision; import org.osgi.resource.Namespace; import org.osgi.resource.Requirement; import org.osgi.resource.Wire; 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.

Concurrent Semantics
Implementations must be thread safe. The database allows for concurrent read operations and all read operations are protected by the read lock. All write operations are protected by the write lock. The read and write locks are reentrant and follow the semantics of the ReentrantReadWriteLock. Just like the ReentrantReadWriteLock the lock on a database can not be upgraded from a read to a write. Doing so will result in an IllegalMonitorStateException being thrown. This is behavior is different from the ReentrantReadWriteLock which results in a deadlock if an attempt is made to upgrade from a read to a write lock.

A database is associated with a 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 load(DataInputStream), all other methods that perform write operations are intended to be used by the associated container.

Since:3.10
/** * 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 #readLock() read} lock. All write operations are * protected by the {@link #writeLock() 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. * @since 3.10 */
public class ModuleDatabase {
The adaptor for this database
/** * The adaptor for this database */
final ModuleContainerAdaptor adaptor;
A map of modules by location.
/** * A map of modules by location. */
private final Map<String, Module> modulesByLocations;
A map of modules by id.
/** * A map of modules by id. */
private final Map<Long, Module> modulesById;
A map of revision wiring objects.
/** * A map of revision wiring objects. */
final Map<ModuleRevision, ModuleWiring> wirings;
Holds the next id to be assigned to a module when it is installed
/** * Holds the next id to be assigned to a module when it is installed */
final AtomicLong nextId;
Holds the current timestamp for revisions of this database.
/** * Holds the current timestamp for revisions of this database. */
final AtomicLong revisionsTimeStamp;
Holds the current timestamp for all changes to this database. This includes changes to revisions and changes to module settings.
/** * Holds the current timestamp for all changes to this database. * This includes changes to revisions and changes to module settings. */
final AtomicLong allTimeStamp;
Holds the construction time which is used to check for empty database on load. This is necessary to ensure the loaded database is consistent with what was persisted.
/** * Holds the construction time which is used to check for empty database on * load. This is necessary to ensure the loaded database is consistent with * what was persisted. */
final long constructionTime; private final Capabilities capabilities;
A map of module settings keyed by module id.
/** * A map of module settings keyed by module id. */
final Map<Long, EnumSet<Settings>> moduleSettings;
The initial module start level.
/** * The initial module start level. */
private int initialModuleStartLevel = 1;
Monitors read and write access to this database
/** * Monitors read and write access to this database */
private final ReentrantReadWriteLock monitor = new ReentrantReadWriteLock(false); static enum Sort { BY_DEPENDENCY, BY_START_LEVEL, BY_ID;
Tests if this option is contained in the specified options
/** * 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.
Params:
  • adaptor – the module container adaptor
/** * Constructs a new empty database. * @param adaptor the module container adaptor */
public ModuleDatabase(ModuleContainerAdaptor adaptor) { this.adaptor = adaptor; this.modulesByLocations = new HashMap<>(); this.modulesById = new HashMap<>(); this.wirings = new HashMap<>(); // Start at id 1 because 0 is reserved for the system bundle this.nextId = new AtomicLong(1); // seed with current time to avoid duplicate timestamps after using -clean this.constructionTime = System.currentTimeMillis(); this.revisionsTimeStamp = new AtomicLong(constructionTime); this.allTimeStamp = new AtomicLong(constructionTime); this.moduleSettings = new HashMap<>(); this.capabilities = new Capabilities(); }
Returns the module at the given location or null if no module exists at the given location.

A read operation protected by the read lock.

Params:
  • location – the location of the module.
Returns:the module at the given location or null.
/** * 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 #readLock() read} lock. * @param location the location of the module. * @return the module at the given location or null. */
final Module getModule(String location) { readLock(); try { return modulesByLocations.get(location); } finally { readUnlock(); } }
Returns the module at the given id or null if no module exists at the given location.

A read operation protected by the read lock.

Params:
  • id – the id of the module.
Returns:the module at the given id or null.
/** * 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 #readLock() read} lock. * @param id the id of the module. * @return the module at the given id or null. */
final Module getModule(long id) { readLock(); try { return modulesById.get(id); } finally { readUnlock(); } }
Installs a new revision using the specified builder, location and module.

A write operation protected by the write lock.

Params:
  • location – the location to use for the installation
  • builder – the builder to use to create the new revision
  • revisionInfo – the revision info for the new revision, may be null.
Returns:the installed module
/** * Installs a new revision using the specified builder, location and module. * <p> * A write operation protected by the {@link #writeLock() write} lock. * @param location the location to use for the installation * @param builder the builder to use to create the new revision * @param revisionInfo the revision info for the new revision, may be {@code null}. * @return the installed module */
final Module install(String location, ModuleRevisionBuilder builder, Object revisionInfo) { writeLock(); try { int startlevel = Constants.SYSTEM_BUNDLE_LOCATION.equals(location) ? 0 : getInitialModuleStartLevel(); long id = Constants.SYSTEM_BUNDLE_LOCATION.equals(location) ? 0 : builder.getId(); if (id == -1) { // the id is not set by the builder; get and increment the next ID id = getAndIncrementNextId(); } if (getModule(id) != null) { throw new IllegalStateException("Duplicate module id: " + id + " used by module: " + getModule(id)); //$NON-NLS-1$//$NON-NLS-2$ } EnumSet<Settings> settings = getActivationPolicySettings(builder); Module module = load(location, builder, revisionInfo, id, settings, startlevel); long currentTime = System.currentTimeMillis(); module.setlastModified(currentTime); setSystemLastModified(currentTime); incrementTimestamps(true); return module; } finally { writeUnlock(); } } private EnumSet<Settings> getActivationPolicySettings(ModuleRevisionBuilder builder) { // do not do this for fragment bundles if ((builder.getTypes() & BundleRevision.TYPE_FRAGMENT) != 0) { return null; } for (GenericInfo info : builder.getCapabilities()) { if (EquinoxModuleDataNamespace.MODULE_DATA_NAMESPACE.equals(info.getNamespace())) { if (EquinoxModuleDataNamespace.CAPABILITY_ACTIVATION_POLICY_LAZY.equals(info.getAttributes().get(EquinoxModuleDataNamespace.CAPABILITY_ACTIVATION_POLICY))) { String compatibilityStartLazy = adaptor.getProperty(EquinoxConfiguration.PROP_COMPATIBILITY_START_LAZY); if (compatibilityStartLazy == null || Boolean.valueOf(compatibilityStartLazy)) { // TODO hack until p2 is fixed (bug 177641) EnumSet<Settings> settings = EnumSet.noneOf(Settings.class); settings.add(Settings.USE_ACTIVATION_POLICY); settings.add(Settings.AUTO_START); return settings; } } return null; } } return null; } final Module load(String location, ModuleRevisionBuilder builder, Object revisionInfo, long id, EnumSet<Settings> settings, int startlevel) { // sanity check checkWrite(); 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 = adaptor.createSystemModule(); } else { module = adaptor.createModule(location, id, settings, startlevel); } builder.addRevision(module, revisionInfo); modulesByLocations.put(location, module); modulesById.put(id, module); if (settings != null) moduleSettings.put(id, settings); ModuleRevision newRevision = module.getCurrentRevision(); addCapabilities(newRevision); return module; }
Uninstalls the specified module from this database. Uninstalling a module will attempt to clean up any removal pending revisions possible.

A write operation protected by the write lock.

Params:
  • module – the module to uninstall
/** * 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 #writeLock() write} lock. * @param module the module to uninstall */
final void uninstall(Module module) { writeLock(); try { ModuleRevisions uninstalling = module.getRevisions(); // mark the revisions as uninstalled before removing the revisions uninstalling.uninstall(); // 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) { // 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); removeCapabilities(revision); } } // attempt to cleanup any removal pendings cleanupRemovalPending(); long currentTime = System.currentTimeMillis(); module.setlastModified(currentTime); setSystemLastModified(currentTime); incrementTimestamps(true); } finally { writeUnlock(); } }
Updates the specified module with anew revision using the specified builder.

A write operation protected by the write lock.

Params:
  • module – the module for which the revision provides an update for
  • builder – the builder to use to create the new revision
  • revisionInfo – the revision info for the new revision, may be null.
/** * Updates the specified module with anew revision using the specified builder. * <p> * A write operation protected by the {@link #writeLock() 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 * @param revisionInfo the revision info for the new revision, may be {@code null}. */
final void update(Module module, ModuleRevisionBuilder builder, Object revisionInfo) { writeLock(); try { ModuleRevision oldRevision = module.getCurrentRevision(); ModuleRevision newRevision = builder.addRevision(module, revisionInfo); addCapabilities(newRevision); // 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(); long currentTime = System.currentTimeMillis(); module.setlastModified(currentTime); setSystemLastModified(currentTime); incrementTimestamps(true); } finally { writeUnlock(); } }
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.
/** * 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. */
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) { Collection<ModuleWiring> toRemoveWirings = new ArrayList<>(); Map<ModuleWiring, Collection<ModuleWire>> toRemoveWireLists = new HashMap<>(); for (ModuleRevision pendingRemoval : dependencyClosure) { ModuleWiring removedWiring = wirings.get(pendingRemoval); if (removedWiring == null) { continue; } 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<>(); toRemoveWireLists.put(wire.getProviderWiring(), providerWires); } providerWires.add(wire); } } for (ModuleRevision pendingRemoval : dependencyClosure) { pendingRemoval.getRevisions().removeRevision(pendingRemoval); removeCapabilities(pendingRemoval); wirings.remove(pendingRemoval); } // 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); // No null checks; we are holding the write lock here. provided.removeAll(entry.getValue()); entry.getKey().setProvidedWires(provided); for (ModuleWire removedWire : entry.getValue()) { // invalidate the wire removedWire.invalidate(); } } // invalidate any removed wiring objects for (ModuleWiring moduleWiring : toRemoveWirings) { moduleWiring.invalidate(); } } } }
Gets all revisions with a removal pending wiring.

A read operation protected by the read lock.

Returns:all revisions with a removal pending wiring.
/** * Gets all revisions with a removal pending wiring. * <p> * A read operation protected by the {@link #readLock() read} lock. * @return all revisions with a removal pending wiring. */
final Collection<ModuleRevision> getRemovalPending() { Collection<ModuleRevision> removalPending = new ArrayList<>(); readLock(); try { for (ModuleWiring wiring : wirings.values()) { if (!wiring.isCurrent()) removalPending.add(wiring.getRevision()); } } finally { readUnlock(); } return removalPending; }
Returns the current wiring for the specified revision or null of no wiring exists for the revision.

A read operation protected by the read lock.

Params:
  • revision – the revision to get the wiring for
Returns:the current wiring for the specified revision.
/** * 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 #readLock() read} lock. * @param revision the revision to get the wiring for * @return the current wiring for the specified revision. */
final ModuleWiring getWiring(ModuleRevision revision) { readLock(); try { return wirings.get(revision); } finally { readUnlock(); } }
Returns a snapshot of the wirings for all revisions. This performs a shallow copy of each entry in the wirings map.

A read operation protected by the read lock.

Returns:a snapshot of the wirings for all revisions.
/** * 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 #readLock() read} lock. * @return a snapshot of the wirings for all revisions. */
final Map<ModuleRevision, ModuleWiring> getWiringsCopy() { readLock(); try { return new HashMap<>(wirings); } finally { readUnlock(); } }
Returns a cloned snapshot of the wirings of all revisions. This performs a clone of each ModuleWiring. The revision, capabilities, requirements, provided wires, required wires, and ModuleWiring.getSubstitutedNames() of each wiring are copied into a cloned copy of the wiring.

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 resolve operations without holding the read or write lock on this database.

A read operation protected by the read lock.

Returns:a cloned snapshot of the wirings of all revisions.
/** * 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}, * {@link ModuleWiring#getRequiredModuleWires(String) required wires}, and * {@link ModuleWiring#getSubstitutedNames()} 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 #readLock() read} lock. * @return a cloned snapshot of the wirings of all revisions. */
final Map<ModuleRevision, ModuleWiring> getWiringsClone() { readLock(); try { Map<ModuleRevision, ModuleWiring> clonedWirings = new HashMap<>(); 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 { readUnlock(); } }
Replaces the complete wiring map with the specified wiring

A write operation protected by the write lock.

Params:
  • newWiring – the new wiring to take effect. The values from the new wiring are copied.
/** * Replaces the complete wiring map with the specified wiring * <p> * A write operation protected by the {@link #writeLock() 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) { writeLock(); try { wirings.clear(); wirings.putAll(newWiring); incrementTimestamps(true); } finally { writeUnlock(); } }
Adds all the values from the specified delta wirings to the wirings current wirings

A write operation protected by the write lock.

Params:
  • deltaWiring – the new wiring values to take effect. The values from the delta wiring are copied.
/** * Adds all the values from the specified delta wirings to the * wirings current wirings * <p> * A write operation protected by the {@link #writeLock() 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) { writeLock(); try { wirings.putAll(deltaWiring); incrementTimestamps(true); } finally { writeUnlock(); } }
Returns a snapshot of all modules ordered by module ID.

A read operation protected by the read lock.

Returns:a snapshot of all modules.
/** * Returns a snapshot of all modules ordered by module ID. * <p> * A read operation protected by the {@link #readLock() 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
Params:
  • sortOptions – options for sorting
Returns:a snapshot of all modules ordered according to the sort options
/** * 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) { readLock(); try { List<Module> modules = new ArrayList<>(modulesByLocations.values()); sortModules(modules, sortOptions); return modules; } finally { readUnlock(); } } 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<>(toSort.size()); for (Module module : toSort) { ModuleRevision current = module.getCurrentRevision(); if (current == null) { continue; } ModuleWiring wiring = current.getWiring(); if (wiring == null) { continue; } // No null check; we are holding the database lock here. for (ModuleWire wire : wiring.getRequiredModuleWires(null)) { ModuleRequirement req = wire.getRequirement(); // Add all requirements that are not package requirements. // Only add package requirements that are not dynamic // TODO may want to consider only adding package, bundle and host requirements, other generic requirement are not that interesting if (!PackageNamespace.PACKAGE_NAMESPACE.equals(req.getNamespace()) || !PackageNamespace.RESOLUTION_DYNAMIC.equals(req.getDirectives().get(Namespace.REQUIREMENT_RESOLUTION_DIRECTIVE))) { 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<>(cycles.length); for (Object[] cycle : cycles) { List<Module> moduleCycle = new ArrayList<>(cycle.length); for (Object module : cycle) { moduleCycle.add((Module) module); } moduleCycles.add(moduleCycle); } return moduleCycles; } private void checkWrite() { if (monitor.getWriteHoldCount() == 0) throw new IllegalMonitorStateException("Must hold the write lock."); //$NON-NLS-1$ }
returns the next module ID.

A read operation protected by the read lock.

Returns:the next module ID
/** * returns the next module ID. * <p> * A read operation protected by the {@link #readLock() read} lock. * @return the next module ID */
public final long getNextId() { readLock(); try { return nextId.get(); } finally { readUnlock(); } }
Atomically increments by one the next module ID.

A write operation protected by the writeLock() lock.

Returns:the previous module ID
Since:3.13
/** * Atomically increments by one the next module ID. * <p> * A write operation protected by the {@link #writeLock()} lock. * @return the previous module ID * @since 3.13 */
public final long getAndIncrementNextId() { writeLock(); try { return nextId.getAndIncrement(); } finally { writeUnlock(); } }
Returns the current timestamp for the revisions of this database. The timestamp is incremented any time a modification is made to the revisions in this database. For example:
  • installing a module
  • updating a module
  • uninstalling a module
  • modifying the wirings

A read operation protected by the read lock.

Returns:the current timestamp of this database.
/** * Returns the current timestamp for the revisions of this database. * The timestamp is incremented any time a modification * is made to the revisions in 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 #readLock() read} lock. * @return the current timestamp of this database. */
final public long getRevisionsTimestamp() { readLock(); try { return revisionsTimeStamp.get(); } finally { readUnlock(); } }
Returns the current timestamp for this database. The timestamp is incremented any time a modification is made to this database. This includes the modifications described in revisions timestamp and the following modifications related to modules:
  • modifying the initial module start level
  • modifying a module start level
  • modifying a module settings

A read operation protected by the read lock.

Returns:the current timestamp of this database.
/** * Returns the current timestamp for this database. * The timestamp is incremented any time a modification * is made to this database. This includes the modifications * described in {@link #getRevisionsTimestamp() revisions timestamp} * and the following modifications related to modules: * <ul> * <li> modifying the initial module start level * <li> modifying a module start level * <li> modifying a module settings * </ul> * <p> * A read operation protected by the {@link #readLock() read} lock. * @return the current timestamp of this database. */
final public long getTimestamp() { readLock(); try { return allTimeStamp.get(); } finally { readUnlock(); } }
Increments the timestamps of this database.
Params:
  • incrementRevision – indicates if the revision timestamp should change
/** * Increments the timestamps of this database. * @param incrementRevision indicates if the revision timestamp should change */
private void incrementTimestamps(boolean incrementRevision) { // sanity check checkWrite(); if (incrementRevision) { revisionsTimeStamp.incrementAndGet(); } allTimeStamp.incrementAndGet(); adaptor.updatedDatabase(); } private void setSystemLastModified(long currentTime) { // sanity check checkWrite(); Module systemModule = getModule(0); if (systemModule != null) { systemModule.setlastModified(currentTime); } }
Acquires the read lock for this database.
See Also:
  • lock.lock()
/** * Acquires the read lock for this database. * @see ReadLock#lock() */
public final void readLock() { monitor.readLock().lock(); }
Acquires the write lock for this database. Same as WriteLock.lock() except an illegal state exception is thrown if the current thread holds one or more read locks.
Throws:
See Also:
/** * Acquires the write lock for this database. * 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 writeLock() { 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(); }
Attempts to release the read lock for this database.
See Also:
  • unlock.unlock()
/** * Attempts to release the read lock for this database. * @see ReadLock#unlock() */
public final void readUnlock() { monitor.readLock().unlock(); }
Attempts to release the write lock for this database.
See Also:
  • unlock.unlock()
/** * Attempts to release the write lock for this database. * @see WriteLock#unlock() */
public final void writeUnlock() { monitor.writeLock().unlock(); }
Adds the capabilities provided by the specified revision to this database. These capabilities must become available for lookup with the findCapabilities(Requirement) method.

This method must be called while holding the write lock.

Params:
  • revision – the revision which has capabilities to add
/** * 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(Requirement)} * method. * <p> * This method must be called while holding the {@link #writeLock() write} lock. * @param revision the revision which has capabilities to add */
final void addCapabilities(ModuleRevision revision) { checkWrite(); Collection<String> packageNames = capabilities.addCapabilities(revision); // Clear the dynamic miss caches for all the package names added for (ModuleWiring wiring : wirings.values()) { wiring.removeDynamicPackageMisses(packageNames); } }
Removes the capabilities provided by the specified revision from this database. These capabilities must no longer be available for lookup with the findCapabilities(Requirement) method.

This method must be called while holding the write lock.

Params:
  • 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(Requirement)} method. * <p> * This method must be called while holding the {@link #writeLock() 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.

A read operation protected by the read lock. Implementers of this method should acquire the read lock while finding capabilities.

Params:
  • requirement – the requirement
Returns:the candidates for the requirement
/** * Returns a mutable snapshot of capabilities that are candidates for * satisfying the specified requirement. * <p> * A read operation protected by the {@link #readLock() read} lock. * Implementers of this method should acquire the read lock while * finding capabilities. * @param requirement the requirement * @return the candidates for the requirement */
final List<ModuleCapability> findCapabilities(Requirement requirement) { readLock(); try { return capabilities.findCapabilities(requirement); } finally { readUnlock(); } }
Writes this database in a format suitable for using the load(DataInputStream) method. All modules are stored which have a current revision. Only the current revision of each module is stored (no removal pending revisions are stored). Optionally the wiring of each current revision may be stored. Wiring can only be stored if there are no removal pending revisions.

This method acquires the read lock while writing this database.

After this database have been written, the output stream is flushed. The output stream remains open after this method returns.

Params:
  • out – the data output steam.
  • persistWirings – true if wirings should be persisted. This option will be ignored if there are removal pending revisions.
Throws:
  • IOException – if writing this database to the specified output stream throws an IOException
/** * 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 #readLock() 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 { readLock(); try { Persistence.store(this, out, persistWirings); } finally { readUnlock(); } }
Loads information into this database from the input data stream. This data base must be empty and never been modified (the 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.

Since this method modifies this database it is considered a write operation. This method acquires the write lock while loading the information into this database.

The specified stream remains open after this method returns.

Params:
  • in – the data input stream.
Throws:
/** * Loads information into this database from the input data stream. This data * base must be empty and never been modified (the {@link #getRevisionsTimestamp() 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 #writeLock() 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 { writeLock(); try { if (allTimeStamp.get() != constructionTime) throw new IllegalStateException("Can only load into a empty database."); //$NON-NLS-1$ Persistence.load(this, in); } finally { writeUnlock(); } } final void persistSettings(EnumSet<Settings> settings, Module module) { writeLock(); try { EnumSet<Settings> existing = moduleSettings.get(module.getId()); if (!settings.equals(existing)) { moduleSettings.put(module.getId(), EnumSet.copyOf(settings)); incrementTimestamps(false); } } finally { writeUnlock(); } } final void setStartLevel(Module module, int startlevel) { writeLock(); try { module.checkValid(); module.storeStartLevel(startlevel); incrementTimestamps(false); } finally { writeUnlock(); } } final int getInitialModuleStartLevel() { readLock(); try { return this.initialModuleStartLevel; } finally { readUnlock(); } } final void setInitialModuleStartLevel(int initialStartlevel) { writeLock(); try { this.initialModuleStartLevel = initialStartlevel; incrementTimestamps(false); } finally { writeUnlock(); } } private static class Persistence { private static final int VERSION = 3; private static final byte NULL = 0; private static final byte OBJECT = 1; private static final byte INDEX = 2; 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; // REMOVED treated as List<String> - private static final byte VALUE_STRING_ARRAY = 1; // REMOVED never was really supported by the OSGi builder - private static final byte VAlUE_BOOLEAN = 2; // REMOVED never was really supported by the OSGi builder - 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; // REMOVED treated as type String - 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, Integer.valueOf(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, List<Object> objectTable) { if (index == objectTable.size()) { objectTable.add(object); } else if (index < objectTable.size()) { objectTable.set(index, object); } else { while (objectTable.size() < index) { objectTable.add(null); } objectTable.add(object); } } public static void store(ModuleDatabase moduleDatabase, DataOutputStream out, boolean persistWirings) throws IOException { out.writeInt(VERSION); out.writeLong(moduleDatabase.getRevisionsTimestamp()); out.writeLong(moduleDatabase.getTimestamp()); out.writeLong(moduleDatabase.getNextId()); out.writeInt(moduleDatabase.getInitialModuleStartLevel()); // prime the object table with all the strings, versions and maps Set<String> allStrings = new HashSet<>(); Set<Version> allVersions = new HashSet<>(); Set<Map<String, ?>> allMaps = new HashSet<>(); // first gather all the strings, versions and maps from the modules List<Module> modules = moduleDatabase.getModules(); for (Module module : modules) { getStringsVersionsAndMaps(module, moduleDatabase, allStrings, allVersions, allMaps); } // outside of the modules the wirings have 'substituted' packages strings Map<ModuleRevision, ModuleWiring> wirings = moduleDatabase.wirings; for (ModuleWiring wiring : wirings.values()) { Collection<String> substituted = wiring.getSubstitutedNames(); allStrings.addAll(substituted); } // Now persist all the Strings Map<Object, Integer> objectTable = new HashMap<>(); allStrings.remove(null); out.writeInt(allStrings.size()); for (String string : allStrings) { writeString(string, out, objectTable); out.writeInt(addToWriteTable(string, objectTable)); } // Followed by versions which may reference strings with their qualifier out.writeInt(allVersions.size()); for (Version version : allVersions) { writeVersion(version, out, objectTable); out.writeInt(addToWriteTable(version, objectTable)); } // Followed by maps which may reference the strings and versions out.writeInt(allMaps.size()); for (Map<String, ?> map : allMaps) { writeMap(map, out, objectTable, moduleDatabase); out.writeInt(addToWriteTable(map, objectTable)); } // Followed by modules which reference the strings, versions, and maps out.writeInt(modules.size()); 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; } // prime the object table with all the required wires which reference the modules out.writeInt(wirings.size()); for (ModuleWiring wiring : wirings.values()) { List<ModuleWire> requiredWires = wiring.getPersistentRequiredWires(); out.writeInt(requiredWires.size()); for (ModuleWire wire : requiredWires) { writeWire(wire, out, objectTable); } } // now write all the info about each wiring using only indexes from the objectTable for (ModuleWiring wiring : wirings.values()) { writeWiring(wiring, out, objectTable); } out.flush(); } private static void getStringsVersionsAndMaps(Module module, ModuleDatabase moduleDatabase, Set<String> allStrings, Set<Version> allVersions, Set<Map<String, ?>> allMaps) { ModuleRevision current = module.getCurrentRevision(); if (current == null) return; allStrings.add(module.getLocation()); allStrings.add(current.getSymbolicName()); allStrings.add(current.getVersion().getQualifier()); allVersions.add(current.getVersion()); EnumSet<Settings> settings = moduleDatabase.moduleSettings.get(module.getId()); if (settings != null) { for (Settings setting : settings) { allStrings.add(setting.toString()); } } List<ModuleCapability> capabilities = current.getModuleCapabilities(null); for (ModuleCapability capability : capabilities) { allStrings.add(capability.getNamespace()); addMap(capability.getPersistentAttributes(), allStrings, allVersions, allMaps); addMap(capability.getDirectives(), allStrings, allVersions, allMaps); } List<ModuleRequirement> requirements = current.getModuleRequirements(null); for (ModuleRequirement requirement : requirements) { allStrings.add(requirement.getNamespace()); addMap(requirement.getAttributes(), allStrings, allVersions, allMaps); addMap(requirement.getDirectives(), allStrings, allVersions, allMaps); } } private static void addMap(Map<String, ?> map, Set<String> allStrings, Set<Version> allVersions, Set<Map<String, ?>> allMaps) { if (!allMaps.add(map)) { // map was already added return; } for (Map.Entry<String, ?> entry : map.entrySet()) { allStrings.add(entry.getKey()); Object value = entry.getValue(); if (value instanceof String) { allStrings.add((String) value); } else if (value instanceof Version) { allStrings.add(((Version) value).getQualifier()); allVersions.add((Version) value); } else if (value instanceof List) { switch (getListType((List<?>) value)) { case VALUE_STRING : for (Object string : (List<?>) value) { allStrings.add((String) string); } break; case VALUE_VERSION : for (Object version : (List<?>) value) { allStrings.add(((Version) version).getQualifier()); allVersions.add((Version) version); } break; } } } } public static void load(ModuleDatabase moduleDatabase, DataInputStream in) throws IOException { int version = in.readInt(); if (version > VERSION || VERSION / 1000 != version / 1000) throw new IllegalArgumentException("The version of the persistent framework data is not compatible: " + version + " expecting: " + VERSION); //$NON-NLS-1$ //$NON-NLS-2$ long revisionsTimeStamp = in.readLong(); long allTimeStamp = in.readLong(); moduleDatabase.nextId.set(in.readLong()); moduleDatabase.setInitialModuleStartLevel(in.readInt()); List<Object> objectTable = new ArrayList<>(); if (version >= 2) { int numStrings = in.readInt(); for (int i = 0; i < numStrings; i++) { readIndexedString(in, objectTable); } int numVersions = in.readInt(); for (int i = 0; i < numVersions; i++) { readIndexedVersion(in, objectTable); } int numMaps = in.readInt(); for (int i = 0; i < numMaps; i++) { readIndexedMap(in, objectTable); } } int numModules = in.readInt(); ModuleRevisionBuilder builder = new ModuleRevisionBuilder(); for (int i = 0; i < numModules; i++) { readModule(builder, moduleDatabase, in, objectTable, version); } moduleDatabase.revisionsTimeStamp.set(revisionsTimeStamp); moduleDatabase.allTimeStamp.set(allTimeStamp); 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<>(); 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.revisionsTimeStamp.set(revisionsTimeStamp); moduleDatabase.allTimeStamp.set(allTimeStamp); } 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, objectTable); out.writeLong(module.getId()); writeString(current.getSymbolicName(), out, objectTable); writeVersion(current.getVersion(), out, objectTable); out.writeInt(current.getTypes()); List<ModuleCapability> capabilities = current.getModuleCapabilities(null); out.writeInt(capabilities.size()); for (ModuleCapability capability : capabilities) { out.writeInt(addToWriteTable(capability, objectTable)); writeGenericInfo(capability.getNamespace(), capability.getPersistentAttributes(), capability.getDirectives(), out, objectTable); } 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, objectTable); } // 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, objectTable); } } // startlevel out.writeInt(module.getStartLevel()); // last modified out.writeLong(module.getLastModified()); } private static void readModule(ModuleRevisionBuilder builder, ModuleDatabase moduleDatabase, DataInputStream in, List<Object> objectTable, int version) throws IOException { builder.clear(); int moduleIndex = in.readInt(); String location = readString(in, objectTable); long id = in.readLong(); builder.setSymbolicName(readString(in, objectTable)); builder.setVersion(readVersion(in, objectTable)); 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, objectTable, version); } int numRequirements = in.readInt(); int[] requirementIndexes = new int[numRequirements]; for (int i = 0; i < numRequirements; i++) { requirementIndexes[i] = in.readInt(); readGenericInfo(false, in, builder, objectTable, version); } // 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, objectTable))); } } // startlevel int startlevel = in.readInt(); Object revisionInfo = moduleDatabase.adaptor.getRevisionInfo(location, id); Module module = moduleDatabase.load(location, builder, revisionInfo, 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, List<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.getPersistentRequirements(); 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.getPersistentProvidedWires(); 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.getPersistentRequiredWires(); 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, objectTable); } } private static ModuleWiring readWiring(DataInputStream in, List<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<>(numCapabilities); for (int i = 0; i < numCapabilities; i++) { capabilities.add((ModuleCapability) objectTable.get(in.readInt())); } int numRequirements = in.readInt(); List<ModuleRequirement> requirements = new ArrayList<>(numRequirements); for (int i = 0; i < numRequirements; i++) { requirements.add((ModuleRequirement) objectTable.get(in.readInt())); } int numProvidedWires = in.readInt(); List<ModuleWire> providedWires = new ArrayList<>(numProvidedWires); for (int i = 0; i < numProvidedWires; i++) { providedWires.add((ModuleWire) objectTable.get(in.readInt())); } int numRequiredWires = in.readInt(); List<ModuleWire> requiredWires = new ArrayList<>(numRequiredWires); for (int i = 0; i < numRequiredWires; i++) { requiredWires.add((ModuleWire) objectTable.get(in.readInt())); } int numSubstitutedNames = in.readInt(); Collection<String> substituted = new ArrayList<>(numSubstitutedNames); for (int i = 0; i < numSubstitutedNames; i++) { substituted.add(readString(in, objectTable)); } return new ModuleWiring(revision, capabilities, requirements, providedWires, requiredWires, substituted); } private static void writeGenericInfo(String namespace, Map<String, ?> attributes, Map<String, String> directives, DataOutputStream out, Map<Object, Integer> objectTable) throws IOException { writeString(namespace, out, objectTable); Integer attributesIndex = objectTable.get(attributes); Integer directivesIndex = objectTable.get(directives); if (attributesIndex == null || directivesIndex == null) throw new NullPointerException("Could not find the expected indexes"); //$NON-NLS-1$ out.writeInt(attributesIndex); out.writeInt(directivesIndex); } @SuppressWarnings("unchecked") private static void readGenericInfo(boolean isCapability, DataInputStream in, ModuleRevisionBuilder builder, List<Object> objectTable, int version) throws IOException { String namespace = readString(in, objectTable); Map<String, Object> attributes = version >= 2 ? (Map<String, Object>) objectTable.get(in.readInt()) : readMap(in, objectTable); Map<String, ?> directives = version >= 2 ? (Map<String, ?>) objectTable.get(in.readInt()) : readMap(in, objectTable); if (attributes == null || directives == null) throw new NullPointerException("Could not find the expected indexes"); //$NON-NLS-1$ if (isCapability) { builder.basicAddCapability(namespace, (Map<String, String>) directives, attributes); } else { builder.basicAddRequirement(namespace, (Map<String, String>) directives, attributes); } } private static void writeMap(Map<String, ?> source, DataOutputStream out, Map<Object, Integer> objectTable, ModuleDatabase moduleDatabase) 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, objectTable); if (value instanceof String) { out.writeByte(VALUE_STRING); writeString((String) value, out, objectTable); } 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, objectTable); } else if (value instanceof List) { out.writeByte(VALUE_LIST); writeList(out, key, (List<?>) value, objectTable, moduleDatabase); } else { // do our best and write a string; post an error. // This will be difficult to debug because we don't know which module it is coming from, but it is better than being silent moduleDatabase.adaptor.publishContainerEvent(ContainerEvent.ERROR, moduleDatabase.getModule(0), new BundleException("Invalid map value: " + key + " = " + value.getClass().getName() + '[' + value + ']')); //$NON-NLS-1$ //$NON-NLS-2$ out.writeByte(VALUE_STRING); writeString(String.valueOf(value), out, objectTable); } } } } private static void readIndexedMap(DataInputStream in, List<Object> objectTable) throws IOException { Map<String, Object> result = readMap(in, objectTable); addToReadTable(result, in.readInt(), objectTable); } private static Map<String, Object> readMap(DataInputStream in, List<Object> objectTable) throws IOException { int count = in.readInt(); Map<String, Object> result; if (count == 0) { result = Collections.emptyMap(); } else if (count == 1) { String key = readString(in, objectTable); byte type = in.readByte(); Object value = readMapValue(in, type, objectTable); result = Collections.singletonMap(key, value); } else { result = new HashMap<>(count); for (int i = 0; i < count; i++) { String key = readString(in, objectTable); byte type = in.readByte(); Object value = readMapValue(in, type, objectTable); result.put(key, value); } result = Collections.unmodifiableMap(result); } return result; } private static Object readMapValue(DataInputStream in, int type, List<Object> objectTable) throws IOException { switch (type) { case VALUE_STRING : return readString(in, objectTable); case VALUE_LONG : return new Long(in.readLong()); case VALUE_DOUBLE : return new Double(in.readDouble()); case VALUE_VERSION : return readVersion(in, objectTable); case VALUE_LIST : return readList(in, objectTable); default : throw new IllegalArgumentException("Invalid type: " + type); //$NON-NLS-1$ } } private static void writeList(DataOutputStream out, String key, List<?> list, Map<Object, Integer> objectTable, ModuleDatabase moduleDatabase) throws IOException { if (list.isEmpty()) { out.writeInt(0); return; } byte type = getListType(list); if (type == -1) { out.writeInt(0); return; // don't understand the list type } out.writeInt(list.size()); out.writeByte(type == -2 ? VALUE_STRING : type); for (Object value : list) { switch (type) { case VALUE_STRING : writeString((String) value, out, objectTable); 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, objectTable); break; default : // do our best and write a string; post an error. // This will be difficult to debug because we don't know which module it is coming from, but it is better than being silent moduleDatabase.adaptor.publishContainerEvent(ContainerEvent.ERROR, moduleDatabase.getModule(0), new BundleException("Invalid list element in map: " + key + " = " + value.getClass().getName() + '[' + value + ']')); //$NON-NLS-1$ //$NON-NLS-2$ writeString(String.valueOf(value), out, objectTable); 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 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, List<Object> objectTable) throws IOException { int size = in.readInt(); if (size == 0) return Collections.emptyList(); byte listType = in.readByte(); if (size == 1) { return Collections.singletonList(readListValue(listType, in, objectTable)); } List<Object> list = new ArrayList<>(size); for (int i = 0; i < size; i++) { list.add(readListValue(listType, in, objectTable)); } return Collections.unmodifiableList(list); } private static Object readListValue(byte listType, DataInputStream in, List<Object> objectTable) throws IOException { switch (listType) { case VALUE_STRING : return readString(in, objectTable); case VALUE_LONG : return new Long(in.readLong()); case VALUE_DOUBLE : return new Double(in.readDouble()); case VALUE_VERSION : return readVersion(in, objectTable); default : throw new IllegalArgumentException("Invalid type: " + listType); //$NON-NLS-1$ } } private static void writeVersion(Version version, DataOutputStream out, Map<Object, Integer> objectTable) throws IOException { if (version == null || version.equals(Version.emptyVersion)) { out.writeByte(NULL); return; } Integer index = objectTable.get(version); if (index != null) { out.writeByte(INDEX); out.writeInt(index); return; } out.writeByte(OBJECT); out.writeInt(version.getMajor()); out.writeInt(version.getMinor()); out.writeInt(version.getMicro()); writeQualifier(version.getQualifier(), out, objectTable); } private static void writeQualifier(String string, DataOutputStream out, Map<Object, Integer> objectTable) throws IOException { if (string != null && string.length() == 0) string = null; writeString(string, out, objectTable); } private static Version readIndexedVersion(DataInputStream in, List<Object> objectTable) throws IOException { Version version = readVersion0(in, objectTable, false); addToReadTable(version, in.readInt(), objectTable); return version; } private static Version readVersion(DataInputStream in, List<Object> objectTable) throws IOException { return readVersion0(in, objectTable, true); } private static Version readVersion0(DataInputStream in, List<Object> objectTable, boolean intern) throws IOException { byte type = in.readByte(); if (type == INDEX) { int index = in.readInt(); return (Version) objectTable.get(index); } if (type == NULL) return Version.emptyVersion; int majorComponent = in.readInt(); int minorComponent = in.readInt(); int serviceComponent = in.readInt(); String qualifierComponent = readString(in, objectTable); Version version = new Version(majorComponent, minorComponent, serviceComponent, qualifierComponent); return intern ? ObjectPool.intern(version) : version; } private static void writeString(String string, DataOutputStream out, Map<Object, Integer> objectTable) throws IOException { Integer index = string != null ? objectTable.get(string) : null; if (index != null) { out.writeByte(INDEX); out.writeInt(index); return; } 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 readIndexedString(DataInputStream in, List<Object> objectTable) throws IOException { String string = readString0(in, objectTable, false); addToReadTable(string, in.readInt(), objectTable); return string; } static private String readString(DataInputStream in, List<Object> objectTable) throws IOException { return readString0(in, objectTable, true); } static private String readString0(DataInputStream in, List<Object> objectTable, boolean intern) throws IOException { byte type = in.readByte(); if (type == INDEX) { int index = in.readInt(); return (String) objectTable.get(index); } if (type == NULL) { return null; } String string; if (type == LONG_STRING) { int length = in.readInt(); byte[] data = new byte[length]; in.readFully(data); string = new String(data, UTF_8); } else { string = in.readUTF(); } return intern ? ObjectPool.intern(string) : string; } } }