/******************************************************************************* * Copyright (c) 2003, 2011 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 * Rob Harrop - SpringSource Inc. (bug 247522) *******************************************************************************/ package org.eclipse.osgi.internal.resolver; import java.io.*; import java.util.*; import org.eclipse.osgi.service.resolver.*; import org.osgi.framework.*; /** * This class is <strong>not</strong> thread safe. Instances must not be * shared across multiple threads. */ 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 final Map<Object, Integer> objectTable = new HashMap<Object, Integer>(); private final List<Object> forcedWrite = new ArrayList<Object>(); private int addToObjectTable(Object object) { Integer cur = 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 { out.write(StateReader.STATE_CACHE_VERSION); if (writePrefix(state, out)) return; out.writeLong(state.getTimeStamp()); // write the platform property keys String[] platformPropKeys = state.getPlatformPropertyKeys(); writePlatformProp(platformPropKeys, out); Dictionary<Object, Object>[] propSet = state.getPlatformProperties(); out.writeInt(propSet.length); for (int i = 0; i < propSet.length; i++) { Dictionary<Object, Object> props = propSet[i]; out.writeInt(platformPropKeys.length); for (int j = 0; j < platformPropKeys.length; j++) writePlatformProp(props.get(platformPropKeys[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; synchronized (state.monitor) { try { 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]); if (bundles[i].getHost() != null) addToObjectTable(bundles[i].getHost()); } // first write the lazy data to get the offsets and sizes to the lazy data fosLazy = new FileOutputStream(lazyFile); outLazy = new DataOutputStream(new BufferedOutputStream(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(new BufferedOutputStream(fosState)); outState.write(StateReader.STATE_CACHE_VERSION); if (writePrefix(state, outState)) return; outState.writeLong(state.getTimeStamp()); // write the platform property keys String[] platformPropKeys = state.getPlatformPropertyKeys(); writePlatformProp(platformPropKeys, outState); // write the platform property values Dictionary<Object, Object>[] propSet = state.getPlatformProperties(); outState.writeInt(propSet.length); for (int i = 0; i < propSet.length; i++) { Dictionary<Object, Object> props = propSet[i]; outState.writeInt(platformPropKeys.length); for (int j = 0; j < platformPropKeys.length; j++) writePlatformProp(props.get(platformPropKeys[j]), outState); } outState.writeInt(bundles.length); 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); // write the DisabledInfos DisabledInfo[] infos = state.getDisabledInfos(); outState.writeInt(infos.length); for (int i = 0; i < infos.length; i++) writeDisabledInfo(infos[i], outState); 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 instanceof String) && !(obj instanceof String[])) 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()); writeList(out, (String[]) ((BundleDescriptionImpl) bundle).getDirective(Constants.MANDATORY_DIRECTIVE)); writeMap(out, bundle.getAttributes()); writeHostSpec((HostSpecificationImpl) bundle.getHost(), out, force); List<BundleDescription> dependencies = ((BundleDescriptionImpl) bundle).getBundleDependencies(); out.writeInt(dependencies.size()); for (Iterator<BundleDescription> iter = dependencies.iterator(); iter.hasNext();) writeBundleDescription(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[] substitutedExports = bundle.getSubstitutedExports(); if (substitutedExports == null) { out.writeInt(0); } else { out.writeInt(substitutedExports.length); for (int i = 0; i < substitutedExports.length; i++) writeExportPackageDesc((ExportPackageDescriptionImpl) substitutedExports[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); Map<String, Long> dynamicStamps = ((BundleDescriptionImpl) bundle).getDynamicStamps(); if (dynamicStamps == null) out.writeInt(0); else { out.writeInt(dynamicStamps.size()); for (Iterator<String> pkgs = dynamicStamps.keySet().iterator(); pkgs.hasNext();) { String pkg = pkgs.next(); writeStringOrNull(pkg, out); out.writeLong(dynamicStamps.get(pkg).longValue()); } } GenericDescription[] genericCapabilities = bundle.getGenericCapabilities(); if (genericCapabilities == null) out.writeInt(0); else { out.writeInt(genericCapabilities.length); for (int i = 0; i < genericCapabilities.length; i++) writeGenericDescription(genericCapabilities[i], out); } GenericSpecification[] genericRequires = bundle.getGenericRequires(); if (genericRequires == null) out.writeInt(0); else { out.writeInt(genericRequires.length); for (int i = 0; i < genericRequires.length; i++) writeGenericSpecification(genericRequires[i], out); } GenericDescription[] selectedCapabilities = bundle.getSelectedGenericCapabilities(); if (selectedCapabilities == null) out.writeInt(0); else { out.writeInt(selectedCapabilities.length); for (int i = 0; i < selectedCapabilities.length; i++) writeGenericDescription(selectedCapabilities[i], out); } GenericDescription[] resolvedCapabilities = bundle.getResolvedGenericRequires(); if (resolvedCapabilities == null) out.writeInt(0); else { out.writeInt(resolvedCapabilities.length); for (int i = 0; i < resolvedCapabilities.length; i++) writeGenericDescription(resolvedCapabilities[i], out); } writeNativeCode(bundle.getNativeCodeSpecification(), out); writeMap(out, ((BundleDescriptionImpl) bundle).getWiresInternal()); // save the size of the lazy data ((BundleDescriptionImpl) bundle).setLazyDataSize(out.size() - dataStart); } private void writeDisabledInfo(DisabledInfo disabledInfo, DataOutputStream out) throws IOException { writeStringOrNull(disabledInfo.getPolicyName(), out); writeStringOrNull(disabledInfo.getMessage(), out); writeBundleDescription(disabledInfo.getBundle(), out, false); } private void writeBundleSpec(BundleSpecificationImpl bundle, DataOutputStream out) throws IOException { if (writePrefix(bundle, out)) return; writeVersionConstraint(bundle, out); writeBundleDescription((BundleDescription) bundle.getSupplier(), out, false); out.writeBoolean(bundle.isExported()); out.writeBoolean(bundle.isOptional()); writeMap(out, bundle.getAttributes()); } private void writeExportPackageDesc(ExportPackageDescriptionImpl exportPackageDesc, DataOutputStream out) throws IOException { if (writePrefix(exportPackageDesc, out)) return; writeBaseDescription(exportPackageDesc, out); writeBundleDescription(exportPackageDesc.getExporter(), out, false); writeMap(out, exportPackageDesc.getAttributes()); writeMap(out, exportPackageDesc.getDirectives()); writeExportPackageDesc((ExportPackageDescriptionImpl) exportPackageDesc.getFragmentDeclaration(), out); } private void writeGenericDescription(GenericDescription description, DataOutputStream out) throws IOException { if (writePrefix(description, out)) return; writeBaseDescription(description, out); writeBundleDescription(description.getSupplier(), out, false); writeStringOrNull(description.getType() == GenericDescription.DEFAULT_TYPE ? null : description.getType(), out); Dictionary<String, Object> attrs = description.getAttributes(); Map<String, Object> mapAttrs = new HashMap<String, Object>(attrs.size()); for (Enumeration<String> keys = attrs.keys(); keys.hasMoreElements();) { String key = keys.nextElement(); mapAttrs.put(key, attrs.get(key)); } writeMap(out, mapAttrs); Map<String, String> directives = description.getDeclaredDirectives(); writeMap(out, directives); writeGenericDescription((GenericDescription) ((BaseDescriptionImpl) description).getFragmentDeclaration(), out); } private void writeGenericSpecification(GenericSpecification specification, DataOutputStream out) throws IOException { if (writePrefix(specification, out)) return; writeVersionConstraint(specification, out); writeStringOrNull(specification.getType() == GenericDescription.DEFAULT_TYPE ? null : specification.getType(), out); GenericDescription[] suppliers = specification.getSuppliers(); out.writeInt(suppliers == null ? 0 : suppliers.length); if (suppliers != null) for (int i = 0; i < suppliers.length; i++) writeGenericDescription(suppliers[i], out); out.writeInt(specification.getResolution()); writeStringOrNull(specification.getMatchingFilter(), out); } private void writeNativeCode(NativeCodeSpecification nativeCodeSpecification, DataOutputStream out) throws IOException { if (nativeCodeSpecification == null) { out.writeBoolean(false); return; } out.writeBoolean(true); out.writeBoolean(nativeCodeSpecification.isOptional()); NativeCodeDescription[] nativeDescs = nativeCodeSpecification.getPossibleSuppliers(); int numDescs = nativeDescs == null ? 0 : nativeDescs.length; out.writeInt(numDescs); int supplierIndex = -1; for (int i = 0; i < numDescs; i++) { if (nativeDescs[i] == nativeCodeSpecification.getSupplier()) supplierIndex = i; writeNativeCodeDescription(nativeDescs[i], out); } out.writeInt(supplierIndex); } private void writeNativeCodeDescription(NativeCodeDescription nativeCodeDescription, DataOutputStream out) throws IOException { writeBaseDescription(nativeCodeDescription, out); writeBundleDescription(nativeCodeDescription.getSupplier(), out, false); Filter filter = nativeCodeDescription.getFilter(); writeStringOrNull(filter == null ? null : filter.toString(), out); writeStringArray(nativeCodeDescription.getLanguages(), out); writeStringArray(nativeCodeDescription.getNativePaths(), out); writeStringArray(nativeCodeDescription.getOSNames(), out); writeVersionRanges(nativeCodeDescription.getOSVersions(), out); writeStringArray(nativeCodeDescription.getProcessors(), out); out.writeBoolean(nativeCodeDescription.hasInvalidNativePaths()); } private void writeVersionRanges(VersionRange[] ranges, DataOutputStream out) throws IOException { out.writeInt(ranges == null ? 0 : ranges.length); if (ranges == null) return; for (int i = 0; i < ranges.length; i++) writeVersionRange(ranges[i], out); } private void writeStringArray(String[] strings, DataOutputStream out) throws IOException { out.writeInt(strings == null ? 0 : strings.length); if (strings == null) return; for (int i = 0; i < strings.length; i++) writeStringOrNull(strings[i], out); } private void writeMap(DataOutputStream out, Map<String, ?> source) throws IOException { if (source == null) { out.writeInt(0); } else { out.writeInt(source.size()); Iterator<String> iter = source.keySet().iterator(); while (iter.hasNext()) { String key = iter.next(); Object value = source.get(key); 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()); } else if (value instanceof Integer) { out.writeByte(3); out.writeInt(((Integer) value).intValue()); } else if (value instanceof Long) { out.writeByte(4); out.writeLong(((Long) value).longValue()); } else if (value instanceof Double) { out.writeByte(5); out.writeDouble(((Double) value).doubleValue()); } else if (value instanceof Version) { out.writeByte(6); writeVersion((Version) value, out); } else if ("java.net.URI".equals(value.getClass().getName())) { //$NON-NLS-1$ out.writeByte(7); writeStringOrNull(value.toString(), out); } else if (value instanceof List) { writeList(out, (List<?>) value); } } } } private void writeList(DataOutputStream out, List<?> list) throws IOException { byte type = getListType(list); if (type == -2) return; // don't understand the list type out.writeByte(8); out.writeByte(type); out.writeInt(list.size()); for (Object value : list) { switch (type) { case 0 : writeStringOrNull((String) value, out); break; case 3 : out.writeInt(((Integer) value).intValue()); break; case 4 : out.writeLong(((Long) value).longValue()); break; case 5 : out.writeDouble(((Double) value).doubleValue()); break; case 6 : writeVersion((Version) value, out); break; case 7 : writeStateWire((StateWire) value, out); default : break; } } } private void writeStateWire(StateWire wire, DataOutputStream out) throws IOException { VersionConstraint requirement = wire.getDeclaredRequirement(); if (requirement instanceof ImportPackageSpecificationImpl) { out.writeByte(0); writeImportPackageSpec((ImportPackageSpecificationImpl) requirement, out); } else if (requirement instanceof BundleSpecificationImpl) { out.writeByte(1); writeBundleSpec((BundleSpecificationImpl) requirement, out); } else if (requirement instanceof HostSpecificationImpl) { out.writeByte(2); writeHostSpec((HostSpecificationImpl) requirement, out, false); } else if (requirement instanceof GenericSpecificationImpl) { out.writeByte(3); writeGenericSpecification((GenericSpecificationImpl) requirement, out); } else throw new IllegalArgumentException("Unknown requiement type: " + requirement.getClass()); BaseDescription capability = wire.getDeclaredCapability(); if (capability instanceof BundleDescription) writeBundleDescription((BundleDescription) capability, out, false); else if (capability instanceof ExportPackageDescriptionImpl) writeExportPackageDesc((ExportPackageDescriptionImpl) capability, out); else if (capability instanceof GenericDescription) writeGenericDescription((GenericDescription) capability, out); else throw new IllegalArgumentException("Unknown capability type: " + requirement.getClass()); writeBundleDescription(wire.getRequirementHost(), out, false); writeBundleDescription(wire.getCapabilityHost(), out, false); } private byte getListType(List<?> list) { if (list.size() == 0) return -1; Object type = list.get(0); if (type instanceof String) return 0; if (type instanceof Integer) return 3; if (type instanceof Long) return 4; if (type instanceof Double) return 5; if (type instanceof Version) return 6; if (type instanceof StateWire) return 7; return -2; } 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 { if (writePrefix(importPackageSpec, out)) return; 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 && force && !forcedWrite.contains(host)) { int index = addToObjectTable(host); out.writeByte(StateReader.OBJECT); out.writeInt(index); forcedWrite.add(host); } else if (writePrefix(host, out)) return; 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); writeMap(out, host.getAttributes()); } // 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); } }