org.eclipse.osgi.container.ModuleDatabase Maven / Gradle / Ivy
Show all versions of aspectjtools Show documentation
/*******************************************************************************
* Copyright (c) 2012, 2020 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.nio.charset.StandardCharsets;
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
* {@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.
*
* 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
*/
final ModuleContainerAdaptor adaptor;
/**
* A map of modules by location.
*/
private final Map modulesByLocations;
/**
* A map of modules by id.
*/
private final Map modulesById;
/**
* A map of revision wiring objects.
*/
final Map wirings;
/**
* 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.
*/
final AtomicLong revisionsTimeStamp;
/**
* 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.
*/
final long constructionTime;
private final Capabilities capabilities;
/**
* A map of module settings keyed by module id.
*/
final Map> moduleSettings;
/**
* The initial module start level.
*/
private int initialModuleStartLevel = 1;
/**
* 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
*/
public boolean isContained(Sort... options) {
for (Sort option : options) {
if (equals(option)) {
return true;
}
}
return false;
}
}
/**
* 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 {@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 {@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 {@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 = 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 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 = 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, 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 {@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 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 {@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.
*/
void cleanupRemovalPending() {
// sanity check
checkWrite();
Collection removalPending = getRemovalPending();
for (ModuleRevision removed : removalPending) {
if (wirings.get(removed) == null)
continue;
Collection dependencyClosure = ModuleContainer.getDependencyClosure(removed, wirings);
boolean allPendingRemoval = true;
for (ModuleRevision pendingRemoval : dependencyClosure) {
if (pendingRemoval.isCurrent()) {
allPendingRemoval = false;
break;
}
}
if (allPendingRemoval) {
Collection toRemoveWirings = new ArrayList<>();
Map> toRemoveWireLists = new HashMap<>();
for (ModuleRevision pendingRemoval : dependencyClosure) {
ModuleWiring removedWiring = wirings.get(pendingRemoval);
if (removedWiring == null) {
continue;
}
toRemoveWirings.add(removedWiring);
List removedWires = removedWiring.getRequiredModuleWires(null);
for (ModuleWire wire : removedWires) {
Collection 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> entry : toRemoveWireLists.entrySet()) {
List 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 {@link #readLock() read} lock.
* @return all revisions with a removal pending wiring.
*/
final Collection getRemovalPending() {
Collection 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 {@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 {@link #readLock() read} lock.
* @return a snapshot of the wirings for all revisions.
*/
final Map 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 {@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.
*
* 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.
*
* A read operation protected by the {@link #readLock() read} lock.
* @return a cloned snapshot of the wirings of all revisions.
*/
final Map getWiringsClone() {
readLock();
try {
Map clonedWirings = new HashMap<>();
for (Map.Entry 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 {@link #writeLock() write} lock.
* @param newWiring the new wiring to take effect. The values
* from the new wiring are copied.
*/
final void setWiring(Map 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 {@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 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 {@link #readLock() read} lock.
* @return a snapshot of all modules.
*/
final List 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 getSortedModules(Sort... sortOptions) {
readLock();
try {
List modules = new ArrayList<>(modulesByLocations.values());
sortModules(modules, sortOptions);
return modules;
} finally {
readUnlock();
}
}
final void sortModules(List modules, Sort... sortOptions) {
if (modules.size() < 2)
return;
if (sortOptions == null || Sort.BY_ID.isContained(sortOptions) || sortOptions.length == 0) {
Collections.sort(modules, new Comparator() {
@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> sortByDependencies(List toSort) {
// Build references so we can sort
List 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> moduleCycles = new ArrayList<>(cycles.length);
for (Object[] cycle : cycles) {
List 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 {@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 {@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 {@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 {@link #getRevisionsTimestamp() 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 {@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.
* @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 ReadLock#lock()
*/
public final void readLock() {
monitor.readLock().lock();
}
/**
* 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 ReadLock#unlock()
*/
public final void readUnlock() {
monitor.readLock().unlock();
}
/**
* Attempts to release the write lock for this database.
* @see WriteLock#unlock()
*/
public final void writeUnlock() {
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(Requirement)}
* method.
*
* 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 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 {@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.
*
* 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 {@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 findCapabilities(Requirement requirement) {
readLock();
try {
return capabilities.findCapabilities(requirement);
} finally {
readUnlock();
}
}
/**
* 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.
*
* This method acquires the {@link #readLock() 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.
* @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 {@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.
*
* 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.
*
* 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, Module module) {
writeLock();
try {
EnumSet 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 byte VALUE_STRING = 0;
// REMOVED treated as List - 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