All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.osgi.container.ModuleDatabase Maven / Gradle / Ivy

Go to download

AspectJ tools most notably contains the AspectJ compiler (AJC). AJC applies aspects to Java classes during compilation, fully replacing Javac for plain Java classes and also compiling native AspectJ or annotation-based @AspectJ syntax. Furthermore, AJC can weave aspects into existing class files in a post-compile binary weaving step. This library is a superset of AspectJ weaver and hence also of AspectJ runtime.

There is a newer version: 1.9.22.1
Show newest version
/*******************************************************************************
 * Copyright (c) 2012, 2021 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.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 java.util.function.BiFunction;
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.container.NamespaceList;
import org.eclipse.osgi.internal.container.NamespaceList.Builder;
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(EquinoxModuleDataNamespace.MODULE_DATA_NAMESPACE)) { 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()) { NamespaceList.Builder provided = entry.getKey().getProvidedWires().createBuilder(); provided.removeAll(entry.getValue()); entry.getKey().setProvidedWires(provided.build()); 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<>(wirings); clonedWirings.replaceAll(new BiFunction() { public ModuleWiring apply(ModuleRevision r, ModuleWiring w) { return new ModuleWiring(r, w.getCapabilities(), w.getRequirements(), w.getProvidedWires(), w.getRequiredWires(), w.getSubstitutedNames()); } }); 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(); } } /** * Perform the specified operation while holding the write lock. * This will also increment the timestamps and optionally the * revisions timestamps. *

* A write operation protected by the {@link #writeLock() write} lock. * @param incrementRevision if true the revision timestamps will be incremented after successfully running the operation * @param op the operation to run while holding the write lock. */ final void writeLockOperation(boolean incrementRevision, Runnable op) { writeLock(); try { op.run(); incrementTimestamps(incrementRevision); } 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 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 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 allStrings = new HashSet<>(); Set allVersions = new HashSet<>(); Set> allMaps = new HashSet<>(); // first gather all the strings, versions and maps from the modules List modules = moduleDatabase.getModules(); for (Module module : modules) { getStringsVersionsAndMaps(module, moduleDatabase, allStrings, allVersions, allMaps); } // outside of the modules the wirings have 'substituted' packages strings Map wirings = moduleDatabase.wirings; for (ModuleWiring wiring : wirings.values()) { Collection substituted = wiring.getSubstitutedNames(); allStrings.addAll(substituted); } // Now persist all the Strings Map 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 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 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 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 allStrings, Set allVersions, Set> 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 = moduleDatabase.moduleSettings.get(module.getId()); if (settings != null) { for (Settings setting : settings) { allStrings.add(setting.toString()); } } List 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 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 map, Set allStrings, Set allVersions, Set> allMaps) { if (!allMaps.add(map)) { // map was already added return; } for (Map.Entry 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 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 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 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 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 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 = 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 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 = 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 capabilities = current.getModuleCapabilities(null); for (int i = 0; i < capabilities.size(); i++) { addToReadTable(capabilities.get(i), capabilityIndexes[i], objectTable); } List 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 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 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 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 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 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 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 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 substituted = wiring.getSubstitutedNames(); out.writeInt(substituted.size()); for (String pkgName : substituted) { writeString(pkgName, out, objectTable); } } private static ModuleWiring readWiring(DataInputStream in, List 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(); NamespaceList.Builder capabilities = Builder.create(NamespaceList.CAPABILITY); for (int i = 0; i < numCapabilities; i++) { capabilities.add((ModuleCapability) objectTable.get(in.readInt())); } int numRequirements = in.readInt(); NamespaceList.Builder requirements = Builder.create(NamespaceList.REQUIREMENT); for (int i = 0; i < numRequirements; i++) { requirements.add((ModuleRequirement) objectTable.get(in.readInt())); } int numProvidedWires = in.readInt(); NamespaceList.Builder providedWires = Builder.create(NamespaceList.WIRE); for (int i = 0; i < numProvidedWires; i++) { providedWires.add((ModuleWire) objectTable.get(in.readInt())); } int numRequiredWires = in.readInt(); NamespaceList.Builder requiredWires = Builder.create(NamespaceList.WIRE); for (int i = 0; i < numRequiredWires; i++) { requiredWires.add((ModuleWire) objectTable.get(in.readInt())); } int numSubstitutedNames = in.readInt(); Collection substituted = new ArrayList<>(numSubstitutedNames); for (int i = 0; i < numSubstitutedNames; i++) { substituted.add(readString(in, objectTable)); } return new ModuleWiring(revision, capabilities.build(), requirements.build(), providedWires.build(), requiredWires.build(), substituted); } private static void writeGenericInfo(String namespace, Map attributes, Map directives, DataOutputStream out, Map 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 objectTable, int version) throws IOException { String namespace = readString(in, objectTable); Map attributes = version >= 2 ? (Map) objectTable.get(in.readInt()) : readMap(in, objectTable); Map directives = version >= 2 ? (Map) 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) directives, attributes); } else { builder.basicAddRequirement(namespace, (Map) directives, attributes); } } private static void writeMap(Map source, DataOutputStream out, Map objectTable, ModuleDatabase moduleDatabase) throws IOException { if (source == null) { out.writeInt(0); } else { out.writeInt(source.size()); for (String key : source.keySet()) { 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 objectTable) throws IOException { Map result = readMap(in, objectTable); addToReadTable(result, in.readInt(), objectTable); } private static Map readMap(DataInputStream in, List objectTable) throws IOException { int count = in.readInt(); Map 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 objectTable) throws IOException { switch (type) { case VALUE_STRING : return readString(in, objectTable); case VALUE_LONG : return Long.valueOf(in.readLong()); case VALUE_DOUBLE : return Double.valueOf(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 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 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 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 objectTable) throws IOException { switch (listType) { case VALUE_STRING : return readString(in, objectTable); case VALUE_LONG : return Long.valueOf(in.readLong()); case VALUE_DOUBLE : return Double.valueOf(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 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 objectTable) throws IOException { if (string != null && string.length() == 0) string = null; writeString(string, out, objectTable); } private static Version readIndexedVersion(DataInputStream in, List objectTable) throws IOException { Version version = readVersion0(in, objectTable, false); addToReadTable(version, in.readInt(), objectTable); return version; } private static Version readVersion(DataInputStream in, List objectTable) throws IOException { return readVersion0(in, objectTable, true); } private static Version readVersion0(DataInputStream in, List 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 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(StandardCharsets.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 objectTable) throws IOException { String string = readString0(in, objectTable, false); addToReadTable(string, in.readInt(), objectTable); return string; } static private String readString(DataInputStream in, List objectTable) throws IOException { return readString0(in, objectTable, true); } static private String readString0(DataInputStream in, List 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, StandardCharsets.UTF_8); } else { string = in.readUTF(); } return intern ? ObjectPool.intern(string) : string; } } }