/** * Copyright (c) 2007-2012 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 - Initial API and implementation */ package org.eclipse.emf.ecore.resource.impl; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.emf.common.util.BasicEList; import org.eclipse.emf.common.util.CommonUtil; import org.eclipse.emf.common.util.Enumerator; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EAttribute; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EDataType; import org.eclipse.emf.ecore.EEnum; import org.eclipse.emf.ecore.EFactory; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EPackage; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.ecore.InternalEObject; import org.eclipse.emf.ecore.impl.EFactoryImpl; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.emf.ecore.resource.URIConverter; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.emf.ecore.util.FeatureMap; import org.eclipse.emf.ecore.util.FeatureMapUtil; import org.eclipse.emf.ecore.util.InternalEList; /** * An API for efficiently producing and consuming a compact binary serialization that's suitable for long term storage. * @since 2.4 */ public class BinaryResourceImpl extends ResourceImpl { /** * A save option to specify the {@link Version} to be used for the serialization. * @see Version * @since 2.7 */ public static final String OPTION_VERSION = "VERSION"; /** * A Boolean save option to specify whether float and double values * are encoded using {@link Float#floatToIntBits(float)} and {@link Double#doubleToLongBits(double)} respectively, * rather than a string representation. * The default is true. * This style option is only supported for serializations with {@link Version#VERSION_1_1 version 1.1} or higher. * @see BinaryIO#STYLE_BINARY_FLOATING_POINT * @since 2.7 */ public static final String OPTION_STYLE_BINARY_FLOATING_POINT = "BINARY_FLOATING_POINT "; /** * A Boolean save option to specify whether {@link Date date} values will be serialized using {@link Date#getTime()} rather than a string representation. * This style option is only supported for serializations with {@link Version#VERSION_1_1 version 1.1} or higher. * The default is false. * @see BinaryIO#STYLE_BINARY_DATE * @since 2.7 */ public static final String OPTION_STYLE_BINARY_DATE = "BINARY_DATE"; /** * A Boolean save option to specify whether serialized proxies will include the serialization of their attribute values. * This style option is only supported for serializations with {@link Version#VERSION_1_1 version 1.1} or higher. * The default is false. * @see BinaryIO#STYLE_PROXY_ATTRIBUTES * @since 2.7 */ public static final String OPTION_STYLE_PROXY_ATTRIBUTES = "PROXY_ATTRIBUTES"; /** * A Boolean save option to specify whether {@link Enumerator enumerator} values will be serialized using {@link Enumerator#getValue()} rather than a string representation. * This style option is only supported for serializations with {@link Version#VERSION_1_1 version 1.1} or higher. * The default is false. * @see BinaryIO#STYLE_BINARY_ENUMERATOR * @since 2.8 */ public static final String OPTION_STYLE_BINARY_ENUMERATOR = "BINARY_ENUMERATOR"; /** * A Boolean save option to specify whether values should be serialized using {@link DataConverter data converters}. * In general, a value will use a data converter only if the {@link EFactory factory} of the {@link EDataType data type} * implements its {@link DataConverter.Factory data converter factory} to return a non-null data converter. * If that data converter specifies that values should be {@link DataConverter.isTabulated() tabulated}, * every {@link Object#equals(Object) unique} value will be serialized in full at most once. * Note that String values have specialized built-in data converter support and will hence also be tabulated if this option is specified. * This style option is only supported for serializations with {@link Version#VERSION_1_1 version 1.1} or higher. * The default value is false. * @since 2.9 */ public static final String OPTION_STYLE_DATA_CONVERTER = "DATA_CONVERTER"; /** * A Boolean load option to specify whether proxies should be eagerly resolved during loading. * This can improve subsequent performance by saving the cost of repeatedly resolving the same proxy for each different use of that proxy. * It could also harm performance by forcing the loading of a large number of resources that might never otherwise be loaded. * Consider carefully the trade-offs involved in using this option. * @since 2.9 */ public static final String OPTION_EAGER_PROXY_RESOLUTION = "EAGER_PROXY_RESOLUTION"; /** * Specify the capacity of the buffered stream * used when {@link #doSave(OutputStream, Map) saving} or {@link #doLoad(InputStream, Map) loading} the resource content. * The value must be an integer. * If not specified, {@link #DEFAULT_BUFFER_CAPACITY} is used. * A value less than one disables the cache. * @since 2.6 */ public static final String OPTION_BUFFER_CAPACITY = "BUFFER_CAPACITY"; /** * The default {@link #OPTION_BUFFER_CAPACITY} capacity of the buffered stream * used when {@link #doSave(OutputStream, Map) saving} or {@link #doLoad(InputStream, Map) loading} the resource content. * @since 2.6 */ public static final int DEFAULT_BUFFER_CAPACITY = 1024; /** * Extract the {@link #OPTION_BUFFER_CAPACITY} from the options. * @param options a map of options. * @return the value associated with the {@link #OPTION_BUFFER_CAPACITY} key in the options map. * @since 2.6 */ public static int getBufferCapacity(Map<?, ?> options) { if (options != null) { Integer capacity = (Integer)options.get(OPTION_BUFFER_CAPACITY); if (capacity != null) { return capacity; } } return DEFAULT_BUFFER_CAPACITY; } /** * Specify the capacity of the internal byte array used in {@link EObjectInputStream} or {@link EObjectOutputStream} for buffering the reading and writing of bytes. * Note that this implies that {@link EObjectInputStream} will read more bytes from the input stream than it may actually consume, * and that {@link EObjectOutputStream } will write more bytes to the byte array than have actually been produced in the output stream. * In both cases it's necessary to call flush * either to put back the unused bytes, which is only possible if the wrapped input stream {@link InputStream#markSupported() supports marks} * or to emit the retained bytes. * A value less then 2 disables internal buffering; the default is 0. * @since 2.9 */ public static final String OPTION_INTERNAL_BUFFER_CAPACITY = "INTERNAL_BUFFER_CAPACITY"; /** * Extract the {@link #OPTION_INTERNAL_BUFFER_CAPACITY} from the options. * @param options a map of options. * @return the value associated with the {@link #OPTION_INTERNAL_BUFFER_CAPACITY} key in the options map. * @since 2.9 */ public static int getInternalBufferCapacity(Map<?, ?> options) { if (options != null) { Integer capacity = (Integer)options.get(OPTION_INTERNAL_BUFFER_CAPACITY); if (capacity != null) { return capacity; } } return 0; } public BinaryResourceImpl() { super(); } public BinaryResourceImpl(URI uri) { super(uri); } @Override protected void doSave(OutputStream outputStream, Map<?, ?> options) throws IOException { if (outputStream instanceof URIConverter.Saveable) { ((URIConverter.Saveable)outputStream).saveResource(this); } else { boolean buffer = !(outputStream instanceof BufferedOutputStream); if (buffer) { int bufferCapacity = getBufferCapacity(options); if (bufferCapacity > 0) { outputStream = new BufferedOutputStream(outputStream, bufferCapacity); } else { buffer = false; } } try { EObjectOutputStream eObjectOutputStream = createEObjectOutputStream(outputStream, options); eObjectOutputStream.saveResource(this); eObjectOutputStream.flush(); } finally { if (buffer) { outputStream.flush(); } } } } /** * @since 2.9 */ protected EObjectOutputStream createEObjectOutputStream(OutputStream outputStream, Map<?, ?> options) throws IOException { return new EObjectOutputStream(outputStream, options); } @Override protected void doLoad(InputStream inputStream, Map<?, ?> options) throws IOException { if (inputStream instanceof URIConverter.Loadable) { ((URIConverter.Loadable)inputStream).loadResource(this); } else { if (!(inputStream instanceof BufferedInputStream)) { int bufferCapacity = getBufferCapacity(options); if (bufferCapacity > 0) { inputStream = new BufferedInputStream(inputStream, bufferCapacity); } } EObjectInputStream eObjectInputStream = createEObjectInputStream(inputStream, options); eObjectInputStream.loadResource(this); eObjectInputStream.flush(); } } /** * @since 2.9 */ protected EObjectInputStream createEObjectInputStream(InputStream inputStream, Map<?, ?> options) throws IOException { return new EObjectInputStream(inputStream, options); } /** * Generally this abstract class is extended as a stateless singleton returned by a generated factory that implements the {@link DataConverter#Factory factory} interface. * The default implementation of {@link EFactoryImpl#create(EDataType)} returns <code>null</code>. * @since 2.9 */ public static abstract class DataConverter<T> { static final DataConverter<Object> NULL = new DataConverter<Object>() { @Override public Object read(EObjectInputStream eObjectInputStream) throws IOException { throw new UnsupportedOperationException(); } @Override protected void doWrite(EObjectOutputStream eObjectOutputStream, Object value) throws IOException { throw new UnsupportedOperationException(); } }; /** * @see EFactoryImpl#create(EDataType) */ public interface Factory { DataConverter<?> create(EDataType eDataType); } public boolean isTabulated() { return true; } public abstract T read(EObjectInputStream eObjectInputStream) throws IOException; @SuppressWarnings("unchecked") public void write(EObjectOutputStream eObjectOutputStream, Object value) throws IOException { doWrite(eObjectOutputStream, (T)value); } protected abstract void doWrite(EObjectOutputStream eObjectOutputStream, T value) throws IOException; } public static class BinaryIO { public enum Version { VERSION_1_0, /** * This version supports styles. * An extra integer value encoding the style is written after the version number so that deserialization will respect the styles used during serialization. * @since 2.7 */ VERSION_1_1 } /** * @see BinaryResourceImpl#OPTION_STYLE_BINARY_FLOATING_POINT * @since 2.7 */ public static final int STYLE_BINARY_FLOATING_POINT = 1 << 0; /** * @see BinaryResourceImpl#OPTION_STYLE_BINARY_DATE * @since 2.7 */ public static final int STYLE_BINARY_DATE = 1 << 1; /** * @see BinaryResourceImpl#OPTION_STYLE_PROXY_ATTRIBUTES * @since 2.7 */ public static final int STYLE_PROXY_ATTRIBUTES = 1 << 2; /** * @see BinaryResourceImpl#OPTION_STYLE_BINARY_ENUMERATOR * @since 2.8 */ public static final int STYLE_BINARY_ENUMERATOR = 1 << 3; /** * @see BinaryResourceImpl#OPTION_STYLE_DATA_CONVERTER * @since 2.9 */ public static final int STYLE_DATA_CONVERTER = 1 << 4; protected Version version; /** * @since 2.7 */ protected int style; protected Resource resource; protected URI baseURI; protected Map<?, ?> options; protected char[] characters; protected InternalEObject[][] internalEObjectDataArrayBuffer = new InternalEObject[50][]; protected int internalEObjectDataArrayBufferCount = -1; Map<EDataType, DataConverter<?>> dataConverterMap = new HashMap<EDataType, DataConverter<?>>(); protected static int getStyle(Map<?, ?> options) { int result = STYLE_BINARY_FLOATING_POINT; if (options != null) { if (Boolean.FALSE.equals(options.get(OPTION_STYLE_BINARY_FLOATING_POINT))) { result &= ~STYLE_BINARY_FLOATING_POINT; } if (Boolean.TRUE.equals(options.get(OPTION_STYLE_BINARY_DATE))) { result |= STYLE_BINARY_DATE; } if (Boolean.TRUE.equals(options.get(OPTION_STYLE_PROXY_ATTRIBUTES))) { result |= STYLE_PROXY_ATTRIBUTES; } if (Boolean.TRUE.equals(options.get(OPTION_STYLE_BINARY_ENUMERATOR))) { result |= STYLE_BINARY_ENUMERATOR; } if (Boolean.TRUE.equals(options.get(OPTION_STYLE_DATA_CONVERTER))) { result |= STYLE_DATA_CONVERTER; } } return result; } protected URI resolve(URI uri) { return baseURI != null && uri.isRelative() && uri.hasRelativePath() ? uri.resolve(baseURI) : uri; } protected URI deresolve(URI uri) { if (baseURI != null && !uri.isRelative()) { URI deresolvedURI = uri.deresolve(baseURI, true, true, false); if (deresolvedURI.hasRelativePath() && (!uri.isPlatform() || uri.segment(0).equals(baseURI.segment(0)))) { uri = deresolvedURI; } } return uri; } protected InternalEObject [] allocateInternalEObjectArray(int length) { if (internalEObjectDataArrayBufferCount == -1) { return new InternalEObject[length]; } else { InternalEObject [] buffer = internalEObjectDataArrayBuffer[internalEObjectDataArrayBufferCount]; internalEObjectDataArrayBuffer[internalEObjectDataArrayBufferCount--] = null; return buffer.length >= length ? buffer : new InternalEObject[length]; } } protected void recycle(InternalEObject[] values) { if (++internalEObjectDataArrayBufferCount >= internalEObjectDataArrayBuffer.length) { InternalEObject [][] newInternalEObjectDataArrayBuffer = new InternalEObject[internalEObjectDataArrayBufferCount * 2][]; System.arraycopy(internalEObjectDataArrayBuffer, 0, newInternalEObjectDataArrayBuffer, 0, internalEObjectDataArrayBufferCount); internalEObjectDataArrayBuffer = newInternalEObjectDataArrayBuffer; } internalEObjectDataArrayBuffer[internalEObjectDataArrayBufferCount] = values; } protected FeatureMap.Entry.Internal[][] featureMapEntryDataArrayBuffer = new FeatureMap.Entry.Internal[50][]; protected int featureMapEntryDataArrayBufferCount = -1; protected FeatureMap.Entry.Internal [] allocateFeatureMapEntryArray(int length) { if (featureMapEntryDataArrayBufferCount == -1) { return new FeatureMap.Entry.Internal[length]; } else { FeatureMap.Entry.Internal [] buffer = featureMapEntryDataArrayBuffer[featureMapEntryDataArrayBufferCount]; featureMapEntryDataArrayBuffer[featureMapEntryDataArrayBufferCount--] = null; return buffer.length >= length ? buffer : new FeatureMap.Entry.Internal[length]; } } protected void recycle(FeatureMap.Entry.Internal[] values) { if (++featureMapEntryDataArrayBufferCount >= featureMapEntryDataArrayBuffer.length) { FeatureMap.Entry.Internal [][] newFeatureMapEntryDataArrayBuffer = new FeatureMap.Entry.Internal[featureMapEntryDataArrayBufferCount * 2][]; System.arraycopy(featureMapEntryDataArrayBuffer, 0, newFeatureMapEntryDataArrayBuffer, 0, featureMapEntryDataArrayBufferCount); featureMapEntryDataArrayBuffer = newFeatureMapEntryDataArrayBuffer; } featureMapEntryDataArrayBuffer[featureMapEntryDataArrayBufferCount] = values; } protected enum FeatureKind { EOBJECT_CONTAINER, EOBJECT_CONTAINER_PROXY_RESOLVING, EOBJECT, EOBJECT_PROXY_RESOLVING, EOBJECT_LIST, EOBJECT_LIST_PROXY_RESOLVING, EOBJECT_CONTAINMENT, EOBJECT_CONTAINMENT_PROXY_RESOLVING, EOBJECT_CONTAINMENT_LIST, EOBJECT_CONTAINMENT_LIST_PROXY_RESOLVING, BOOLEAN, BYTE, CHAR, DOUBLE, FLOAT, INT, LONG, SHORT, STRING, /** * @since 2.7 */ DATE, /** * @since 2.8 */ ENUMERATOR, DATA, DATA_LIST, FEATURE_MAP; public static FeatureKind get(EStructuralFeature eStructuralFeature) { if (eStructuralFeature instanceof EReference) { EReference eReference = (EReference)eStructuralFeature; if (eReference.isContainment()) { if (eReference.isResolveProxies()) { if (eReference.isMany()) { return EOBJECT_CONTAINMENT_LIST_PROXY_RESOLVING; } else { return EOBJECT_CONTAINMENT_PROXY_RESOLVING; } } else { if (eReference.isMany()) { return EOBJECT_CONTAINMENT_LIST; } else { return EOBJECT_CONTAINMENT; } } } else if (eReference.isContainer()) { if (eReference.isResolveProxies()) { return EOBJECT_CONTAINER_PROXY_RESOLVING; } else { return EOBJECT_CONTAINER; } } else if (eReference.isResolveProxies()) { if (eReference.isMany()) { return EOBJECT_LIST_PROXY_RESOLVING; } else { return EOBJECT_PROXY_RESOLVING; } } else { if (eReference.isMany()) { return EOBJECT_LIST; } else { return EOBJECT; } } } else { EAttribute eAttribute = (EAttribute)eStructuralFeature; EDataType eDataType = eAttribute.getEAttributeType(); String instanceClassName = eDataType.getInstanceClassName(); if (instanceClassName == "org.eclipse.emf.ecore.util.FeatureMap$Entry") { return FEATURE_MAP; } else if (eAttribute.isMany()) { return DATA_LIST; } else if (instanceClassName == "java.lang.String") { return STRING; } else if (instanceClassName == "boolean") { return BOOLEAN; } else if (instanceClassName == "byte") { return BYTE; } else if (instanceClassName == "char") { return CHAR; } else if (instanceClassName == "double") { return DOUBLE; } else if (instanceClassName == "float") { return FLOAT; } else if (instanceClassName == "int") { return INT; } else if (instanceClassName == "long") { return LONG; } else if (instanceClassName == "short") { return SHORT; } else if (instanceClassName == "java.util.Date") { return DATE; } else if (eDataType instanceof EEnum) { return ENUMERATOR; } else { return DATA; } } } } static final int MAX_DELIMITER = 0xC0; static final String[] DELIMITERS = new String[MAX_DELIMITER]; static final List<String> INTRINSIC_STRINGS = new ArrayList<String>(); static final Map<String, Integer> INTRINSIC_STRING_TO_ID_MAP = new HashMap<String, Integer>(); static { INTRINSIC_STRING_TO_ID_MAP.put("", 0); INTRINSIC_STRINGS.add(""); for (char c = 0; c < MAX_DELIMITER; ++c) { if (Character.isDigit(c)) { String string = CommonUtil.javaIntern(Character.toString(c)); INTRINSIC_STRING_TO_ID_MAP.put(string, INTRINSIC_STRINGS.size()); INTRINSIC_STRINGS.add(string); } else if (!Character.isLetter(c)) { String delimiter = CommonUtil.javaIntern(Character.toString(c)); DELIMITERS[c] = delimiter; INTRINSIC_STRING_TO_ID_MAP.put(delimiter, INTRINSIC_STRINGS.size()); INTRINSIC_STRINGS.add(delimiter); } } } } public static class EObjectOutputStream extends BinaryIO { public enum Check { NOTHING, DIRECT_RESOURCE, RESOURCE, CONTAINER } protected static class EPackageData { public int id; public EClassData[] eClassData; public final int allocateEClassID() { for (int i = 0, length = eClassData.length; i < length; ++i) { EClassData eClassData = this.eClassData[i]; if (eClassData == null) { return i; } } return -1; } } protected static class EClassData { public int ePackageID; public int id; public EStructuralFeatureData[] eStructuralFeatureData; } protected static class EStructuralFeatureData { public String name; public boolean isTransient; /** * @since 2.7 */ public boolean isProxyTransient; public FeatureKind kind; public EFactory eFactory; public EDataType eDataType; /** * @since 2.9 */ public DataConverter<?> dataConverter; } private byte[] bytes; private int index; protected OutputStream outputStream; protected Map<EPackage, EPackageData> ePackageDataMap = new HashMap<EPackage, EPackageData>(); protected Map<EClass, EClassData> eClassDataMap = new HashMap<EClass, EClassData>(); protected Map<EObject, Integer> eObjectIDMap = new HashMap<EObject, Integer>(); protected Map<URI, Integer> uriToIDMap = new HashMap<URI, Integer>(); private Map<String, Integer> segmentedStringToIDMap; private Map<String, Integer> segmentToIDMap; private String[] segments; public EObjectOutputStream(OutputStream outputStream, Map<?, ?> options) throws IOException { this(outputStream, options, options != null && options.containsKey(OPTION_VERSION)? (Version)options.get(OPTION_VERSION) : Version.VERSION_1_0); } public EObjectOutputStream(OutputStream outputStream, Map<?, ?> options, Version version) throws IOException { this(outputStream, options, version, version.ordinal() > 0 ? getStyle(options) : STYLE_BINARY_FLOATING_POINT); } /** * @since 2.7 */ public EObjectOutputStream(OutputStream outputStream, Map<?, ?> options, Version version, int style) throws IOException { this.outputStream = outputStream; this.options = options; this.version = version; this.style = style; int bufferCapacity = getInternalBufferCapacity(options); if (bufferCapacity > 1) { bytes = new byte[bufferCapacity]; } writeSignature(); writeVersion(); if (version.ordinal() > 0) { writeStyle(); } if ((style & STYLE_DATA_CONVERTER) != 0) { segmentedStringToIDMap = new HashMap<String, Integer>(); segmentToIDMap = new HashMap<String, Integer>(INTRINSIC_STRING_TO_ID_MAP); } } protected void writeSignature() throws IOException { // Write a signature that will be obviously corrupt // if the binary contents end up being UTF-8 encoded // or altered by line feed or carriage return changes. // writeByte('\211'); writeByte('e'); writeByte('m'); writeByte('f'); writeByte('\n'); writeByte('\r'); writeByte('\032'); writeByte('\n'); } protected void writeVersion() throws IOException { writeByte(version.ordinal()); } /** * @since 2.7 */ protected void writeStyle() throws IOException { writeInt(style); } protected EPackageData writeEPackage(EPackage ePackage) throws IOException { EPackageData ePackageData = ePackageDataMap.get(ePackage); if (ePackageData == null) { ePackageData = new EPackageData(); int id = ePackageDataMap.size(); ePackageData.id = id; ePackageData.eClassData = new EClassData[ePackage.getEClassifiers().size()]; writeCompressedInt(id); writeSegmentedString(ePackage.getNsURI()); writeURI(EcoreUtil.getURI(ePackage)); ePackageDataMap.put(ePackage, ePackageData); } else { writeCompressedInt(ePackageData.id); } return ePackageData; } protected EClassData writeEClass(EClass eClass) throws IOException { EClassData eClassData = eClassDataMap.get(eClass); if (eClassData == null) { eClassData = new EClassData(); EPackageData ePackageData = writeEPackage(eClass.getEPackage()); eClassData.ePackageID = ePackageData.id; writeCompressedInt(eClassData.id = ePackageData.allocateEClassID()); writeString(eClass.getName()); int featureCount = eClass.getFeatureCount(); EStructuralFeatureData [] eStructuralFeaturesData = eClassData.eStructuralFeatureData = new EStructuralFeatureData[featureCount]; for (int i = 0; i < featureCount; ++i) { EStructuralFeatureData eStructuralFeatureData = eStructuralFeaturesData[i] = new EStructuralFeatureData(); EStructuralFeature.Internal eStructuralFeature = (EStructuralFeature.Internal)eClass.getEStructuralFeature(i); eStructuralFeatureData.name = eStructuralFeature.getName(); eStructuralFeatureData.isTransient = eStructuralFeature.isTransient() || eStructuralFeature.isContainer() && !eStructuralFeature.isResolveProxies(); eStructuralFeatureData.kind = FeatureKind.get(eStructuralFeature); if (eStructuralFeature instanceof EAttribute) { EAttribute eAttribute = (EAttribute)eStructuralFeature; EDataType eDataType = eAttribute.getEAttributeType(); eStructuralFeatureData.eDataType = eDataType; eStructuralFeatureData.eFactory = eDataType.getEPackage().getEFactoryInstance(); eStructuralFeatureData.isProxyTransient = eStructuralFeatureData.kind == FeatureKind.FEATURE_MAP; if (segmentedStringToIDMap != null) { DataConverter<?> dataConverter = dataConverterMap.get(eDataType); if (dataConverter != DataConverter.NULL) { if (dataConverter == null) { final DataConverter<?> rawDataConverter = ((DataConverter.Factory)eStructuralFeatureData.eFactory).create(eDataType); if (rawDataConverter == null) { dataConverterMap.put(eDataType, DataConverter.NULL); } else { if (rawDataConverter.isTabulated()) { DataConverter<?> tabulatedDataConverter = new DataConverter<Object>() { Map<Object, Integer> objectToIDMap = new HashMap<Object, Integer>(); @Override public Object read(EObjectInputStream eObjectInputStream) throws IOException { throw new UnsupportedOperationException(); } @Override public void write(EObjectOutputStream eObjectOutputStream, Object value) throws IOException { if (value == null) { eObjectOutputStream.writeCompressedInt(-1); } else { Integer id = objectToIDMap.get(value); if (id == null) { int idValue = objectToIDMap.size(); objectToIDMap.put(value, idValue); eObjectOutputStream.writeCompressedInt(idValue); super.write(eObjectOutputStream, value); } else { eObjectOutputStream.writeCompressedInt(id); } } } @Override protected void doWrite(EObjectOutputStream eObjectOutputStream, Object value) throws IOException { rawDataConverter.write(eObjectOutputStream, value); } }; dataConverter = tabulatedDataConverter; } else { dataConverter = rawDataConverter; } dataConverterMap.put(eDataType, dataConverter); } } eStructuralFeatureData.dataConverter = dataConverter; } } } else { eStructuralFeatureData.isProxyTransient = true; } } ePackageData.eClassData[eClassData.id] = eClassData; eClassDataMap.put(eClass, eClassData); } else { writeCompressedInt(eClassData.ePackageID); writeCompressedInt(eClassData.id); } return eClassData; } protected EStructuralFeatureData writeEStructuralFeature(EStructuralFeature eStructuralFeature) throws IOException { EClass eClass = eStructuralFeature.getEContainingClass(); EClassData eClassData = writeEClass(eClass); int featureID = eClass.getFeatureID(eStructuralFeature); EStructuralFeatureData eStructuralFeatureData = eClassData.eStructuralFeatureData[featureID]; writeCompressedInt(featureID); if (eStructuralFeatureData.name != null) { writeString(eStructuralFeatureData.name); if (segmentedStringToIDMap != null && eStructuralFeatureData.eDataType != null) { writeBoolean(eStructuralFeatureData.dataConverter != null); } eStructuralFeatureData.name = null; } return eStructuralFeatureData; } public void saveResource(Resource resource) throws IOException { this.resource = resource; URI uri = resource.getURI(); if (uri != null && uri.isHierarchical() && !uri.isRelative()) { baseURI = uri; } @SuppressWarnings("unchecked") InternalEList<? extends InternalEObject> internalEList = (InternalEList<? extends InternalEObject>)(InternalEList<?>)resource.getContents(); saveEObjects(internalEList, Check.CONTAINER); } public void saveEObjects(InternalEList<? extends InternalEObject> internalEObjects, Check check) throws IOException { int size = internalEObjects.size(); InternalEObject [] values = allocateInternalEObjectArray(size); internalEObjects.basicToArray(values); writeCompressedInt(size); for (int i = 0; i < size; ++i) { InternalEObject internalEObject = values[i]; saveEObject(internalEObject, check); } recycle(values); } public void saveFeatureMap(FeatureMap.Internal featureMap) throws IOException { int size = featureMap.size(); FeatureMap.Entry.Internal [] values = allocateFeatureMapEntryArray(size); featureMap.toArray(values); writeCompressedInt(size); for (int i = 0; i < size; ++i) { FeatureMap.Entry.Internal entry = values[i]; saveFeatureMapEntry(entry); } recycle(values); } public void saveFeatureMapEntry(FeatureMap.Entry.Internal entry) throws IOException { EStructuralFeatureData eStructuralFeatureData = writeEStructuralFeature(entry.getEStructuralFeature()); Object value = entry.getValue(); switch (eStructuralFeatureData.kind) { case EOBJECT: case EOBJECT_LIST: case EOBJECT_CONTAINMENT: case EOBJECT_CONTAINMENT_LIST: { saveEObject((InternalEObject)value, Check.NOTHING); break; } case EOBJECT_CONTAINMENT_PROXY_RESOLVING: case EOBJECT_CONTAINMENT_LIST_PROXY_RESOLVING: { saveEObject((InternalEObject)value, Check.DIRECT_RESOURCE); break; } case EOBJECT_PROXY_RESOLVING: case EOBJECT_LIST_PROXY_RESOLVING: { saveEObject((InternalEObject)value, Check.RESOURCE); break; } case BOOLEAN: { writeBoolean((Boolean)value); break; } case BYTE: { writeByte((Byte)value); break; } case CHAR: { writeChar((Character)value); break; } case DOUBLE: { writeDouble((Double)value); break; } case FLOAT: { writeFloat((Float)value); break; } case INT: { writeInt((Integer)value); break; } case LONG: { writeLong((Long)value); break; } case SHORT: { writeShort((Short)value); break; } case STRING: { writeSegmentedString((String)value); break; } case DATE: { if (eStructuralFeatureData.dataConverter != null) { eStructuralFeatureData.dataConverter.write(this, value); } else if ((style & STYLE_BINARY_DATE) != 0) { writeDate((Date)value); } else { writeSegmentedString(eStructuralFeatureData.eFactory.convertToString(eStructuralFeatureData.eDataType, value)); } break; } case ENUMERATOR: { if (eStructuralFeatureData.dataConverter != null) { eStructuralFeatureData.dataConverter.write(this, value); } else if ((style & STYLE_BINARY_ENUMERATOR) != 0) { writeInt(((Enumerator)value).getValue()); } else { writeString(eStructuralFeatureData.eFactory.convertToString(eStructuralFeatureData.eDataType, value)); } break; } case DATA: case DATA_LIST: { if (eStructuralFeatureData.dataConverter != null) { eStructuralFeatureData.dataConverter.write(this, value); } else { String literal = eStructuralFeatureData.eFactory.convertToString(eStructuralFeatureData.eDataType, value); writeSegmentedString(literal); } break; } default: { throw new IOException("Unhandled case " + eStructuralFeatureData.kind); } } } public void saveEObject(InternalEObject internalEObject, Check check) throws IOException { if (internalEObject == null) { writeCompressedInt(-1); } else { Integer id = eObjectIDMap.get(internalEObject); if (id == null) { int idValue = eObjectIDMap.size(); writeCompressedInt(idValue); eObjectIDMap.put(internalEObject, idValue); EClass eClass = internalEObject.eClass(); EClassData eClassData = writeEClass(eClass); boolean checkIsTransientProxy = false; switch (check) { case DIRECT_RESOURCE: { Internal resource = internalEObject.eDirectResource(); if (resource != null) { writeCompressedInt(-1); writeURI(resource.getURI(), resource.getURIFragment(internalEObject)); if ((style & STYLE_PROXY_ATTRIBUTES) == 0) { return; } checkIsTransientProxy = true; } else if (internalEObject.eIsProxy()) { writeCompressedInt(-1); writeURI(internalEObject.eProxyURI()); if ((style & STYLE_PROXY_ATTRIBUTES) == 0) { return; } checkIsTransientProxy = true; } break; } case RESOURCE: { Resource resource = internalEObject.eResource(); if (resource != this.resource && resource != null) { writeCompressedInt(-1); writeURI(resource.getURI(), resource.getURIFragment(internalEObject)); if ((style & STYLE_PROXY_ATTRIBUTES) == 0) { return; } checkIsTransientProxy = true; } else if (internalEObject.eIsProxy()) { writeCompressedInt(-1); writeURI(internalEObject.eProxyURI()); if ((style & STYLE_PROXY_ATTRIBUTES) == 0) { return; } checkIsTransientProxy = true; } break; } case NOTHING: case CONTAINER: { break; } } EStructuralFeatureData [] eStructuralFeatureData = eClassData.eStructuralFeatureData; for (int i = 0, length = eStructuralFeatureData.length; i < length; ++i) { EStructuralFeatureData structuralFeatureData = eStructuralFeatureData[i]; if (!structuralFeatureData.isTransient && (structuralFeatureData.kind != FeatureKind.EOBJECT_CONTAINER_PROXY_RESOLVING || check == Check.CONTAINER) && (!checkIsTransientProxy || !structuralFeatureData.isProxyTransient)) { saveFeatureValue(internalEObject, i, structuralFeatureData); } } writeCompressedInt(0); } else { writeCompressedInt(id); } } } protected void saveFeatureValue(InternalEObject internalEObject, int featureID, EStructuralFeatureData eStructuralFeatureData) throws IOException { if (internalEObject.eIsSet(featureID)) { writeCompressedInt(featureID + 1); if (eStructuralFeatureData.name != null) { writeString(eStructuralFeatureData.name); if (segmentedStringToIDMap != null && eStructuralFeatureData.eDataType != null) { writeBoolean(eStructuralFeatureData.dataConverter != null); } eStructuralFeatureData.name = null; } Object value = internalEObject.eGet(featureID, false, true); saveFeatureValue(internalEObject, value, featureID, eStructuralFeatureData); } } /** * @since 2.9 */ protected void saveFeatureValue(InternalEObject internalEObject, Object value, int featureID, EStructuralFeatureData eStructuralFeatureData) throws IOException { switch (eStructuralFeatureData.kind) { case EOBJECT: case EOBJECT_CONTAINMENT: { saveEObject((InternalEObject)value, Check.NOTHING); break; } case EOBJECT_CONTAINER_PROXY_RESOLVING: { saveEObject((InternalEObject)value, Check.RESOURCE); break; } case EOBJECT_CONTAINMENT_PROXY_RESOLVING: { saveEObject((InternalEObject)value, Check.DIRECT_RESOURCE); break; } case EOBJECT_PROXY_RESOLVING: { saveEObject((InternalEObject)value, Check.RESOURCE); break; } case EOBJECT_LIST: case EOBJECT_CONTAINMENT_LIST: { @SuppressWarnings("unchecked") InternalEList<? extends InternalEObject> internalEList = (InternalEList<? extends InternalEObject>)value; saveEObjects(internalEList, Check.NOTHING); break; } case EOBJECT_CONTAINMENT_LIST_PROXY_RESOLVING: { @SuppressWarnings("unchecked") InternalEList<? extends InternalEObject> internalEList = (InternalEList<? extends InternalEObject>)value; saveEObjects(internalEList, Check.DIRECT_RESOURCE); break; } case EOBJECT_LIST_PROXY_RESOLVING: { @SuppressWarnings("unchecked") InternalEList<? extends InternalEObject> internalEList = (InternalEList<? extends InternalEObject>)value; saveEObjects(internalEList, Check.RESOURCE); break; } case BOOLEAN: { writeBoolean((Boolean)value); break; } case BYTE: { writeByte((Byte)value); break; } case CHAR: { writeChar((Character)value); break; } case DOUBLE: { writeDouble((Double)value); break; } case FLOAT: { writeFloat((Float)value); break; } case INT: { writeInt((Integer)value); break; } case LONG: { writeLong((Long)value); break; } case SHORT: { writeShort((Short)value); break; } case STRING: { writeSegmentedString((String)value); break; } case FEATURE_MAP: { FeatureMap.Internal featureMap = (FeatureMap.Internal)value; saveFeatureMap(featureMap); break; } case DATE: { if (eStructuralFeatureData.dataConverter != null) { eStructuralFeatureData.dataConverter.write(this, value); } else if ((style & STYLE_BINARY_DATE) != 0) { writeDate((Date)value); } else { writeSegmentedString(eStructuralFeatureData.eFactory.convertToString(eStructuralFeatureData.eDataType, value)); } break; } case ENUMERATOR: { if (eStructuralFeatureData.dataConverter != null) { eStructuralFeatureData.dataConverter.write(this, value); } else if ((style & STYLE_BINARY_ENUMERATOR) != 0) { writeInt(((Enumerator)value).getValue()); } else { writeString(eStructuralFeatureData.eFactory.convertToString(eStructuralFeatureData.eDataType, value)); } break; } case DATA: { if (eStructuralFeatureData.dataConverter != null) { eStructuralFeatureData.dataConverter.write(this, value); } else { String literal = eStructuralFeatureData.eFactory.convertToString(eStructuralFeatureData.eDataType, value); writeSegmentedString(literal); } break; } case DATA_LIST: { List<?> dataValues = (List<?>)value; int length = dataValues.size(); writeCompressedInt(length); if (eStructuralFeatureData.dataConverter != null) { for (int j = 0; j < length; ++j) { eStructuralFeatureData.dataConverter.write(this, dataValues.get(j)); } } else { for (int j = 0; j < length; ++j) { String literal = eStructuralFeatureData.eFactory.convertToString(eStructuralFeatureData.eDataType, dataValues.get(j)); writeSegmentedString(literal); } } break; } default: { throw new IOException("Unhandled case " + eStructuralFeatureData.kind); } } } public void writeByte(int value) throws IOException { if (bytes == null) { outputStream.write(value); } else { if (index == bytes.length) { doFlush(); } bytes[index++] = (byte)value; } } /** * @since 2.9 */ public void flush() throws IOException { if (bytes != null && index > 0) { doFlush(); } } private void doFlush() throws IOException { outputStream.write(bytes, 0, index); index = 0; } public void writeBoolean(boolean value) throws IOException { writeByte(value ? 1 : 0); } public void writeChar(int value) throws IOException { writeByte((byte)(value >> 8 & 0xFF)); writeByte((byte)(value & 0xFF)); } public void writeShort(int value) throws IOException { writeByte((byte)(value >> 8 & 0xFF)); writeByte((byte)(value & 0xFF)); } public void writeInt(int value) throws IOException { writeByte((byte)(value >> 24 & 0xFF)); writeByte((byte)(value >> 16 & 0xFF)); writeByte((byte)(value >> 8 & 0xFF)); writeByte((byte)(value & 0xFF)); } public void writeLong(long value) throws IOException { writeInt((int)(value >> 32)); writeInt((int)value); } public void writeFloat(float value) throws IOException { if ((style & STYLE_BINARY_FLOATING_POINT) != 0) { writeInt(Float.floatToIntBits(value)); } else { writeString(Float.toString(value)); } } public void writeDouble(double value) throws IOException { if ((style & STYLE_BINARY_FLOATING_POINT) != 0) { writeLong(Double.doubleToLongBits(value)); } else { writeString(Double.toString(value)); } } public void writeCompressedInt(int value) throws IOException { ++value; if ( value < 0) { handleInvalidValue(value); } else if (value <= 0x3F) { writeByte(value); } else if (value <= 0x3FFF) { writeByte(value >> 8 | 0x40); writeByte(value & 0xFF); } else if (value <= 0x3FFFFF) { writeByte(value >> 16 | 0x80); writeByte(value >> 8 & 0xFF); writeByte(value & 0xFF); } else if (value <= 0x3FFFFFFF) { writeByte(value >> 24 | 0xC0); writeByte(value >> 16 & 0xFF); writeByte(value >> 8 & 0xFF); writeByte(value & 0xFF); } else { handleInvalidValue(value); } } private final void handleInvalidValue(int value) throws IOException { throw new IOException("Invalid value " + value); } private void ensureSegmentCapacity(int capacity) { if (segments == null) { segments = new String [Math.max(20, capacity)]; } else if (segments.length < capacity) { String[] newSegments = new String [Math.max(2 * segments.length, capacity)]; System.arraycopy(segments, 0, newSegments, 0, segments.length); segments = newSegments; } } /** * @since 2.9 */ public void writeSegmentedString(String value) throws IOException { if (segmentedStringToIDMap == null) { writeString(value); } else if (value == null) { writeCompressedInt(-1); } else { Integer id = segmentedStringToIDMap.get(value); if (id == null) { int idValue = segmentedStringToIDMap.size(); segmentedStringToIDMap.put(value, idValue); writeCompressedInt(idValue); int segmentCount = 0; int start = 0; int end = value.length(); for (int i = 0; i < end; ++i) { char c = value.charAt(i); if (c < MAX_DELIMITER) { String delimiter = DELIMITERS[c]; if (delimiter != null) { ensureSegmentCapacity(segmentCount + 2); if (start < i) { segments[segmentCount++] = value.substring(start, i); } segments[segmentCount++] = delimiter; start = i + 1; } } } if (start == 0 || segmentCount == 1 && start == end) { writeCompressedInt(0); writeString(value); } else { if (start < end) { ensureSegmentCapacity(segmentCount + 1); segments[segmentCount++] = value.substring(start, end); } writeCompressedInt(segmentCount); for (int i = 0; i < segmentCount; ++i) { writeString(segments[i]); } } } else { writeCompressedInt(id); } } } public void writeString(String value) throws IOException { if (value == null) { writeCompressedInt(-1); } else { if (segmentToIDMap != null) { Integer id = segmentToIDMap.get(value); if (id != null) { writeCompressedInt(id); return; } else { int idValue = segmentToIDMap.size(); segmentToIDMap.put(value, idValue); writeCompressedInt(idValue); } } int length = value.length(); writeCompressedInt(length); if (characters == null || characters.length < length) { characters = new char[length]; } value.getChars(0, length, characters, 0); LOOP: for (int i = 0; i < length; ++i) { char character = characters[i]; if (character == 0 || character > 0xFF) { writeByte((byte)0); writeChar(character); while (++i < length) { writeChar(characters[i]); } break LOOP; } else { writeByte((byte)character); } } } } public void writeDate(Date date) throws IOException { writeLong(date.getTime()); } public void writeURI(URI uri) throws IOException { writeURI(uri.trimFragment(), uri.fragment()); } public void writeURI(URI uri, String fragment) throws IOException { if (uri == null) { writeCompressedInt(-1); } else { assert uri.fragment() == null; Integer id = uriToIDMap.get(uri); if (id == null) { int idValue = uriToIDMap.size(); uriToIDMap.put(uri, idValue); writeCompressedInt(idValue); writeSegmentedString(deresolve(uri).toString()); } else { writeCompressedInt(id); } writeSegmentedString(fragment); } } } public static class EObjectInputStream extends BinaryIO { protected static class EPackageData { public EPackage ePackage; public EClassData[] eClassData; public final int allocateEClassID() { for (int i = 0, length = eClassData.length; i < length; ++i) { EClassData eClassData = this.eClassData[i]; if (eClassData == null) { return i; } } return -1; } } protected static class EClassData { public EClass eClass; public EFactory eFactory; public EStructuralFeatureData[] eStructuralFeatureData; } protected static class EStructuralFeatureData { public int featureID; public EStructuralFeature eStructuralFeature; public FeatureKind kind; public EFactory eFactory; public EDataType eDataType; /** * @since 2.9 */ public DataConverter<?> dataConverter; } private static class InternalEObjectList extends BasicEList<InternalEObject> { private static final long serialVersionUID = 1L; public InternalEObject[] eObjects; public InternalEObjectList() { super(1000); } @Override protected Object[] newData(int capacity) { return eObjects = new InternalEObject[capacity]; } @Override public final boolean add(InternalEObject object) { if (size == eObjects.length) { grow(size + 1); } eObjects[size++] = object; return true; } } private static class StringList extends BasicEList<String> { private static final long serialVersionUID = 1L; public String[] strings; public StringList() { super(1000); } @Override protected Object[] newData(int capacity) { return strings = new String[capacity]; } @Override public final boolean add(String object) { if (size == strings.length) { grow(size + 1); } strings[size++] = object; return true; } } private static class URIList extends BasicEList<URI> { private static final long serialVersionUID = 1L; public URI[] uris; public URIList() { super(1000); } @Override protected Object[] newData(int capacity) { return uris = new URI[capacity]; } @Override public final boolean add(URI object) { if (size == uris.length) { grow(size + 1); } uris[size++] = object; return true; } } private static class DataValueList extends BasicEList<Object> { private static final long serialVersionUID = 1L; public Object[] values; public DataValueList() { super(1000); } @Override protected Object[] newData(int capacity) { return values = new Object[capacity]; } @Override public final boolean add(Object object) { if (size == data.length) { grow(size + 1); } data[size++] = object; return true; } } private static class EPackageDataList extends BasicEList<EPackageData> { private static final long serialVersionUID = 1L; public EPackageData[] ePackageData; public EPackageDataList() { super(20); } @Override protected Object[] newData(int capacity) { return ePackageData = new EPackageData[capacity]; } @Override public final boolean add(EPackageData object) { if (size == data.length) { grow(size + 1); } ePackageData[size++] = object; return true; } } private boolean isMarkSupported; private byte[] bytes; private int index; private int count; protected ResourceSet resourceSet; protected InputStream inputStream; private EPackageDataList internalEPackageDataList = new EPackageDataList(); protected List<EPackageData> ePackageDataList = internalEPackageDataList; private InternalEObjectList internalInternalEObjectList = new InternalEObjectList(); protected List<InternalEObject> eObjectList = internalInternalEObjectList; private URIList internalURIList = new URIList(); protected List<URI> uriList = internalURIList; protected BasicEList<InternalEObject> internalEObjectList = new BasicEList<InternalEObject>(); protected BasicEList<Object> dataValueList = new BasicEList<Object>(); private final StringList segmentedStringsList; private final StringList segmentsList; private char[] builder; /** * @since 2.9 */ protected boolean isEagerProxyResolution; public EObjectInputStream(InputStream inputStream, Map<?, ?> options) throws IOException { this.inputStream = inputStream; this.options = options; int bufferCapacity = getInternalBufferCapacity(options); if (bufferCapacity > 1) { bytes = new byte[bufferCapacity]; } isMarkSupported = inputStream.markSupported(); if (options != null) { isEagerProxyResolution = Boolean.TRUE.equals(options.get(OPTION_EAGER_PROXY_RESOLUTION)); } readSignature(); readVersion(); if (version.ordinal() > 0) { readStyle(); if ((style & STYLE_DATA_CONVERTER) != 0) { segmentedStringsList = new StringList(); segmentsList = new StringList(); segmentsList.addAllUnique(INTRINSIC_STRINGS); builder = new char[200]; } else { segmentedStringsList = null; segmentsList = null; } } else { style = STYLE_BINARY_FLOATING_POINT; segmentedStringsList = null; segmentsList = null; } } protected void readSignature() throws IOException { if (readByte() != (byte)'\211' || readByte() != 'e' || readByte() != 'm' || readByte() != 'f' || readByte() != '\n' || readByte() != '\r' || readByte() != '\032' || readByte() != '\n') { throw new IOException("Invalid signature for a binary EMF serialization"); } } protected void readVersion() throws IOException { version = Version.values()[readByte()]; } protected void readStyle() throws IOException { style = readInt(); } protected int[][] intDataArrayBuffer = new int[50][]; protected int intDataArrayBufferCount = -1; protected int [] allocateIntArray(int length) { if (intDataArrayBufferCount == -1) { return new int[length]; } else { int[] buffer = intDataArrayBuffer[intDataArrayBufferCount]; intDataArrayBuffer[intDataArrayBufferCount--] = null; return buffer.length >= length ? buffer : new int[length]; } } protected void recycle(int[] values) { if (++intDataArrayBufferCount >= intDataArrayBuffer.length) { int [][] newIntDataArrayBuffer = new int[intDataArrayBufferCount * 2][]; System.arraycopy(intDataArrayBuffer, 0, newIntDataArrayBuffer, 0, intDataArrayBufferCount); intDataArrayBuffer = newIntDataArrayBuffer; } intDataArrayBuffer[intDataArrayBufferCount] = values; } protected EPackageData readEPackage() throws IOException { int id = readCompressedInt(); if (internalEPackageDataList.size() <= id) { EPackageData ePackageData = new EPackageData(); String nsURI = readSegmentedString(); URI uri = readURI(); if (resourceSet != null) { ePackageData.ePackage = resourceSet.getPackageRegistry().getEPackage(nsURI); if (ePackageData.ePackage == null) { ePackageData.ePackage = (EPackage)resourceSet.getEObject(uri, true); } } else { ePackageData.ePackage = EPackage.Registry.INSTANCE.getEPackage(nsURI); } ePackageData.eClassData = new EClassData [ePackageData.ePackage.getEClassifiers().size()]; internalEPackageDataList.add(ePackageData); return ePackageData; } else { return internalEPackageDataList.ePackageData[id]; } } protected EClassData readEClass() throws IOException { // This is a fast pat for the case that there is an internal buffer with at least two bytes, // which most likely represent the ID of the package and the ID of the class in that package. // if (index + 2 < count) { // If the first byte represents the entire compressed integer ID of the package and there are that many packages already read... // int packageID = bytes[index]; if (packageID <= 0x3F && packageID >= 0 && --packageID < internalEPackageDataList.size()) { // If the second byte represents the entire compressed integer ID of the class... // int classID = bytes[index + 1]; if (classID <= 0x3F && classID >= 0) { // Increment the index as if we'd read the bytes... // index += 2; // Initialize the class data, if it's not already initialized. // EPackageData ePackageData = internalEPackageDataList.ePackageData[packageID]; EClassData eClassData = ePackageData.eClassData[--classID]; return eClassData == null ? initEClassData(ePackageData, classID) : eClassData; } } } // Just process the data as normal. // EPackageData ePackageData = readEPackage(); int id = readCompressedInt(); EClassData eClassData = ePackageData.eClassData[id]; return eClassData == null ? initEClassData(ePackageData, id) : eClassData; } private EClassData initEClassData(EPackageData ePackageData, int id) throws IOException { EClassData eClassData = ePackageData.eClassData[id] = new EClassData(); String name = readString(); eClassData.eClass = (EClass)ePackageData.ePackage.getEClassifier(name); eClassData.eFactory = ePackageData.ePackage.getEFactoryInstance(); eClassData.eStructuralFeatureData = new EStructuralFeatureData [eClassData.eClass.getFeatureCount()]; return eClassData; } protected EStructuralFeatureData readEStructuralFeature() throws IOException { EClassData eClassData = readEClass(); int featureID = readCompressedInt(); return getEStructuralFeatureData(eClassData, featureID); } protected EStructuralFeatureData getEStructuralFeatureData(EClassData eClassData, int featureID) throws IOException { EStructuralFeatureData eStructuralFeatureData = eClassData.eStructuralFeatureData[featureID]; if (eStructuralFeatureData == null) { eStructuralFeatureData = eClassData.eStructuralFeatureData[featureID] = new EStructuralFeatureData(); String name = readString(); eStructuralFeatureData.eStructuralFeature = eClassData.eClass.getEStructuralFeature(name); eStructuralFeatureData.featureID = eClassData.eClass.getFeatureID(eStructuralFeatureData.eStructuralFeature); eStructuralFeatureData.kind = FeatureKind.get(eStructuralFeatureData.eStructuralFeature); if (eStructuralFeatureData.eStructuralFeature instanceof EAttribute) { EAttribute eAttribute = (EAttribute)eStructuralFeatureData.eStructuralFeature; eStructuralFeatureData.eDataType = eAttribute.getEAttributeType(); eStructuralFeatureData.eFactory = eStructuralFeatureData.eDataType.getEPackage().getEFactoryInstance(); if (segmentedStringsList != null && readBoolean()) { DataConverter<?> dataConverter = dataConverterMap.get(eStructuralFeatureData.eDataType); if (dataConverter == null) { final DataConverter<?> rawDataConverter = ((DataConverter.Factory)eStructuralFeatureData.eFactory).create(eStructuralFeatureData.eDataType); if (rawDataConverter.isTabulated()) { DataConverter<?> tabulatedDataConverter = new DataConverter<Object>() { final DataValueList objects = new DataValueList(); @Override public Object read(EObjectInputStream eObjectInputStream) throws IOException { int id = eObjectInputStream.readCompressedInt(); if (id == -1) { return null; } else if (objects.size() <= id) { Object value = rawDataConverter.read(eObjectInputStream); objects.add(value); return value; } else { return objects.values[id]; } } @Override protected void doWrite(EObjectOutputStream eObjectOutputStream, Object value) throws IOException { throw new UnsupportedOperationException(); } }; dataConverter = tabulatedDataConverter; } else { dataConverter = rawDataConverter; } dataConverterMap.put(eStructuralFeatureData.eDataType, dataConverter); } eStructuralFeatureData.dataConverter = dataConverter; } } } return eStructuralFeatureData; } public void loadResource(Resource resource) throws IOException { this.resource = resource; this.resourceSet = resource.getResourceSet(); URI uri = resource.getURI(); if (uri != null && uri.isHierarchical() && !uri.isRelative()) { baseURI = uri; } int size = readCompressedInt(); InternalEObject[] values = allocateInternalEObjectArray(size); for (int i = 0; i < size; ++i) { values[i] = loadEObject(); } internalEObjectList.setData(size, values); @SuppressWarnings("unchecked") InternalEList<InternalEObject> internalEObjects = (InternalEList<InternalEObject>)(InternalEList<?>)resource.getContents(); internalEObjects.addAllUnique(internalEObjectList); recycle(values); } public void loadEObjects(InternalEList<InternalEObject> internalEObjects) throws IOException { // Read all the values into an array. // int size = readCompressedInt(); InternalEObject[] values = allocateInternalEObjectArray(size); for (int i = 0; i < size; ++i) { values[i] = loadEObject(); } int existingSize = internalEObjects.size(); // If the list is empty, we need to add all the objects, // otherwise, the reference is bidirectional and the list is at least partially populated. // if (existingSize == 0) { internalEObjectList.setData(size, values); internalEObjects.addAllUnique(0, internalEObjectList); } else { InternalEObject [] existingValues = allocateInternalEObjectArray(existingSize); internalEObjects.basicToArray(existingValues); int [] indices = allocateIntArray(existingSize); int duplicateCount = 0; LOOP: for (int i = 0; i < size; ++i) { InternalEObject internalEObject = values[i]; for (int j = 0, count = duplicateCount; j < existingSize; ++j) { InternalEObject existingInternalEObject = existingValues[j]; if (existingInternalEObject == internalEObject) { if (duplicateCount != count) { internalEObjects.move(duplicateCount, count); } indices[duplicateCount] = i; ++duplicateCount; existingValues[j] = null; continue LOOP; } else if (existingInternalEObject != null) { ++count; } } values[i - duplicateCount] = internalEObject; } size -= existingSize; internalEObjectList.setData(size, values); internalEObjects.addAllUnique(0, internalEObjectList); for (int i = 0; i < existingSize; ++i) { int newPosition = indices[i]; int oldPosition = size + i; if (newPosition != oldPosition) { internalEObjects.move(newPosition, oldPosition); } } recycle(existingValues); recycle(indices); } recycle(values); } public void loadFeatureMap(FeatureMap.Internal featureMap) throws IOException { // Read all the values into an array. // int size = readCompressedInt(); FeatureMap.Entry.Internal[] values = allocateFeatureMapEntryArray(size); for (int i = 0; i < size; ++i) { values[i] = loadFeatureMapEntry(); } int existingSize = featureMap.size(); // If the list is empty, we need to add all the objects, // otherwise, the reference is bidirectional and the list is at least partially populated. // if (existingSize == 0) { featureMap.addAllUnique(values, 0, size); } else { FeatureMap.Entry.Internal [] existingValues = allocateFeatureMapEntryArray(existingSize); featureMap.basicToArray(existingValues); int [] indices = allocateIntArray(existingSize); int duplicateCount = 0; LOOP: for (int i = 0; i < size; ++i) { FeatureMap.Entry.Internal entry = values[i]; for (int j = 0, count = 0; j < existingSize; ++j) { FeatureMap.Entry.Internal existingEntry = existingValues[j]; if (entry.equals(existingEntry)) { if (duplicateCount != count) { featureMap.move(duplicateCount, count); } indices[duplicateCount] = i; ++count; ++duplicateCount; existingValues[j] = null; continue LOOP; } else if (existingEntry != null) { ++count; } } values[i - duplicateCount] = entry; } size -= existingSize; internalEObjectList.setData(size, values); featureMap.addAllUnique(0, values, 0, size); for (int i = 0; i < existingSize; ++i) { int newPosition = indices[i]; int oldPosition = size + i; if (newPosition != oldPosition) { featureMap.move(newPosition, oldPosition); } } recycle(existingValues); recycle(indices); } recycle(values); } public FeatureMap.Entry.Internal loadFeatureMapEntry() throws IOException { EStructuralFeatureData eStructuralFeatureData = readEStructuralFeature(); Object value; switch (eStructuralFeatureData.kind) { case EOBJECT_CONTAINER: case EOBJECT_CONTAINER_PROXY_RESOLVING: case EOBJECT: case EOBJECT_LIST: case EOBJECT_PROXY_RESOLVING: case EOBJECT_LIST_PROXY_RESOLVING: case EOBJECT_CONTAINMENT: case EOBJECT_CONTAINMENT_LIST: case EOBJECT_CONTAINMENT_PROXY_RESOLVING: case EOBJECT_CONTAINMENT_LIST_PROXY_RESOLVING: { value = loadEObject(); break; } case STRING: { value = readSegmentedString(); break; } case DATE: { if (eStructuralFeatureData.dataConverter != null) { value = eStructuralFeatureData.dataConverter.read(this); } else if ((style & STYLE_BINARY_DATE) != 0) { value = readDate(); } else { value = eStructuralFeatureData.eFactory.createFromString(eStructuralFeatureData.eDataType, readSegmentedString()); } break; } case ENUMERATOR: { if (eStructuralFeatureData.dataConverter != null) { value = eStructuralFeatureData.dataConverter.read(this); } else if ((style & STYLE_BINARY_ENUMERATOR) != 0) { value = ((EEnum)eStructuralFeatureData.eDataType).getEEnumLiteral(readInt()).getInstance(); } else { value = eStructuralFeatureData.eFactory.createFromString(eStructuralFeatureData.eDataType, readString()); } break; } case DATA: case DATA_LIST: { value = eStructuralFeatureData.dataConverter != null ? eStructuralFeatureData.dataConverter.read(this) : eStructuralFeatureData.eFactory.createFromString(eStructuralFeatureData.eDataType, readSegmentedString()); break; } case BOOLEAN: { value = readBoolean(); break; } case BYTE: { value = readByte(); break; } case CHAR: { value = readChar(); break; } case DOUBLE: { value = readDouble(); break; } case FLOAT: { value = readFloat(); break; } case INT: { value = readInt(); break; } case LONG: { value = readLong(); break; } case SHORT: { value = readShort(); break; } default: { throw new IOException("Unhandled case " + eStructuralFeatureData.kind); } } return FeatureMapUtil.createRawEntry(eStructuralFeatureData.eStructuralFeature, value); } public InternalEObject loadEObject() throws IOException { int id = readCompressedInt(); if (id == -1) { return null; } else { if (internalInternalEObjectList.size() <= id) { EClassData eClassData = readEClass(); InternalEObject internalEObject = (InternalEObject)eClassData.eFactory.create(eClassData.eClass); InternalEObject result = internalEObject; // Check if we have a "feature" representing the proxy URI... // int featureID = readCompressedInt() - 1; if (featureID == -2) { internalEObject.eSetProxyURI(readURI()); if (isEagerProxyResolution) { result = (InternalEObject)EcoreUtil.resolve(internalEObject, resource); internalInternalEObjectList.add(result); if ((style & STYLE_PROXY_ATTRIBUTES) == 0) { return result; } } else { internalInternalEObjectList.add(internalEObject); if ((style & STYLE_PROXY_ATTRIBUTES) == 0) { return internalEObject; } } // We must process the proxy attributes even for the case of eager proxy resolution when we will immediately discard the proxy. // featureID = readCompressedInt() - 1; } else { internalInternalEObjectList.add(internalEObject); } for (; featureID != -1; featureID = readCompressedInt() - 1) { EStructuralFeatureData eStructuralFeatureData = getEStructuralFeatureData(eClassData, featureID); loadFeatureValue(internalEObject, eStructuralFeatureData); } return result; } else { return internalInternalEObjectList.eObjects[id]; } } } protected void loadFeatureValue(InternalEObject internalEObject, EStructuralFeatureData eStructuralFeatureData) throws IOException { switch (eStructuralFeatureData.kind) { case EOBJECT_CONTAINER: case EOBJECT_CONTAINER_PROXY_RESOLVING: case EOBJECT: case EOBJECT_PROXY_RESOLVING: case EOBJECT_CONTAINMENT: case EOBJECT_CONTAINMENT_PROXY_RESOLVING: { internalEObject.eSet(eStructuralFeatureData.featureID, loadEObject()); break; } case EOBJECT_LIST: case EOBJECT_LIST_PROXY_RESOLVING: case EOBJECT_CONTAINMENT_LIST: case EOBJECT_CONTAINMENT_LIST_PROXY_RESOLVING: { @SuppressWarnings("unchecked") InternalEList<InternalEObject> internalEList = (InternalEList<InternalEObject>)internalEObject.eGet(eStructuralFeatureData.featureID, false, true); loadEObjects(internalEList); break; } case STRING: { internalEObject.eSet(eStructuralFeatureData.featureID, readSegmentedString()); break; } case FEATURE_MAP: { FeatureMap.Internal featureMap = (FeatureMap.Internal)internalEObject.eGet(eStructuralFeatureData.featureID, false, true); loadFeatureMap(featureMap); break; } case DATE: { if (eStructuralFeatureData.dataConverter != null) { internalEObject.eSet(eStructuralFeatureData.featureID, eStructuralFeatureData.dataConverter.read(this)); } else if ((style & STYLE_BINARY_DATE) != 0) { internalEObject.eSet(eStructuralFeatureData.featureID, readDate()); } else { internalEObject.eSet(eStructuralFeatureData.featureID, eStructuralFeatureData.eFactory.createFromString(eStructuralFeatureData.eDataType, readSegmentedString())); } break; } case ENUMERATOR: { if (eStructuralFeatureData.dataConverter != null) { internalEObject.eSet(eStructuralFeatureData.featureID, eStructuralFeatureData.dataConverter.read(this)); } else if ((style & STYLE_BINARY_ENUMERATOR) != 0) { internalEObject.eSet(eStructuralFeatureData.featureID, ((EEnum)eStructuralFeatureData.eDataType).getEEnumLiteral(readInt()).getInstance()); } else { internalEObject.eSet(eStructuralFeatureData.featureID, eStructuralFeatureData.eFactory.createFromString(eStructuralFeatureData.eDataType, readString())); } break; } case DATA: { Object value = eStructuralFeatureData.dataConverter != null ? eStructuralFeatureData.dataConverter.read(this) : eStructuralFeatureData.eFactory.createFromString(eStructuralFeatureData.eDataType, readSegmentedString()); internalEObject.eSet(eStructuralFeatureData.featureID, value); break; } case DATA_LIST: { int size = readCompressedInt(); dataValueList.grow(size); Object[] dataValues = dataValueList.data(); if (eStructuralFeatureData.dataConverter != null) { for (int i = 0; i < size; ++i) { dataValues[i] = eStructuralFeatureData.dataConverter.read(this); } } else { for (int i = 0; i < size; ++i) { String literal = readSegmentedString(); dataValues[i] = eStructuralFeatureData.eFactory.createFromString(eStructuralFeatureData.eDataType, literal); } } dataValueList.setData(size, dataValues); @SuppressWarnings("unchecked") InternalEList<Object> values = (InternalEList<Object>)internalEObject.eGet(eStructuralFeatureData.featureID, false, true); values.addAllUnique(dataValueList); break; } case BOOLEAN: { internalEObject.eSet(eStructuralFeatureData.featureID, readBoolean()); break; } case BYTE: { internalEObject.eSet(eStructuralFeatureData.featureID, readByte()); break; } case CHAR: { internalEObject.eSet(eStructuralFeatureData.featureID, readChar()); break; } case DOUBLE: { internalEObject.eSet(eStructuralFeatureData.featureID, readDouble()); break; } case FLOAT: { internalEObject.eSet(eStructuralFeatureData.featureID, readFloat()); break; } case INT: { internalEObject.eSet(eStructuralFeatureData.featureID, readInt()); break; } case LONG: { internalEObject.eSet(eStructuralFeatureData.featureID, readLong()); break; } case SHORT: { internalEObject.eSet(eStructuralFeatureData.featureID, readShort()); break; } default: { throw new IOException("Unhandled case " + eStructuralFeatureData.kind); } } } public byte readByte() throws IOException { return index < count ? bytes[index++] : fill(); } private byte fill() throws IOException { if (bytes == null) { int result = inputStream.read(); if (result == -1) { throw new IOException("Unexpected end of stream"); } return (byte)result; } else { if (isMarkSupported) { inputStream.mark(bytes.length); } count = inputStream.read(bytes, 0, bytes.length); if (count == -1) { throw new IOException("Unexpected end of stream"); } index = 1; return bytes[0]; } } /** * @since 2.9 */ public void flush() throws IOException { if (bytes != null && index < count) { if (isMarkSupported) { // Put back all the bytes buffered since the last call to fill. // inputStream.reset(); // Read back out again all the bytes that were consumed by readByte. // inputStream.read(bytes, 0, index); } else { throw new IOException("Unable to place " + index + "unconsumed bytes back into the input stream; specify an OPTION_BUFFER_CAPACITY to support this"); } } } public boolean readBoolean() throws IOException { return readByte() != 0; } public char readChar() throws IOException { return (char)((readByte() << 8) & 0xFF00 | readByte() & 0xFF); } public short readShort() throws IOException { return (short)((readByte() << 8) & 0xFF00 | readByte() & 0xFF); } public int readInt() throws IOException { return (readByte() << 24) | (readByte() << 16) & 0xFF0000 | (readByte() << 8) & 0xFF00 | readByte() & 0xFF; } public long readLong() throws IOException { return (long)readInt() << 32 | readInt() & 0xFFFFFFFFL; } public float readFloat() throws IOException { if ((style & STYLE_BINARY_FLOATING_POINT) != 0) { return Float.intBitsToFloat(readInt()); } else { return Float.parseFloat(readString()); } } public double readDouble() throws IOException { if ((style & STYLE_BINARY_FLOATING_POINT) != 0) { return Double.longBitsToDouble(readLong()); } else { return Double.parseDouble(readString()); } } public int readCompressedInt() throws IOException { // If the internal buffer holds all the bytes we could potentially need, i.e., all 4 bytes... // if (index + 4 < count) { // Do all the processing directly from the buffered bytes rather than by calling readByte. // int initialByte = bytes[index++]; int code = (initialByte >> 6) & 0x3; switch (code) { case 0: { return initialByte - 1; } case 1: { return (initialByte << 8 & 0x3F00 | bytes[index++] & 0xFF) - 1; } case 2: { return ((initialByte << 16) & 0x3F0000 | (bytes[index++] << 8) & 0xFF00 | bytes[index++] & 0xFF) - 1; } default: { return ((initialByte << 24) & 0x3F000000 | (bytes[index++] << 16) & 0xFF0000 | (bytes[index++] << 8) & 0xFF00 | bytes[index++] & 0xFF) - 1; } } } else { // Do the processing by reading each individual byte as needed. // int initialByte = readByte(); int code = (initialByte >> 6) & 0x3; switch (code) { case 0: { return initialByte - 1; } case 1: { return (initialByte << 8 & 0x3F00 | readByte() & 0xFF) - 1; } case 2: { return ((initialByte << 16) & 0x3F0000 | (readByte() << 8) & 0xFF00 | readByte() & 0xFF) - 1; } default: { return ((initialByte << 24) & 0x3F000000 | (readByte() << 16) & 0xFF0000 | (readByte() << 8) & 0xFF00 | readByte() & 0xFF) - 1; } } } } /** * @since 2.9 */ public String readSegmentedString() throws IOException { if (segmentedStringsList == null) { return basicReadString(); } else { int id = readCompressedInt(); if (id == -1) { return null; } else if (segmentedStringsList.size() <= id) { int segmentCount = readCompressedInt(); String value; if (segmentCount == 0) { value = readString(); } else { int length = 0; for (int i = 0; i < segmentCount; ++i) { String segment = readString(); int segmentLength = segment.length(); int newLength = length + segmentLength; if (builder.length < newLength) { char[] newBuilder = new char [Math.max(2 * builder.length, newLength)]; System.arraycopy(builder, 0, newBuilder, 0, builder.length); builder = newBuilder; } if (segmentLength == 1) { builder[length] = segment.charAt(0); } else { segment.getChars(0, segmentLength, builder, length); } length = newLength; } value = new String(builder, 0, length); } segmentedStringsList.add(value); return value; } else { return segmentedStringsList.strings[id]; } } } public String readString() throws IOException { if (segmentsList != null) { int id = readCompressedInt(); if (id == -1) { return null; } else if (segmentsList.size() <= id) { String value = basicReadString(); segmentsList.add(value); return value; } else { return segmentsList.strings[id]; } } else { return basicReadString(); } } private String basicReadString() throws IOException { int length = readCompressedInt(); if (length == -1) { return null; } else { if (characters == null || characters.length < length) { characters = new char[length]; } LOOP: for (int i = 0; i < length; ++i) { byte value = readByte(); if (value == 0) { do { characters[i] = readChar(); } while (++i < length); break LOOP; } else { characters[i] = (char)(value & 0xFF); } } return new String(characters, 0, length); } } public Date readDate() throws IOException { long time = readLong(); return new Date(time); } public URI readURI() throws IOException { int id = readCompressedInt(); if (id == -1) { return null; } else { URI uri; if (internalURIList.size() <= id) { String value = readSegmentedString(); uri = resolve(URI.createURI(value)); internalURIList.add(uri); } else { uri = internalURIList.uris[id]; } String fragment = readSegmentedString(); if (fragment != null) { uri = uri.appendFragment(fragment); } return uri; } } } }