| /******************************************************************************* |
| * 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); |
| } |
| } |