blob: 77454b81bd8d95c46c110f4e7d0b4e0d325c615f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2003, 2005 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.osgi.internal.resolver;
import java.io.*;
import java.util.*;
import org.eclipse.osgi.service.resolver.*;
import org.osgi.framework.Version;
class StateWriter {
// objectTable will be a hashmap of objects. The objects will be things
// like BundleDescription, ExportPackageDescription, Version etc.. The integer
// index value will be used in the cache to allow cross-references in the
// cached state.
private Map objectTable = new HashMap();
private ArrayList forcedWrite = new ArrayList();
private int addToObjectTable(Object object) {
Integer cur = (Integer) objectTable.get(object);
if (cur != null)
return cur.intValue();
objectTable.put(object, new Integer(objectTable.size()));
// return the index of the object just added (i.e. size - 1)
return (objectTable.size() - 1);
}
private int getFromObjectTable(Object object) {
if (objectTable != null) {
Object objectResult = objectTable.get(object);
if (objectResult != null) {
return ((Integer) objectResult).intValue();
}
}
return -1;
}
private boolean writePrefix(Object object, DataOutputStream out) throws IOException {
if (writeIndex(object, out))
return true;
// add this object to the object table first
int index = addToObjectTable(object);
out.writeByte(StateReader.OBJECT);
out.writeInt(index);
return false;
}
private void writeStateDeprecated(StateImpl state, DataOutputStream out) throws IOException {
// first clear the System exports because we don't want to persist them in the system
// bundles bundle description data
state.setSystemExports(null);
out.write(StateReader.STATE_CACHE_VERSION);
if (writePrefix(state, out))
return;
out.writeLong(state.getTimeStamp());
Dictionary[] propSet = state.getPlatformProperties();
out.writeInt(propSet.length);
for (int i = 0; i < propSet.length; i++){
Dictionary props = propSet[i];
out.writeInt(StateImpl.PROPS.length);
for (int j = 0; j < StateImpl.PROPS.length; j++)
writePlatformProp(props.get(StateImpl.PROPS[j]), out);
}
BundleDescription[] bundles = state.getBundles();
StateHelperImpl.getInstance().sortBundles(bundles);
out.writeInt(bundles.length);
if (bundles.length == 0)
return;
for (int i = 0; i < bundles.length; i++)
writeBundleDescription(bundles[i], out, false);
out.writeBoolean(state.isResolved());
// save the lazy data offset
out.writeInt(out.size());
for (int i = 0; i < bundles.length; i++)
writeBundleDescriptionLazyData(bundles[i], out);
}
public void saveState(StateImpl state, File stateFile, File lazyFile) throws IOException {
DataOutputStream outLazy = null;
DataOutputStream outState = null;
FileOutputStream fosLazy = null;
FileOutputStream fosState = null;
try {
// first clear the System exports because we don't want to persist them in the system
// bundles bundle description data
state.setSystemExports(null);
BundleDescription[] bundles = state.getBundles();
StateHelperImpl.getInstance().sortBundles(bundles);
// need to prime the object table with all bundles
// this allows us to write only indexes to bundles in the lazy data
for (int i = 0; i < bundles.length; i++)
addToObjectTable(bundles[i]);
// first write the lazy data to get the offsets and sizes to the lazy data
fosLazy = new FileOutputStream(lazyFile);
outLazy = new DataOutputStream(fosLazy);
for (int i = 0; i < bundles.length; i++)
writeBundleDescriptionLazyData(bundles[i], outLazy);
// now write the state data
fosState = new FileOutputStream(stateFile);
outState = new DataOutputStream(fosState);
outState.write(StateReader.STATE_CACHE_VERSION);
if (writePrefix(state, outState))
return;
outState.writeLong(state.getTimeStamp());
Dictionary[] propSet = state.getPlatformProperties();
outState.writeInt(propSet.length);
for (int i = 0; i < propSet.length; i++){
Dictionary props = propSet[i];
outState.writeInt(StateImpl.PROPS.length);
for (int j = 0; j < StateImpl.PROPS.length; j++)
writePlatformProp(props.get(StateImpl.PROPS[j]), outState);
}
outState.writeInt(bundles.length);
if (bundles.length == 0)
return;
for (int i = 0; i < bundles.length; i++)
// write out each bundle with the force flag set to make sure
// the data is written at least once in the non-lazy state data
writeBundleDescription(bundles[i], outState, true);
outState.writeBoolean(state.isResolved());
} finally {
if (outLazy != null)
try {
outLazy.flush();
fosLazy.getFD().sync();
} catch (IOException e) {
// do nothing, we tried
}
try {
outLazy.close();
} catch (IOException e) {
// do nothing
}
if (outState != null)
try {
outState.flush();
fosState.getFD().sync();
} catch (IOException e) {
// do nothing, we tried
}
try {
outState.close();
} catch (IOException e) {
// do nothing
}
}
}
private void writePlatformProp(Object obj, DataOutputStream out) throws IOException {
if (obj == null)
out.writeByte(StateReader.NULL);
else {
out.writeByte(StateReader.OBJECT);
if (obj instanceof String) {
out.writeInt(1);
writeStringOrNull((String) obj, out);
} else {
String[] props = (String[]) obj;
out.writeInt(props.length);
for (int i = 0; i < props.length; i++)
writeStringOrNull(props[i], out);
}
}
}
/*
* The force flag is used when writing the non-lazy state data. This forces the data to be
* written once even if the object exists in the object table.
* This is needed because we want to write the lazy data first but we only want
* to include indexes to the actual bundles in the lazy data. To do this we
* prime the object table with all the bundles first. Then we write the
* lazy data. Finally we write the non-lazy data and force a write of the
* bundles data once even if the bundle is in the object table.
*/
private void writeBundleDescription(BundleDescription bundle, DataOutputStream out, boolean force) throws IOException {
if (force && !forcedWrite.contains(bundle)) {
int index = addToObjectTable(bundle);
out.writeByte(StateReader.OBJECT);
out.writeInt(index);
forcedWrite.add(bundle);
} else if (writePrefix(bundle, out))
return;
// first write out non-lazy loaded data
out.writeLong(bundle.getBundleId()); // ID must be the first thing
writeBaseDescription(bundle, out);
out.writeInt(((BundleDescriptionImpl) bundle).getLazyDataOffset());
out.writeInt(((BundleDescriptionImpl) bundle).getLazyDataSize());
out.writeBoolean(bundle.isResolved());
out.writeBoolean(bundle.isSingleton());
out.writeBoolean(bundle.hasDynamicImports());
out.writeBoolean(bundle.attachFragments());
out.writeBoolean(bundle.dynamicFragments());
writeHostSpec((HostSpecificationImpl) bundle.getHost(), out, force);
List dependencies = ((BundleDescriptionImpl) bundle).getBundleDependencies();
out.writeInt(dependencies.size());
for (Iterator iter = dependencies.iterator(); iter.hasNext();)
writeBundleDescription((BundleDescription) iter.next(), out, force);
// the rest is lazy loaded data
}
private void writeBundleDescriptionLazyData(BundleDescription bundle, DataOutputStream out) throws IOException {
int dataStart = out.size(); // save the offset of lazy data start
int index = getFromObjectTable(bundle);
((BundleDescriptionImpl) bundle).setLazyDataOffset(out.size());
out.writeInt(index);
writeStringOrNull(bundle.getLocation(), out);
writeStringOrNull(bundle.getPlatformFilter(), out);
ExportPackageDescription[] exports = bundle.getExportPackages();
out.writeInt(exports.length);
for (int i = 0; i < exports.length; i++)
writeExportPackageDesc((ExportPackageDescriptionImpl) exports[i], out);
ImportPackageSpecification[] imports = bundle.getImportPackages();
out.writeInt(imports.length);
for (int i = 0; i < imports.length; i++)
writeImportPackageSpec(imports[i], out);
BundleSpecification[] requiredBundles = bundle.getRequiredBundles();
out.writeInt(requiredBundles.length);
for (int i = 0; i < requiredBundles.length; i++)
writeBundleSpec((BundleSpecificationImpl) requiredBundles[i], out);
ExportPackageDescription[] selectedExports = bundle.getSelectedExports();
if (selectedExports == null) {
out.writeInt(0);
} else {
out.writeInt(selectedExports.length);
for (int i = 0; i < selectedExports.length; i++)
writeExportPackageDesc((ExportPackageDescriptionImpl) selectedExports[i], out);
}
ExportPackageDescription[] resolvedImports = bundle.getResolvedImports();
if (resolvedImports == null) {
out.writeInt(0);
} else {
out.writeInt(resolvedImports.length);
for (int i = 0; i < resolvedImports.length; i++)
writeExportPackageDesc((ExportPackageDescriptionImpl) resolvedImports[i], out);
}
BundleDescription[] resolvedRequires = bundle.getResolvedRequires();
if (resolvedRequires == null) {
out.writeInt(0);
} else {
out.writeInt(resolvedRequires.length);
for (int i = 0; i < resolvedRequires.length; i++)
writeBundleDescription(resolvedRequires[i], out, false);
}
String[] ees = bundle.getExecutionEnvironments();
out.writeInt(ees.length);
for (int i = 0; i < ees.length; i++)
writeStringOrNull(ees[i], out);
// save the size of the lazy data
((BundleDescriptionImpl) bundle).setLazyDataSize(out.size() - dataStart);
}
private void writeBundleSpec(BundleSpecificationImpl bundle, DataOutputStream out) throws IOException {
writeVersionConstraint(bundle, out);
writeBundleDescription((BundleDescription) bundle.getSupplier(), out, false);
out.writeBoolean(bundle.isExported());
out.writeBoolean(bundle.isOptional());
}
private void writeExportPackageDesc(ExportPackageDescriptionImpl exportPackageDesc, DataOutputStream out) throws IOException {
if (writePrefix(exportPackageDesc, out))
return;
writeBaseDescription(exportPackageDesc, out);
out.writeBoolean(exportPackageDesc.isRoot());
writeMap(out, exportPackageDesc.getAttributes());
writeMap(out, exportPackageDesc.getDirectives());
}
private void writeMap(DataOutputStream out, Map source) throws IOException {
if (source == null) {
out.writeInt(0);
} else {
out.writeInt(source.size());
Iterator iter = source.keySet().iterator();
while (iter.hasNext()) {
String key = (String) iter.next();
Object value = source.get(key);
writeStringOrNull(key, out);
if (value instanceof String) {
out.writeByte(0);
writeStringOrNull((String) value, out);
} else if (value instanceof String[]) {
out.writeByte(1);
writeList(out, (String[]) value);
} else if (value instanceof Boolean) {
out.writeByte(2);
out.writeBoolean(((Boolean) value).booleanValue());
}
}
}
}
private void writeList(DataOutputStream out, String[] list) throws IOException {
if (list == null) {
out.writeInt(0);
} else {
out.writeInt(list.length);
for (int i = 0; i < list.length; i++)
writeStringOrNull(list[i], out);
}
}
private void writeBaseDescription(BaseDescription rootDesc, DataOutputStream out) throws IOException {
writeStringOrNull(rootDesc.getName(), out);
writeVersion(rootDesc.getVersion(), out);
}
private void writeImportPackageSpec(ImportPackageSpecification importPackageSpec, DataOutputStream out) throws IOException {
writeVersionConstraint(importPackageSpec, out);
// TODO this is a hack until the state dynamic loading is cleaned up
// we should only write the supplier if we are resolved
if (importPackageSpec.getBundle().isResolved())
writeExportPackageDesc((ExportPackageDescriptionImpl) importPackageSpec.getSupplier(), out);
else
out.writeByte(StateReader.NULL);
writeStringOrNull(importPackageSpec.getBundleSymbolicName(), out);
writeVersionRange(importPackageSpec.getBundleVersionRange(), out);
writeMap(out, importPackageSpec.getAttributes());
writeMap(out, importPackageSpec.getDirectives());
}
private void writeHostSpec(HostSpecificationImpl host, DataOutputStream out, boolean force) throws IOException {
if (host == null) {
out.writeByte(StateReader.NULL);
return;
}
out.writeByte(StateReader.OBJECT);
writeVersionConstraint(host, out);
BundleDescription[] hosts = host.getHosts();
if (hosts == null) {
out.writeInt(0);
return;
}
out.writeInt(hosts.length);
for (int i = 0; i < hosts.length; i++)
writeBundleDescription(hosts[i], out, force);
}
// called by writers for VersionConstraintImpl subclasses
private void writeVersionConstraint(VersionConstraint constraint, DataOutputStream out) throws IOException {
writeStringOrNull(constraint.getName(), out);
writeVersionRange(constraint.getVersionRange(), out);
}
private void writeVersion(Version version, DataOutputStream out) throws IOException {
if (version == null || version.equals(Version.emptyVersion)) {
out.writeByte(StateReader.NULL);
return;
}
out.writeByte(StateReader.OBJECT);
out.writeInt(version.getMajor());
out.writeInt(version.getMinor());
out.writeInt(version.getMicro());
writeQualifier(version.getQualifier(), out);
}
private void writeVersionRange(VersionRange versionRange, DataOutputStream out) throws IOException {
if (versionRange == null || versionRange.equals(VersionRange.emptyRange)) {
out.writeByte(StateReader.NULL);
return;
}
out.writeByte(StateReader.OBJECT);
writeVersion(versionRange.getMinimum(), out);
out.writeBoolean(versionRange.getIncludeMinimum());
writeVersion(versionRange.getMaximum(), out);
out.writeBoolean(versionRange.getIncludeMaximum());
}
private boolean writeIndex(Object object, DataOutputStream out) throws IOException {
if (object == null) {
out.writeByte(StateReader.NULL);
return true;
}
int index = getFromObjectTable(object);
if (index == -1)
return false;
out.writeByte(StateReader.INDEX);
out.writeInt(index);
return true;
}
public void saveStateDeprecated(StateImpl state, DataOutputStream output) throws IOException {
try {
writeStateDeprecated(state, output);
} finally {
output.close();
}
}
private void writeStringOrNull(String string, DataOutputStream out) throws IOException {
if (string == null)
out.writeByte(StateReader.NULL);
else {
out.writeByte(StateReader.OBJECT);
out.writeUTF(string);
}
}
private void writeQualifier(String string, DataOutputStream out) throws IOException {
if (string != null && string.length() == 0)
string = null;
writeStringOrNull(string, out);
}
}