OutputStream.java
// Copyright (c) ZeroC, Inc.
package com.zeroc.Ice;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.LongBuffer;
import java.nio.ShortBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalDouble;
import java.util.OptionalInt;
import java.util.OptionalLong;
import java.util.TreeMap;
/**
* Interface to marshal (write) Slice types into sequence of bytes encoded using the Slice encoding.
*
* @see InputStream
*/
public final class OutputStream {
/**
* Construct a new instance of the OutputStream class. The output stream is initially empty, and
* uses the 1.1 encoding, and compact class format.
*/
public OutputStream() {
this(Util.currentEncoding(), FormatType.CompactFormat, false);
}
/**
* Construct a new instance of the OutputStream class. The output stream is initially empty, and
* uses the specified encoding, and compact class format.
*
* @param encoding The encoding version to use.
*/
public OutputStream(EncodingVersion encoding) {
this(encoding, FormatType.CompactFormat, false);
}
/**
* Construct a new instance of the OutputStream class. The output stream is initially empty, and
* uses the specified encoding, and compact class format.
*
* @param encoding The encoding version to use.
* @param direct Indicates whether to use a direct buffer.
*/
public OutputStream(EncodingVersion encoding, boolean direct) {
this(encoding, FormatType.CompactFormat, direct);
}
/**
* Construct a new instance of the OutputStream class. The output stream is initially empty, and
* uses the specified encoding, and class format.
*
* @param encoding The encoding version to use.
* @param format The format to use for class encoding.
* @param direct Indicates whether to use a direct buffer.
*/
public OutputStream(EncodingVersion encoding, FormatType format, boolean direct) {
// The 1.0 encoding doesn't use the class format type, but we still have to set it in case
// the stream reads an 1.1 encapsulation, in which case it would use the format type set in
// the stream.
_buf = new Buffer(direct);
_encoding = encoding;
_format = format;
}
/**
* Construct a new instance of the OutputStream class. The output stream is initially empty, and
* uses the communicator's default encoding version and default class format.
*
* @param communicator The communicator that provides the encoding version and class format.
*/
public OutputStream(Communicator communicator) {
this(
communicator.getInstance().defaultsAndOverrides().defaultEncoding,
communicator.getInstance().defaultsAndOverrides().defaultFormat,
communicator.getInstance().cacheMessageBuffers() > 1);
}
/**
* Constructs an OutputStream using the provided buffer and encoding.
*
* @param buf the buffer to use
* @param encoding the encoding version to use
*/
OutputStream(Buffer buf, EncodingVersion encoding) {
_buf = buf;
_encoding = encoding != null ? encoding : Protocol.currentEncoding;
_format = FormatType.CompactFormat;
}
/**
* Resets this output stream. This method allows the stream to be reused, to avoid creating
* unnecessary garbage.
*/
public void reset() {
_buf.reset();
clear();
}
/**
* Releases any data retained by encapsulations. The {@link #reset} method internally calls
* <code>
* clear</code>.
*/
public void clear() {
if (_encapsStack != null) {
assert (_encapsStack.next == null);
_encapsStack.next = _encapsCache;
_encapsCache = _encapsStack;
_encapsCache.reset();
_encapsStack = null;
}
}
/**
* Indicates that marshaling is finished.
*
* @return The byte sequence containing the encoded data.
*/
public byte[] finished() {
Buffer buf = prepareWrite();
byte[] result = new byte[buf.b.limit()];
buf.b.get(result);
return result;
}
/**
* Swaps the contents of one stream with another.
*
* @param other The other stream.
*/
public void swap(OutputStream other) {
Buffer tmpBuf = other._buf;
other._buf = _buf;
_buf = tmpBuf;
EncodingVersion tmpEncoding = other._encoding;
other._encoding = _encoding;
_encoding = tmpEncoding;
//
// Swap is never called for streams that have encapsulations being written. However,
// encapsulations might still be set in case marshaling failed. We just reset the
// encapsulations if there are still some set.
//
resetEncapsulation();
other.resetEncapsulation();
}
private void resetEncapsulation() {
_encapsStack = null;
}
/**
* Resizes the stream to a new size.
*
* @param sz The new size.
*/
public void resize(int sz) {
_buf.resize(sz, false);
_buf.position(sz);
}
/**
* Prepares the internal data buffer to be written to a socket.
*
* @return The internal buffer.
*/
public Buffer prepareWrite() {
_buf.limit(_buf.size());
_buf.position(0);
return _buf;
}
/**
* Retrieves the internal data buffer.
*
* @return The buffer.
*/
public Buffer getBuffer() {
return _buf;
}
/**
* Marks the start of a class instance.
*
* @param data Preserved slices for this instance, or null.
*/
public void startValue(SlicedData data) {
assert (_encapsStack != null && _encapsStack.encoder != null);
_encapsStack.encoder.startInstance(SliceType.ValueSlice, data);
}
/** Marks the end of a class instance. */
public void endValue() {
assert (_encapsStack != null && _encapsStack.encoder != null);
_encapsStack.encoder.endInstance();
}
/** Marks the start of a user exception. */
public void startException() {
assert (_encapsStack != null && _encapsStack.encoder != null);
_encapsStack.encoder.startInstance(SliceType.ExceptionSlice, null);
}
/** Marks the end of a user exception. */
public void endException() {
assert (_encapsStack != null && _encapsStack.encoder != null);
_encapsStack.encoder.endInstance();
}
/** Writes the start of an encapsulation to the stream. */
public void startEncapsulation() {
//
// If no encoding version is specified, use the current write encapsulation encoding version
// if there's a current write encapsulation, otherwise, use the stream encoding version.
//
if (_encapsStack != null) {
startEncapsulation(_encapsStack.encoding, _encapsStack.format);
} else {
startEncapsulation(_encoding, null);
}
}
/**
* Writes the start of an encapsulation to the stream.
*
* @param encoding The encoding version of the encapsulation.
* @param format Specify the compact or sliced format, or null.
*/
public void startEncapsulation(EncodingVersion encoding, FormatType format) {
Protocol.checkSupportedEncoding(encoding);
Encaps curr = _encapsCache;
if (curr != null) {
curr.reset();
_encapsCache = _encapsCache.next;
} else {
curr = new Encaps();
}
curr.next = _encapsStack;
_encapsStack = curr;
_encapsStack.format = format;
_encapsStack.setEncoding(encoding);
_encapsStack.start = _buf.size();
writeInt(0); // Placeholder for the encapsulation length.
_encapsStack.encoding.ice_writeMembers(this);
}
/** Ends the previous encapsulation. */
public void endEncapsulation() {
assert (_encapsStack != null);
// Size includes size and version.
int start = _encapsStack.start;
int sz = _buf.size() - start;
_buf.b.putInt(start, sz);
Encaps curr = _encapsStack;
_encapsStack = curr.next;
curr.next = _encapsCache;
_encapsCache = curr;
_encapsCache.reset();
}
/**
* Writes an empty encapsulation using the given encoding version.
*
* @param encoding The desired encoding version.
*/
public void writeEmptyEncapsulation(EncodingVersion encoding) {
Protocol.checkSupportedEncoding(encoding);
writeInt(6); // Size
encoding.ice_writeMembers(this);
}
/**
* Writes a pre-encoded encapsulation.
*
* @param v The encapsulation data.
*/
public void writeEncapsulation(byte[] v) {
if (v.length < 6) {
throw new MarshalException(
"A byte sequence with " + v.length + " bytes is not a valid encapsulation.");
}
expand(v.length);
_buf.b.put(v);
}
/**
* Determines the current encoding version.
*
* @return The encoding version.
*/
public EncodingVersion getEncoding() {
return _encapsStack != null ? _encapsStack.encoding : _encoding;
}
/**
* Marks the start of a new slice for a class instance or user exception.
*
* @param typeId The Slice type ID corresponding to this slice.
* @param compactId The Slice compact type ID corresponding to this slice or -1 if no compact ID
* is defined for the type ID.
* @param last True if this is the last slice, false otherwise.
*/
public void startSlice(String typeId, int compactId, boolean last) {
assert (_encapsStack != null && _encapsStack.encoder != null);
_encapsStack.encoder.startSlice(typeId, compactId, last);
}
/** Marks the end of a slice for a class instance or user exception. */
public void endSlice() {
assert (_encapsStack != null && _encapsStack.encoder != null);
_encapsStack.encoder.endSlice();
}
/**
* Writes the state of Slice classes whose index was previously written with {@link #writeValue}
* to the stream.
*/
public void writePendingValues() {
if (_encapsStack != null && _encapsStack.encoder != null) {
_encapsStack.encoder.writePendingValues();
} else if (_encapsStack != null
? _encapsStack.encoding_1_0
: _encoding.equals(Util.Encoding_1_0)) {
//
// If using the 1.0 encoding and no instances were written, we still write an empty
// sequence for pending instances if requested (i.e.: if this is called).
//
// This is required by the 1.0 encoding, even if no instances are written we do marshal
// an empty sequence if marshaled data types use classes.
//
writeSize(0);
}
}
/**
* Writes a size to the stream.
*
* @param v The size to write.
*/
public void writeSize(int v) {
if (v > 254) {
expand(5);
_buf.b.put((byte) -1);
_buf.b.putInt(v);
} else {
expand(1);
_buf.b.put((byte) v);
}
}
/**
* Returns the current position and allocates four bytes for a fixed-length (32-bit) size value.
*
* @return The current position.
*/
public int startSize() {
int pos = _buf.b.position();
writeInt(0); // Placeholder for 32-bit size
return pos;
}
/**
* Computes the amount of data written since the previous call to startSize and writes that
* value at the saved position.
*
* @param pos The saved position.
*/
public void endSize(int pos) {
assert (pos >= 0);
rewriteInt(_buf.b.position() - pos - 4, pos);
}
/**
* Writes a blob of bytes to the stream.
*
* @param v The byte array to be written. All of the bytes in the array are written.
*/
public void writeBlob(byte[] v) {
if (v == null) {
return;
}
expand(v.length);
_buf.b.put(v);
}
/**
* Writes a blob of bytes to the stream.
*
* @param v The byte array to be written.
* @param off The offset into the byte array from which to copy.
* @param len The number of bytes from the byte array to copy.
*/
public void writeBlob(byte[] v, int off, int len) {
if (v == null) {
return;
}
expand(len);
_buf.b.put(v, off, len);
}
/**
* Write the header information for an optional value.
*
* @param tag The numeric tag associated with the value.
* @param format The optional format of the value.
* @return True if the current encoding supports optionals, false otherwise.
*/
public boolean writeOptional(int tag, OptionalFormat format) {
assert (_encapsStack != null);
if (_encapsStack.encoder != null) {
return _encapsStack.encoder.writeOptional(tag, format);
} else {
return writeOptionalImpl(tag, format);
}
}
/**
* Writes a byte to the stream.
*
* @param v The byte to write to the stream.
*/
public void writeByte(byte v) {
expand(1);
_buf.b.put(v);
}
/**
* Writes an optional byte to the stream.
*
* @param tag The optional tag.
* @param v The optional byte to write to the stream.
*/
public void writeByte(int tag, Optional<Byte> v) {
if (v != null && v.isPresent()) {
writeByte(tag, v.get());
}
}
/**
* Writes an optional byte to the stream.
*
* @param tag The optional tag.
* @param v The byte to write to the stream.
*/
public void writeByte(int tag, byte v) {
if (writeOptional(tag, OptionalFormat.F1)) {
writeByte(v);
}
}
/**
* Writes a byte to the stream at the given position. The current position of the stream is not
* modified.
*
* @param v The byte to write to the stream.
* @param dest The position at which to store the byte in the buffer.
*/
public void rewriteByte(byte v, int dest) {
_buf.b.put(dest, v);
}
/**
* Writes a byte sequence to the stream.
*
* @param v The byte sequence to write to the stream. Passing <code>null</code> causes an empty
* sequence to be written to the stream.
*/
public void writeByteSeq(byte[] v) {
if (v == null) {
writeSize(0);
} else {
writeSize(v.length);
expand(v.length);
_buf.b.put(v);
}
}
/**
* Writes an optional byte sequence to the stream.
*
* @param tag The optional tag.
* @param v The optional byte sequence to write to the stream.
*/
public void writeByteSeq(int tag, Optional<byte[]> v) {
if (v != null && v.isPresent()) {
writeByteSeq(tag, v.get());
}
}
/**
* Writes an optional byte sequence to the stream.
*
* @param tag The optional tag.
* @param v The byte sequence to write to the stream.
*/
public void writeByteSeq(int tag, byte[] v) {
if (writeOptional(tag, OptionalFormat.VSize)) {
writeByteSeq(v);
}
}
/**
* Writes the remaining contents of the byte buffer as a byte sequence to the stream.
*
* @param v The byte buffer to write to the stream.
*/
public void writeByteBuffer(ByteBuffer v) {
if (v == null || v.remaining() == 0) {
writeSize(0);
} else {
writeSize(v.remaining());
expand(v.remaining());
_buf.b.put(v);
}
}
/**
* Writes a serializable Java object to the stream.
*
* @param o The serializable object to write.
*/
public void writeSerializable(Serializable o) {
if (o == null) {
writeSize(0);
return;
}
try {
var w = new OutputStreamWrapper(this);
ObjectOutputStream out = new ObjectOutputStream(w);
out.writeObject(o);
out.close();
w.close();
} catch (Exception ex) {
throw new MarshalException("cannot serialize object: " + ex);
}
}
/**
* Writes an optional serializable Java object to the stream.
*
* @param <T> The serializable class.
* @param tag The optional tag.
* @param v The optional serializable object to write.
*/
public <T extends Serializable> void writeSerializable(
int tag, Optional<T> v) {
if (v != null && v.isPresent()) {
writeSerializable(tag, v.get());
}
}
/**
* Writes an optional serializable Java object to the stream.
*
* @param tag The optional tag.
* @param v The serializable object to write.
*/
public void writeSerializable(int tag, Serializable v) {
if (writeOptional(tag, OptionalFormat.VSize)) {
writeSerializable(v);
}
}
/**
* Writes a boolean to the stream.
*
* @param v The boolean to write to the stream.
*/
public void writeBool(boolean v) {
expand(1);
_buf.b.put(v ? (byte) 1 : (byte) 0);
}
/**
* Writes an optional boolean to the stream.
*
* @param tag The optional tag.
* @param v The optional boolean to write to the stream.
*/
public void writeBool(int tag, Optional<Boolean> v) {
if (v != null && v.isPresent()) {
writeBool(tag, v.get());
}
}
/**
* Writes an optional boolean to the stream.
*
* @param tag The optional tag.
* @param v The boolean to write to the stream.
*/
public void writeBool(int tag, boolean v) {
if (writeOptional(tag, OptionalFormat.F1)) {
writeBool(v);
}
}
/**
* Writes a boolean to the stream at the given position. The current position of the stream is
* not modified.
*
* @param v The boolean to write to the stream.
* @param dest The position at which to store the boolean in the buffer.
*/
public void rewriteBool(boolean v, int dest) {
_buf.b.put(dest, v ? (byte) 1 : (byte) 0);
}
/**
* Writes a boolean sequence to the stream.
*
* @param v The boolean sequence to write to the stream. Passing <code>null</code> causes an
* empty sequence to be written to the stream.
*/
public void writeBoolSeq(boolean[] v) {
if (v == null) {
writeSize(0);
} else {
writeSize(v.length);
expand(v.length);
for (boolean b : v) {
_buf.b.put(b ? (byte) 1 : (byte) 0);
}
}
}
/**
* Writes an optional boolean sequence to the stream.
*
* @param tag The optional tag.
* @param v The optional boolean sequence to write to the stream.
*/
public void writeBoolSeq(int tag, Optional<boolean[]> v) {
if (v != null && v.isPresent()) {
writeBoolSeq(tag, v.get());
}
}
/**
* Writes an optional boolean sequence to the stream.
*
* @param tag The optional tag.
* @param v The boolean sequence to write to the stream.
*/
public void writeBoolSeq(int tag, boolean[] v) {
if (writeOptional(tag, OptionalFormat.VSize)) {
writeBoolSeq(v);
}
}
/**
* Writes a short to the stream.
*
* @param v The short to write to the stream.
*/
public void writeShort(short v) {
expand(2);
_buf.b.putShort(v);
}
/**
* Writes an optional short to the stream.
*
* @param tag The optional tag.
* @param v The optional short to write to the stream.
*/
public void writeShort(int tag, Optional<Short> v) {
if (v != null && v.isPresent()) {
writeShort(tag, v.get());
}
}
/**
* Writes an optional short to the stream.
*
* @param tag The optional tag.
* @param v The short to write to the stream.
*/
public void writeShort(int tag, short v) {
if (writeOptional(tag, OptionalFormat.F2)) {
writeShort(v);
}
}
/**
* Writes a short sequence to the stream.
*
* @param v The short sequence to write to the stream. Passing <code>null</code> causes an empty
* sequence to be written to the stream.
*/
public void writeShortSeq(short[] v) {
if (v == null) {
writeSize(0);
} else {
writeSize(v.length);
expand(v.length * 2);
ShortBuffer shortBuf = _buf.b.asShortBuffer();
shortBuf.put(v);
_buf.position(_buf.b.position() + v.length * 2);
}
}
/**
* Writes an optional short sequence to the stream.
*
* @param tag The optional tag.
* @param v The optional short sequence to write to the stream.
*/
public void writeShortSeq(int tag, Optional<short[]> v) {
if (v != null && v.isPresent()) {
writeShortSeq(tag, v.get());
}
}
/**
* Writes an optional short sequence to the stream.
*
* @param tag The optional tag.
* @param v The short sequence to write to the stream.
*/
public void writeShortSeq(int tag, short[] v) {
if (writeOptional(tag, OptionalFormat.VSize)) {
writeSize(v == null || v.length == 0 ? 1 : v.length * 2 + (v.length > 254 ? 5 : 1));
writeShortSeq(v);
}
}
/**
* Writes the remaining contents of the short buffer as a short sequence to the stream.
*
* @param v The short buffer to write to the stream.
*/
public void writeShortBuffer(ShortBuffer v) {
if (v == null || v.remaining() == 0) {
writeSize(0);
} else {
int sz = v.remaining();
writeSize(sz);
expand(sz * 2);
ShortBuffer shortBuf = _buf.b.asShortBuffer();
shortBuf.put(v);
_buf.position(_buf.b.position() + sz * 2);
}
}
/**
* Writes an int to the stream.
*
* @param v The int to write to the stream.
*/
public void writeInt(int v) {
expand(4);
_buf.b.putInt(v);
}
/**
* Writes an optional int to the stream.
*
* @param tag The optional tag.
* @param v The optional int to write to the stream.
*/
public void writeInt(int tag, OptionalInt v) {
if (v != null && v.isPresent()) {
writeInt(tag, v.getAsInt());
}
}
/**
* Writes an optional int to the stream.
*
* @param tag The optional tag.
* @param v The int to write to the stream.
*/
public void writeInt(int tag, int v) {
if (writeOptional(tag, OptionalFormat.F4)) {
writeInt(v);
}
}
/**
* Writes an int to the stream at the given position. The current position of the stream is not
* modified.
*
* @param v The int to write to the stream.
* @param dest The position at which to store the int in the buffer.
*/
public void rewriteInt(int v, int dest) {
_buf.b.putInt(dest, v);
}
/**
* Writes an int sequence to the stream.
*
* @param v The int sequence to write to the stream. Passing <code>null</code> causes an empty
* sequence to be written to the stream.
*/
public void writeIntSeq(int[] v) {
if (v == null) {
writeSize(0);
} else {
writeSize(v.length);
expand(v.length * 4);
IntBuffer intBuf = _buf.b.asIntBuffer();
intBuf.put(v);
_buf.position(_buf.b.position() + v.length * 4);
}
}
/**
* Writes an optional int sequence to the stream.
*
* @param tag The optional tag.
* @param v The optional int sequence to write to the stream.
*/
public void writeIntSeq(int tag, Optional<int[]> v) {
if (v != null && v.isPresent()) {
writeIntSeq(tag, v.get());
}
}
/**
* Writes an optional int sequence to the stream.
*
* @param tag The optional tag.
* @param v The int sequence to write to the stream.
*/
public void writeIntSeq(int tag, int[] v) {
if (writeOptional(tag, OptionalFormat.VSize)) {
writeSize(v == null || v.length == 0 ? 1 : v.length * 4 + (v.length > 254 ? 5 : 1));
writeIntSeq(v);
}
}
/**
* Writes the remaining contents of the int buffer as an int sequence to the stream.
*
* @param v The int buffer to write to the stream.
*/
public void writeIntBuffer(IntBuffer v) {
if (v == null || v.remaining() == 0) {
writeSize(0);
} else {
int sz = v.remaining();
writeSize(sz);
expand(sz * 4);
IntBuffer intBuf = _buf.b.asIntBuffer();
intBuf.put(v);
_buf.position(_buf.b.position() + sz * 4);
}
}
/**
* Writes a long to the stream.
*
* @param v The long to write to the stream.
*/
public void writeLong(long v) {
expand(8);
_buf.b.putLong(v);
}
/**
* Writes an optional long to the stream.
*
* @param tag The optional tag.
* @param v The optional long to write to the stream.
*/
public void writeLong(int tag, OptionalLong v) {
if (v != null && v.isPresent()) {
writeLong(tag, v.getAsLong());
}
}
/**
* Writes an optional long to the stream.
*
* @param tag The optional tag.
* @param v The long to write to the stream.
*/
public void writeLong(int tag, long v) {
if (writeOptional(tag, OptionalFormat.F8)) {
writeLong(v);
}
}
/**
* Writes a long sequence to the stream.
*
* @param v The long sequence to write to the stream. Passing <code>null</code> causes an empty
* sequence to be written to the stream.
*/
public void writeLongSeq(long[] v) {
if (v == null) {
writeSize(0);
} else {
writeSize(v.length);
expand(v.length * 8);
LongBuffer longBuf = _buf.b.asLongBuffer();
longBuf.put(v);
_buf.position(_buf.b.position() + v.length * 8);
}
}
/**
* Writes an optional long sequence to the stream.
*
* @param tag The optional tag.
* @param v The optional long sequence to write to the stream.
*/
public void writeLongSeq(int tag, Optional<long[]> v) {
if (v != null && v.isPresent()) {
writeLongSeq(tag, v.get());
}
}
/**
* Writes an optional long sequence to the stream.
*
* @param tag The optional tag.
* @param v The long sequence to write to the stream.
*/
public void writeLongSeq(int tag, long[] v) {
if (writeOptional(tag, OptionalFormat.VSize)) {
writeSize(v == null || v.length == 0 ? 1 : v.length * 8 + (v.length > 254 ? 5 : 1));
writeLongSeq(v);
}
}
/**
* Writes the remaining contents of the long buffer as a long sequence to the stream.
*
* @param v The long buffer to write to the stream.
*/
public void writeLongBuffer(LongBuffer v) {
if (v == null || v.remaining() == 0) {
writeSize(0);
} else {
int sz = v.remaining();
writeSize(sz);
expand(sz * 8);
LongBuffer longBuf = _buf.b.asLongBuffer();
longBuf.put(v);
_buf.position(_buf.b.position() + sz * 8);
}
}
/**
* Writes a float to the stream.
*
* @param v The float to write to the stream.
*/
public void writeFloat(float v) {
expand(4);
_buf.b.putFloat(v);
}
/**
* Writes an optional float to the stream.
*
* @param tag The optional tag.
* @param v The optional float to write to the stream.
*/
public void writeFloat(int tag, Optional<Float> v) {
if (v != null && v.isPresent()) {
writeFloat(tag, v.get());
}
}
/**
* Writes an optional float to the stream.
*
* @param tag The optional tag.
* @param v The float to write to the stream.
*/
public void writeFloat(int tag, float v) {
if (writeOptional(tag, OptionalFormat.F4)) {
writeFloat(v);
}
}
/**
* Writes a float sequence to the stream.
*
* @param v The float sequence to write to the stream. Passing <code>null</code> causes an empty
* sequence to be written to the stream.
*/
public void writeFloatSeq(float[] v) {
if (v == null) {
writeSize(0);
} else {
writeSize(v.length);
expand(v.length * 4);
FloatBuffer floatBuf = _buf.b.asFloatBuffer();
floatBuf.put(v);
_buf.position(_buf.b.position() + v.length * 4);
}
}
/**
* Writes an optional float sequence to the stream.
*
* @param tag The optional tag.
* @param v The optional float sequence to write to the stream.
*/
public void writeFloatSeq(int tag, Optional<float[]> v) {
if (v != null && v.isPresent()) {
writeFloatSeq(tag, v.get());
}
}
/**
* Writes an optional float sequence to the stream.
*
* @param tag The optional tag.
* @param v The float sequence to write to the stream.
*/
public void writeFloatSeq(int tag, float[] v) {
if (writeOptional(tag, OptionalFormat.VSize)) {
writeSize(v == null || v.length == 0 ? 1 : v.length * 4 + (v.length > 254 ? 5 : 1));
writeFloatSeq(v);
}
}
/**
* Writes the remaining contents of the float buffer as a float sequence to the stream.
*
* @param v The float buffer to write to the stream.
*/
public void writeFloatBuffer(FloatBuffer v) {
if (v == null || v.remaining() == 0) {
writeSize(0);
} else {
int sz = v.remaining();
writeSize(sz);
expand(sz * 4);
FloatBuffer floatBuf = _buf.b.asFloatBuffer();
floatBuf.put(v);
_buf.position(_buf.b.position() + sz * 4);
}
}
/**
* Writes a double to the stream.
*
* @param v The double to write to the stream.
*/
public void writeDouble(double v) {
expand(8);
_buf.b.putDouble(v);
}
/**
* Writes an optional double to the stream.
*
* @param tag The optional tag.
* @param v The optional double to write to the stream.
*/
public void writeDouble(int tag, OptionalDouble v) {
if (v != null && v.isPresent()) {
writeDouble(tag, v.getAsDouble());
}
}
/**
* Writes an optional double to the stream.
*
* @param tag The optional tag.
* @param v The double to write to the stream.
*/
public void writeDouble(int tag, double v) {
if (writeOptional(tag, OptionalFormat.F8)) {
writeDouble(v);
}
}
/**
* Writes a double sequence to the stream.
*
* @param v The double sequence to write to the stream. Passing <code>null</code> causes an
* empty sequence to be written to the stream.
*/
public void writeDoubleSeq(double[] v) {
if (v == null) {
writeSize(0);
} else {
writeSize(v.length);
expand(v.length * 8);
DoubleBuffer doubleBuf = _buf.b.asDoubleBuffer();
doubleBuf.put(v);
_buf.position(_buf.b.position() + v.length * 8);
}
}
/**
* Writes an optional double sequence to the stream.
*
* @param tag The optional tag.
* @param v The optional double sequence to write to the stream.
*/
public void writeDoubleSeq(int tag, Optional<double[]> v) {
if (v != null && v.isPresent()) {
writeDoubleSeq(tag, v.get());
}
}
/**
* Writes an optional double sequence to the stream.
*
* @param tag The optional tag.
* @param v The double sequence to write to the stream.
*/
public void writeDoubleSeq(int tag, double[] v) {
if (writeOptional(tag, OptionalFormat.VSize)) {
writeSize(v == null || v.length == 0 ? 1 : v.length * 8 + (v.length > 254 ? 5 : 1));
writeDoubleSeq(v);
}
}
/**
* Writes the remaining contents of the double buffer as a double sequence to the stream.
*
* @param v The double buffer to write to the stream.
*/
public void writeDoubleBuffer(DoubleBuffer v) {
if (v == null || v.remaining() == 0) {
writeSize(0);
} else {
int sz = v.remaining();
writeSize(sz);
expand(sz * 8);
DoubleBuffer doubleBuf = _buf.b.asDoubleBuffer();
doubleBuf.put(v);
_buf.position(_buf.b.position() + sz * 8);
}
}
static final Charset _utf8 = Charset.forName("UTF8");
private CharsetEncoder _charEncoder;
/**
* Writes a string to the stream.
*
* @param v The string to write to the stream. Passing <code>null</code> causes an empty string
* to be written to the stream.
*/
public void writeString(String v) {
if (v == null) {
writeSize(0);
} else {
final int len = v.length();
if (len > 0) {
if (_stringBytes == null || len > _stringBytes.length) {
_stringBytes = new byte[len];
}
if (_stringChars == null || len > _stringChars.length) {
_stringChars = new char[len];
}
//
// If the string contains only 7-bit characters, it's more efficient
// to perform the conversion to UTF-8 manually.
//
v.getChars(0, len, _stringChars, 0);
for (int i = 0; i < len; i++) {
if (_stringChars[i] > (char) 127) {
//
// Found a multibyte character.
//
if (_charEncoder == null) {
_charEncoder = _utf8.newEncoder();
}
ByteBuffer b = null;
try {
b = _charEncoder.encode(CharBuffer.wrap(_stringChars, 0, len));
} catch (CharacterCodingException ex) {
throw new MarshalException("failed to encode multibyte character", ex);
}
writeSize(b.limit());
expand(b.limit());
_buf.b.put(b);
return;
}
_stringBytes[i] = (byte) _stringChars[i];
}
writeSize(len);
expand(len);
_buf.b.put(_stringBytes, 0, len);
} else {
writeSize(0);
}
}
}
/**
* Writes an optional string to the stream.
*
* @param tag The optional tag.
* @param v The optional string to write to the stream.
*/
public void writeString(int tag, Optional<String> v) {
if (v != null && v.isPresent()) {
writeString(tag, v.get());
}
}
/**
* Writes an optional string to the stream.
*
* @param tag The optional tag.
* @param v The string to write to the stream.
*/
public void writeString(int tag, String v) {
if (writeOptional(tag, OptionalFormat.VSize)) {
writeString(v);
}
}
/**
* Writes a string sequence to the stream.
*
* @param v The string sequence to write to the stream. Passing <code>null</code> causes an
* empty sequence to be written to the stream.
*/
public void writeStringSeq(String[] v) {
if (v == null) {
writeSize(0);
} else {
writeSize(v.length);
for (String e : v) {
writeString(e);
}
}
}
/**
* Writes an optional string sequence to the stream.
*
* @param tag The optional tag.
* @param v The optional string sequence to write to the stream.
*/
public void writeStringSeq(int tag, Optional<String[]> v) {
if (v != null && v.isPresent()) {
writeStringSeq(tag, v.get());
}
}
/**
* Writes an optional string sequence to the stream.
*
* @param tag The optional tag.
* @param v The string sequence to write to the stream.
*/
public void writeStringSeq(int tag, String[] v) {
if (writeOptional(tag, OptionalFormat.FSize)) {
int pos = startSize();
writeStringSeq(v);
endSize(pos);
}
}
/**
* Writes a proxy to the stream.
*
* @param v The proxy to write.
*/
public void writeProxy(ObjectPrx v) {
if (v != null) {
v._write(this);
} else {
Identity ident = new Identity();
ident.ice_writeMembers(this);
}
}
/**
* Writes an optional proxy to the stream.
*
* @param <Prx> The proxy type.
* @param tag The optional tag.
* @param v The optional proxy to write to the stream.
*/
public <Prx extends ObjectPrx> void writeProxy(int tag, Optional<Prx> v) {
if (v != null && v.isPresent()) {
writeProxy(tag, v.get());
}
}
/**
* Writes an optional proxy to the stream.
*
* @param tag The optional tag.
* @param v The proxy to write to the stream.
*/
public void writeProxy(int tag, ObjectPrx v) {
if (writeOptional(tag, OptionalFormat.FSize)) {
int pos = startSize();
writeProxy(v);
endSize(pos);
}
}
/**
* Write an enumerated value.
*
* @param v The enumerator.
* @param maxValue The maximum enumerator value in the definition.
*/
public void writeEnum(int v, int maxValue) {
if (isEncoding_1_0()) {
if (maxValue < 127) {
writeByte((byte) v);
} else if (maxValue < 32767) {
writeShort((short) v);
} else {
writeInt(v);
}
} else {
writeSize(v);
}
}
/**
* Writes a Slice value to the stream.
*
* @param v The value to write. This method writes the index of an instance; the state of the
* value is written once {@link #writePendingValues} is called.
*/
public void writeValue(Value v) {
initEncaps();
_encapsStack.encoder.writeValue(v);
}
/**
* Writes a user exception to the stream.
*
* @param v The user exception to write.
*/
public void writeException(UserException v) {
initEncaps();
// Exceptions are always encoded with the sliced format.
_encapsStack.format = FormatType.SlicedFormat;
_encapsStack.encoder.writeException(v);
}
private boolean writeOptionalImpl(int tag, OptionalFormat format) {
if (isEncoding_1_0()) {
return false; // Optional members aren't supported with the 1.0 encoding.
}
int v = format.value();
if (tag < 30) {
v |= tag << 3;
writeByte((byte) v);
} else {
v |= 0x0F0; // tag = 30
writeByte((byte) v);
writeSize(tag);
}
return true;
}
/**
* Determines the current position in the stream.
*
* @return The current position.
*/
public int pos() {
return _buf.b.position();
}
/**
* Sets the current position in the stream.
*
* @param n The new position.
*/
public void pos(int n) {
_buf.position(n);
}
/**
* Determines the current size of the stream.
*
* @return The current size.
*/
public int size() {
return _buf.size();
}
/**
* Determines whether the stream is empty.
*
* @return True if no data has been written yet, false otherwise.
*/
public boolean isEmpty() {
return _buf.empty();
}
/**
* Expand the stream to accept more data.
*
* @param n The number of bytes to accommodate in the stream.
*/
public void expand(int n) {
_buf.expand(n);
}
private Buffer _buf;
private FormatType _format;
private byte[] _stringBytes; // Reusable array for string operations.
private char[] _stringChars; // Reusable array for string operations.
private enum SliceType {
NoSlice,
ValueSlice,
ExceptionSlice
}
private abstract static class EncapsEncoder {
protected EncapsEncoder(OutputStream stream, Encaps encaps) {
_stream = stream;
_encaps = encaps;
_typeIdIndex = 0;
_marshaledMap = new IdentityHashMap<>();
}
abstract void writeValue(Value v);
abstract void writeException(UserException v);
abstract void startInstance(SliceType type, SlicedData data);
abstract void endInstance();
abstract void startSlice(String typeId, int compactId, boolean last);
abstract void endSlice();
boolean writeOptional(int tag, OptionalFormat format) {
return false;
}
void writePendingValues() {}
protected int registerTypeId(String typeId) {
// Lazy initialization
if (_typeIdMap == null) {
_typeIdMap = new TreeMap<>();
}
Integer p = _typeIdMap.get(typeId);
if (p != null) {
return p;
} else {
_typeIdMap.put(typeId, ++_typeIdIndex);
return -1;
}
}
protected final OutputStream _stream;
protected final Encaps _encaps;
// Encapsulation attributes for instance marshaling.
protected final IdentityHashMap<Value, Integer> _marshaledMap;
private TreeMap<String, Integer> _typeIdMap;
private int _typeIdIndex;
}
private static final class EncapsEncoder10 extends EncapsEncoder {
EncapsEncoder10(OutputStream stream, Encaps encaps) {
super(stream, encaps);
_sliceType = SliceType.NoSlice;
_valueIdIndex = 0;
_toBeMarshaledMap = new IdentityHashMap<>();
}
@Override
void writeValue(Value v) {
//
// Object references are encoded as a negative integer in 1.0.
//
if (v != null) {
_stream.writeInt(-registerValue(v));
} else {
_stream.writeInt(0);
}
}
@Override
void writeException(UserException v) {
//
// User exception with the 1.0 encoding start with a boolean flag that indicates whether
// or not the exception uses classes.
//
// This allows reading the pending instances even if some part of the exception was
// sliced.
//
boolean usesClasses = v._usesClasses();
_stream.writeBool(usesClasses);
v._write(_stream);
if (usesClasses) {
writePendingValues();
}
}
@Override
void startInstance(SliceType sliceType, SlicedData sliceData) {
_sliceType = sliceType;
}
@Override
void endInstance() {
if (_sliceType == SliceType.ValueSlice) {
//
// Write the Object slice.
//
startSlice(Value.ice_staticId(), -1, true);
_stream.writeSize(0); // For compatibility with the old AFM.
endSlice();
}
_sliceType = SliceType.NoSlice;
}
@Override
void startSlice(String typeId, int compactId, boolean last) {
//
// For instance slices, encode a boolean to indicate how the type ID
// is encoded and the type ID either as a string or index. For exception slices, always
// encode the type ID as a string.
//
if (_sliceType == SliceType.ValueSlice) {
int index = registerTypeId(typeId);
if (index < 0) {
_stream.writeBool(false);
_stream.writeString(typeId);
} else {
_stream.writeBool(true);
_stream.writeSize(index);
}
} else {
_stream.writeString(typeId);
}
_stream.writeInt(0); // Placeholder for the slice length.
_writeSlice = _stream.pos();
}
@Override
void endSlice() {
//
// Write the slice length.
//
final int sz = _stream.pos() - _writeSlice + 4;
_stream.rewriteInt(sz, _writeSlice - 4);
}
@Override
void writePendingValues() {
while (!_toBeMarshaledMap.isEmpty()) {
//
// Consider the to be marshaled instances as marshaled now, this is necessary to
// avoid adding again the "to be
// marshaled instances" into _toBeMarshaledMap while writing
// instances.
//
_marshaledMap.putAll(_toBeMarshaledMap);
IdentityHashMap<Value, Integer> savedMap = _toBeMarshaledMap;
_toBeMarshaledMap = new IdentityHashMap<>();
_stream.writeSize(savedMap.size());
for (Map.Entry<Value, Integer> p : savedMap.entrySet()) {
//
// Ask the instance to marshal itself. Any new class instances that are
// triggered by the classes marshaled are added to toBeMarshaledMap.
//
_stream.writeInt(p.getValue().intValue());
p.getKey().ice_preMarshal();
p.getKey()._iceWrite(_stream);
}
}
_stream.writeSize(
0); // Zero marker indicates end of sequence of sequences of instances.
}
private int registerValue(Value v) {
assert (v != null);
//
// Look for this instance in the to-be-marshaled map.
//
Integer p = _toBeMarshaledMap.get(v);
if (p != null) {
return p.intValue();
}
//
// Didn't find it, try the marshaled map next.
//
p = _marshaledMap.get(v);
if (p != null) {
return p.intValue();
}
//
// We haven't seen this instance previously, create a new
// index, and insert it into the to-be-marshaled map.
//
_toBeMarshaledMap.put(v, ++_valueIdIndex);
return _valueIdIndex;
}
// Instance attributes
private SliceType _sliceType;
// Slice attributes
private int _writeSlice; // Position of the slice data members
// Encapsulation attributes for instance marshaling.
private int _valueIdIndex;
private IdentityHashMap<Value, Integer> _toBeMarshaledMap;
}
private static final class EncapsEncoder11 extends EncapsEncoder {
EncapsEncoder11(OutputStream stream, Encaps encaps) {
super(stream, encaps);
_current = null;
_valueIdIndex = 1;
}
@Override
void writeValue(Value v) {
if (v == null) {
_stream.writeSize(0);
} else if (_current != null && _encaps.format == FormatType.SlicedFormat) {
// Lazy initialization
if (_current.indirectionTable == null) {
_current.indirectionTable = new ArrayList<>();
_current.indirectionMap = new IdentityHashMap<>();
}
//
// If writing an instance within a slice and using the sliced format, write an index
// from the instance indirection table. The indirect instance table is encoded at
// the end of each slice and is always read (even if the Slice is unknown).
//
Integer index = _current.indirectionMap.get(v);
if (index == null) {
_current.indirectionTable.add(v);
final int idx =
_current.indirectionTable
.size(); // Position + 1 (0 is reserved for nil)
_current.indirectionMap.put(v, idx);
_stream.writeSize(idx);
} else {
_stream.writeSize(index.intValue());
}
} else {
writeInstance(v); // Write the instance or a reference if already marshaled.
}
}
@Override
void writeException(UserException v) {
v._write(_stream);
}
@Override
void startInstance(SliceType sliceType, SlicedData data) {
if (_current == null) {
_current = new InstanceData(null);
} else {
_current = _current.next == null ? new InstanceData(_current) : _current.next;
}
_current.sliceType = sliceType;
_current.firstSlice = true;
if (data != null) {
writeSlicedData(data);
}
}
@Override
void endInstance() {
_current = _current.previous;
}
@Override
void startSlice(String typeId, int compactId, boolean last) {
assert ((_current.indirectionTable == null || _current.indirectionTable.isEmpty())
&& (_current.indirectionMap == null || _current.indirectionMap.isEmpty()));
_current.sliceFlagsPos = _stream.pos();
_current.sliceFlags = (byte) 0;
if (_encaps.format == FormatType.SlicedFormat) {
// Encode the slice size if using the sliced format.
_current.sliceFlags |= Protocol.FLAG_HAS_SLICE_SIZE;
}
if (last) {
_current.sliceFlags |= Protocol.FLAG_IS_LAST_SLICE; // This is the last slice.
}
_stream.writeByte((byte) 0); // Placeholder for the slice flags
//
// For instance slices, encode the flag and the type ID either as a string or index. For
// exception slices, always encode the type ID a string.
//
if (_current.sliceType == SliceType.ValueSlice) {
//
// Encode the type ID (only in the first slice for the compact encoding).
//
if (_encaps.format == FormatType.SlicedFormat || _current.firstSlice) {
if (compactId != -1) {
_current.sliceFlags |= Protocol.FLAG_HAS_TYPE_ID_COMPACT;
_stream.writeSize(compactId);
} else {
int index = registerTypeId(typeId);
if (index < 0) {
_current.sliceFlags |= Protocol.FLAG_HAS_TYPE_ID_STRING;
_stream.writeString(typeId);
} else {
_current.sliceFlags |= Protocol.FLAG_HAS_TYPE_ID_INDEX;
_stream.writeSize(index);
}
}
}
} else {
_stream.writeString(typeId);
}
if ((_current.sliceFlags & Protocol.FLAG_HAS_SLICE_SIZE) != 0) {
_stream.writeInt(0); // Placeholder for the slice length.
}
_current.writeSlice = _stream.pos();
_current.firstSlice = false;
}
@Override
void endSlice() {
//
// Write the optional member end marker if some optional members were encoded. Note that
// the optional members are encoded before the indirection table and are included in the
// slice size.
//
if ((_current.sliceFlags & Protocol.FLAG_HAS_OPTIONAL_MEMBERS) != 0) {
_stream.writeByte((byte) Protocol.OPTIONAL_END_MARKER);
}
//
// Write the slice length if necessary.
//
if ((_current.sliceFlags & Protocol.FLAG_HAS_SLICE_SIZE) != 0) {
final int sz = _stream.pos() - _current.writeSlice + 4;
_stream.rewriteInt(sz, _current.writeSlice - 4);
}
//
// Only write the indirection table if it contains entries.
//
if (_current.indirectionTable != null && !_current.indirectionTable.isEmpty()) {
assert (_encaps.format == FormatType.SlicedFormat);
_current.sliceFlags |= Protocol.FLAG_HAS_INDIRECTION_TABLE;
//
// Write the indirection instance table.
//
_stream.writeSize(_current.indirectionTable.size());
for (Value v : _current.indirectionTable) {
writeInstance(v);
}
_current.indirectionTable.clear();
_current.indirectionMap.clear();
}
//
// Finally, update the slice flags.
//
_stream.rewriteByte(_current.sliceFlags, _current.sliceFlagsPos);
}
@Override
boolean writeOptional(int tag, OptionalFormat format) {
if (_current == null) {
return _stream.writeOptionalImpl(tag, format);
} else {
if (_stream.writeOptionalImpl(tag, format)) {
_current.sliceFlags |= Protocol.FLAG_HAS_OPTIONAL_MEMBERS;
return true;
} else {
return false;
}
}
}
private void writeSlicedData(SlicedData slicedData) {
assert (slicedData != null);
//
// We only remarshal preserved slices if we are using the sliced
// format. Otherwise, we ignore the preserved slices, which
// essentially "slices" the instance into the most-derived type
// known by the sender.
//
if (_encaps.format != FormatType.SlicedFormat) {
return;
}
for (SliceInfo info : slicedData.slices) {
startSlice(info.typeId, info.compactId, info.isLastSlice);
//
// Write the bytes associated with this slice.
//
_stream.writeBlob(info.bytes);
if (info.hasOptionalMembers) {
_current.sliceFlags |= Protocol.FLAG_HAS_OPTIONAL_MEMBERS;
}
// Make sure to also re-write the instance indirection table.
if (info.instances != null && info.instances.length > 0) {
// Lazy initialization
if (_current.indirectionTable == null) {
_current.indirectionTable = new ArrayList<>();
_current.indirectionMap = new IdentityHashMap<>();
}
for (Value o : info.instances) {
_current.indirectionTable.add(o);
}
}
endSlice();
}
}
private void writeInstance(Value v) {
assert (v != null);
//
// If the instance was already marshaled, just write it's ID.
//
Integer p = _marshaledMap.get(v);
if (p != null) {
_stream.writeSize(p);
return;
}
//
// We haven't seen this instance previously, create a new ID,
// insert it into the marshaled map, and write the instance.
//
_marshaledMap.put(v, ++_valueIdIndex);
v.ice_preMarshal();
_stream.writeSize(1); // Class instance marker.
v._iceWrite(_stream);
}
private static final class InstanceData {
InstanceData(InstanceData previous) {
if (previous != null) {
previous.next = this;
}
this.previous = previous;
this.next = null;
}
// Instance attributes
SliceType sliceType;
boolean firstSlice;
// Slice attributes
byte sliceFlags;
int writeSlice; // Position of the slice data members
int sliceFlagsPos; // Position of the slice flags
List<Value> indirectionTable;
IdentityHashMap<Value, Integer> indirectionMap;
final InstanceData previous;
InstanceData next;
}
private InstanceData _current;
private int _valueIdIndex; // The ID of the next instance to marhsal
}
private static final class Encaps {
void reset() {
encoder = null;
}
void setEncoding(EncodingVersion encoding) {
this.encoding = encoding;
encoding_1_0 = encoding.equals(Util.Encoding_1_0);
}
int start;
FormatType format;
EncodingVersion encoding;
boolean encoding_1_0;
EncapsEncoder encoder;
Encaps next;
}
//
// The encoding version to use when there's no encapsulation to read from or write to. This is
// for example used to read message headers or when the user is using the streaming API with no
// encapsulation.
//
private EncodingVersion _encoding;
private boolean isEncoding_1_0() {
return _encapsStack != null
? _encapsStack.encoding_1_0
: _encoding.equals(Util.Encoding_1_0);
}
private Encaps _encapsStack;
private Encaps _encapsCache;
private void initEncaps() {
// Lazy initialization
if (_encapsStack == null) {
_encapsStack = _encapsCache;
if (_encapsStack != null) {
_encapsCache = _encapsCache.next;
} else {
_encapsStack = new Encaps();
}
_encapsStack.setEncoding(_encoding);
}
if (_encapsStack.format == null) {
_encapsStack.format = _format;
}
// Lazy initialization.
if (_encapsStack.encoder == null) {
if (_encapsStack.encoding_1_0) {
_encapsStack.encoder = new EncapsEncoder10(this, _encapsStack);
} else {
_encapsStack.encoder = new EncapsEncoder11(this, _encapsStack);
}
}
}
/**
* @hidden
*/
@FunctionalInterface
public static interface Marshaler {
void marshal(OutputStream ostr);
}
}