| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package org.chromium.mojo.bindings; |
| |
| import org.chromium.mojo.bindings.Interface.Proxy.Handler; |
| import org.chromium.mojo.system.Core; |
| import org.chromium.mojo.system.Handle; |
| import org.chromium.mojo.system.MessagePipeHandle; |
| import org.chromium.mojo.system.Pair; |
| |
| import java.nio.ByteBuffer; |
| import java.nio.ByteOrder; |
| import java.nio.charset.Charset; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| /** |
| * Helper class to encode a mojo struct. It keeps track of the output buffer, resizing it as needed. |
| * It also keeps track of the associated handles, and the offset of the current data section. |
| */ |
| public class Encoder { |
| |
| /** |
| * Container class for all state that must be shared between the main encoder and any used sub |
| * encoder. |
| */ |
| private static class EncoderState { |
| |
| /** |
| * The core used to encode interfaces. |
| */ |
| public final Core core; |
| |
| /** |
| * The ByteBuffer to which the message will be encoded. |
| */ |
| public ByteBuffer byteBuffer; |
| |
| /** |
| * The list of encountered handles. |
| */ |
| public final List<Handle> handles = new ArrayList<Handle>(); |
| |
| /** |
| * The current absolute position for the next data section. |
| */ |
| public int dataEnd; |
| |
| /** |
| * @param core the |Core| implementation used to generate handles. Only used if the data |
| * structure being encoded contains interfaces, can be |null| otherwise. |
| * @param bufferSize A hint on the size of the message. Used to build the initial byte |
| * buffer. |
| */ |
| private EncoderState(Core core, int bufferSize) { |
| assert bufferSize % BindingsHelper.ALIGNMENT == 0; |
| this.core = core; |
| byteBuffer = ByteBuffer.allocateDirect( |
| bufferSize > 0 ? bufferSize : INITIAL_BUFFER_SIZE); |
| byteBuffer.order(ByteOrder.LITTLE_ENDIAN); |
| dataEnd = 0; |
| } |
| |
| /** |
| * Claim the given amount of memory at the end of the buffer, resizing it if needed. |
| */ |
| public void claimMemory(int size) { |
| dataEnd += size; |
| growIfNeeded(); |
| } |
| |
| /** |
| * Grow the associated ByteBuffer if needed. |
| */ |
| private void growIfNeeded() { |
| if (byteBuffer.capacity() >= dataEnd) { |
| return; |
| } |
| int targetSize = byteBuffer.capacity() * 2; |
| while (targetSize < dataEnd) { |
| targetSize *= 2; |
| } |
| ByteBuffer newBuffer = ByteBuffer.allocateDirect(targetSize); |
| newBuffer.order(ByteOrder.nativeOrder()); |
| byteBuffer.position(0); |
| byteBuffer.limit(byteBuffer.capacity()); |
| newBuffer.put(byteBuffer); |
| byteBuffer = newBuffer; |
| } |
| } |
| |
| /** |
| * Default initial size of the data buffer. This must be a multiple of 8 bytes. |
| */ |
| private static final int INITIAL_BUFFER_SIZE = 1024; |
| |
| /** |
| * Base offset in the byte buffer for writing. |
| */ |
| private int mBaseOffset; |
| |
| /** |
| * The encoder state shared by the main encoder and all its sub-encoder. |
| */ |
| private final EncoderState mEncoderState; |
| |
| /** |
| * Returns the result message. |
| */ |
| public Message getMessage() { |
| mEncoderState.byteBuffer.position(0); |
| mEncoderState.byteBuffer.limit(mEncoderState.dataEnd); |
| return new Message(mEncoderState.byteBuffer, mEncoderState.handles); |
| } |
| |
| /** |
| * Constructor. |
| * |
| * @param core the |Core| implementation used to generate handles. Only used if the data |
| * structure being encoded contains interfaces, can be |null| otherwise. |
| * @param sizeHint A hint on the size of the message. Used to build the initial byte buffer. |
| */ |
| public Encoder(Core core, int sizeHint) { |
| this(new EncoderState(core, sizeHint)); |
| } |
| |
| /** |
| * Private constructor for sub-encoders. |
| */ |
| private Encoder(EncoderState bufferInformation) { |
| mEncoderState = bufferInformation; |
| mBaseOffset = bufferInformation.dataEnd; |
| } |
| |
| /** |
| * Returns a new encoder that will append to the current buffer. |
| */ |
| public Encoder getEncoderAtDataOffset(DataHeader dataHeader) { |
| Encoder result = new Encoder(mEncoderState); |
| result.encode(dataHeader); |
| return result; |
| } |
| |
| /** |
| * Encode a {@link DataHeader} and claim the amount of memory required for the data section |
| * (resizing the buffer if required). |
| */ |
| public void encode(DataHeader s) { |
| mEncoderState.claimMemory(BindingsHelper.align(s.size)); |
| encode(s.size, DataHeader.SIZE_OFFSET); |
| encode(s.elementsOrVersion, DataHeader.ELEMENTS_OR_VERSION_OFFSET); |
| } |
| |
| /** |
| * Encode a byte at the given offset. |
| */ |
| public void encode(byte v, int offset) { |
| mEncoderState.byteBuffer.put(mBaseOffset + offset, v); |
| } |
| |
| /** |
| * Encode a boolean at the given offset. |
| */ |
| public void encode(boolean v, int offset, int bit) { |
| if (v) { |
| byte encodedValue = mEncoderState.byteBuffer.get(mBaseOffset + offset); |
| encodedValue |= (byte) (1 << bit); |
| mEncoderState.byteBuffer.put(mBaseOffset + offset, encodedValue); |
| } |
| } |
| |
| /** |
| * Encode a short at the given offset. |
| */ |
| public void encode(short v, int offset) { |
| mEncoderState.byteBuffer.putShort(mBaseOffset + offset, v); |
| } |
| |
| /** |
| * Encode an int at the given offset. |
| */ |
| public void encode(int v, int offset) { |
| mEncoderState.byteBuffer.putInt(mBaseOffset + offset, v); |
| } |
| |
| /** |
| * Encode a float at the given offset. |
| */ |
| public void encode(float v, int offset) { |
| mEncoderState.byteBuffer.putFloat(mBaseOffset + offset, v); |
| } |
| |
| /** |
| * Encode a long at the given offset. |
| */ |
| public void encode(long v, int offset) { |
| mEncoderState.byteBuffer.putLong(mBaseOffset + offset, v); |
| } |
| |
| /** |
| * Encode a double at the given offset. |
| */ |
| public void encode(double v, int offset) { |
| mEncoderState.byteBuffer.putDouble(mBaseOffset + offset, v); |
| } |
| |
| /** |
| * Encode a {@link Struct} at the given offset. |
| */ |
| public void encode(Struct v, int offset, boolean nullable) { |
| if (v == null) { |
| encodeNullPointer(offset, nullable); |
| return; |
| } |
| encodePointerToNextUnclaimedData(offset); |
| v.encode(this); |
| } |
| |
| /** |
| * Encode a {@link Union} at the given offset. |
| */ |
| public void encode(Union v, int offset, boolean nullable) { |
| if (v == null && !nullable) { |
| throw new SerializationException( |
| "Trying to encode a null pointer for a non-nullable type."); |
| } |
| if (v == null) { |
| encode(0L, offset); |
| encode(0L, offset + DataHeader.HEADER_SIZE); |
| return; |
| } |
| v.encode(this, offset); |
| } |
| |
| /** |
| * Encodes a String. |
| */ |
| public void encode(String v, int offset, boolean nullable) { |
| if (v == null) { |
| encodeNullPointer(offset, nullable); |
| return; |
| } |
| final int arrayNullability = nullable |
| ? BindingsHelper.ARRAY_NULLABLE : BindingsHelper.NOTHING_NULLABLE; |
| encode(v.getBytes(Charset.forName("utf8")), offset, arrayNullability, |
| BindingsHelper.UNSPECIFIED_ARRAY_LENGTH); |
| } |
| |
| /** |
| * Encodes a {@link Handle}. |
| */ |
| public void encode(Handle v, int offset, boolean nullable) { |
| if (v == null || !v.isValid()) { |
| encodeInvalidHandle(offset, nullable); |
| } else { |
| encode(mEncoderState.handles.size(), offset); |
| mEncoderState.handles.add(v); |
| } |
| } |
| |
| /** |
| * Encode an {@link Interface}. |
| */ |
| public <T extends Interface> void encode(T v, int offset, boolean nullable, |
| Interface.Manager<T, ?> manager) { |
| if (v == null) { |
| encodeInvalidHandle(offset, nullable); |
| encode(0, offset + BindingsHelper.SERIALIZED_HANDLE_SIZE); |
| return; |
| } |
| if (mEncoderState.core == null) { |
| throw new UnsupportedOperationException( |
| "The encoder has been created without a Core. It can't encode an interface."); |
| } |
| // If the instance is a proxy, pass the proxy's handle instead of creating a new stub. |
| if (v instanceof Interface.Proxy) { |
| Handler handler = ((Interface.Proxy) v).getProxyHandler(); |
| encode(handler.passHandle(), offset, nullable); |
| encode(handler.getVersion(), offset + BindingsHelper.SERIALIZED_HANDLE_SIZE); |
| return; |
| } |
| Pair<MessagePipeHandle, MessagePipeHandle> handles = |
| mEncoderState.core.createMessagePipe(null); |
| manager.bind(v, handles.first); |
| encode(handles.second, offset, nullable); |
| encode(manager.getVersion(), offset + BindingsHelper.SERIALIZED_HANDLE_SIZE); |
| } |
| |
| /** |
| * Encode an {@link InterfaceRequest}. |
| */ |
| public <I extends Interface> void encode(InterfaceRequest<I> v, int offset, boolean nullable) { |
| if (v == null) { |
| encodeInvalidHandle(offset, nullable); |
| return; |
| } |
| if (mEncoderState.core == null) { |
| throw new UnsupportedOperationException( |
| "The encoder has been created without a Core. It can't encode an interface."); |
| } |
| encode(v.passHandle(), offset, nullable); |
| } |
| |
| /** |
| * Returns an {@link Encoder} suitable for encoding an array of pointer of the given length. |
| */ |
| public Encoder encodePointerArray(int length, int offset, int expectedLength) { |
| return encoderForArray(BindingsHelper.POINTER_SIZE, length, offset, expectedLength); |
| } |
| |
| /** |
| * Returns an {@link Encoder} suitable for encoding an array of union of the given length. |
| */ |
| public Encoder encodeUnionArray(int length, int offset, int expectedLength) { |
| return encoderForArray(BindingsHelper.UNION_SIZE, length, offset, expectedLength); |
| } |
| |
| /** |
| * Encodes an array of booleans. |
| */ |
| public void encode(boolean[] v, int offset, int arrayNullability, int expectedLength) { |
| if (v == null) { |
| encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); |
| return; |
| } |
| if (expectedLength != BindingsHelper.UNSPECIFIED_ARRAY_LENGTH |
| && expectedLength != v.length) { |
| throw new SerializationException("Trying to encode a fixed array of incorrect length."); |
| } |
| byte[] bytes = new byte[(v.length + 7) / BindingsHelper.ALIGNMENT]; |
| for (int i = 0; i < bytes.length; ++i) { |
| for (int j = 0; j < BindingsHelper.ALIGNMENT; ++j) { |
| int booleanIndex = BindingsHelper.ALIGNMENT * i + j; |
| if (booleanIndex < v.length && v[booleanIndex]) { |
| bytes[i] |= (byte) (1 << j); |
| } |
| } |
| } |
| encodeByteArray(bytes, v.length, offset); |
| } |
| |
| /** |
| * Encodes an array of bytes. |
| */ |
| public void encode(byte[] v, int offset, int arrayNullability, int expectedLength) { |
| if (v == null) { |
| encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); |
| return; |
| } |
| if (expectedLength != BindingsHelper.UNSPECIFIED_ARRAY_LENGTH |
| && expectedLength != v.length) { |
| throw new SerializationException("Trying to encode a fixed array of incorrect length."); |
| } |
| encodeByteArray(v, v.length, offset); |
| } |
| |
| /** |
| * Encodes an array of shorts. |
| */ |
| public void encode(short[] v, int offset, int arrayNullability, int expectedLength) { |
| if (v == null) { |
| encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); |
| return; |
| } |
| encoderForArray(2, v.length, offset, expectedLength).append(v); |
| } |
| |
| /** |
| * Encodes an array of ints. |
| */ |
| public void encode(int[] v, int offset, int arrayNullability, int expectedLength) { |
| if (v == null) { |
| encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); |
| return; |
| } |
| encoderForArray(4, v.length, offset, expectedLength).append(v); |
| } |
| |
| /** |
| * Encodes an array of floats. |
| */ |
| public void encode(float[] v, int offset, int arrayNullability, int expectedLength) { |
| if (v == null) { |
| encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); |
| return; |
| } |
| encoderForArray(4, v.length, offset, expectedLength).append(v); |
| } |
| |
| /** |
| * Encodes an array of longs. |
| */ |
| public void encode(long[] v, int offset, int arrayNullability, int expectedLength) { |
| if (v == null) { |
| encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); |
| return; |
| } |
| encoderForArray(8, v.length, offset, expectedLength).append(v); |
| } |
| |
| /** |
| * Encodes an array of doubles. |
| */ |
| public void encode(double[] v, int offset, int arrayNullability, int expectedLength) { |
| if (v == null) { |
| encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); |
| return; |
| } |
| encoderForArray(8, v.length, offset, expectedLength).append(v); |
| } |
| |
| /** |
| * Encodes an array of {@link Handle}. |
| */ |
| public void encode(Handle[] v, int offset, int arrayNullability, int expectedLength) { |
| if (v == null) { |
| encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); |
| return; |
| } |
| Encoder e = encoderForArray( |
| BindingsHelper.SERIALIZED_HANDLE_SIZE, v.length, offset, expectedLength); |
| for (int i = 0; i < v.length; ++i) { |
| e.encode(v[i], DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_HANDLE_SIZE * i, |
| BindingsHelper.isElementNullable(arrayNullability)); |
| } |
| } |
| |
| /** |
| * Encodes an array of {@link Interface}. |
| */ |
| public <T extends Interface> void encode(T[] v, int offset, int arrayNullability, |
| int expectedLength, Interface.Manager<T, ?> manager) { |
| if (v == null) { |
| encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); |
| return; |
| } |
| Encoder e = encoderForArray( |
| BindingsHelper.SERIALIZED_INTERFACE_SIZE, v.length, offset, expectedLength); |
| for (int i = 0; i < v.length; ++i) { |
| e.encode(v[i], DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_INTERFACE_SIZE * i, |
| BindingsHelper.isElementNullable(arrayNullability), manager); |
| } |
| } |
| |
| public Encoder encoderForMap(int offset) { |
| encodePointerToNextUnclaimedData(offset); |
| return getEncoderAtDataOffset(BindingsHelper.MAP_STRUCT_HEADER); |
| } |
| |
| /** |
| * Encodes a pointer to the next unclaimed memory and returns an encoder suitable to encode an |
| * union at this location. |
| */ |
| public Encoder encoderForUnionPointer(int offset) { |
| encodePointerToNextUnclaimedData(offset); |
| Encoder result = new Encoder(mEncoderState); |
| result.mEncoderState.claimMemory(16); |
| return result; |
| } |
| |
| /** |
| * Encodes an array of {@link InterfaceRequest}. |
| */ |
| public <I extends Interface> void encode(InterfaceRequest<I>[] v, int offset, |
| int arrayNullability, int expectedLength) { |
| if (v == null) { |
| encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); |
| return; |
| } |
| Encoder e = encoderForArray( |
| BindingsHelper.SERIALIZED_HANDLE_SIZE, v.length, offset, expectedLength); |
| for (int i = 0; i < v.length; ++i) { |
| e.encode(v[i], DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_HANDLE_SIZE * i, |
| BindingsHelper.isElementNullable(arrayNullability)); |
| } |
| } |
| |
| /** |
| * Encodes a <code>null</code> pointer iff the object is nullable, raises an exception |
| * otherwise. |
| */ |
| public void encodeNullPointer(int offset, boolean nullable) { |
| if (!nullable) { |
| throw new SerializationException( |
| "Trying to encode a null pointer for a non-nullable type."); |
| } |
| mEncoderState.byteBuffer.putLong(mBaseOffset + offset, 0); |
| } |
| |
| /** |
| * Encodes an invalid handle iff the object is nullable, raises an exception otherwise. |
| */ |
| public void encodeInvalidHandle(int offset, boolean nullable) { |
| if (!nullable) { |
| throw new SerializationException( |
| "Trying to encode an invalid handle for a non-nullable type."); |
| } |
| mEncoderState.byteBuffer.putInt(mBaseOffset + offset, -1); |
| } |
| |
| /** |
| * Claim the given amount of memory at the end of the buffer, resizing it if needed. |
| */ |
| void claimMemory(int size) { |
| mEncoderState.claimMemory(BindingsHelper.align(size)); |
| } |
| |
| private void encodePointerToNextUnclaimedData(int offset) { |
| encode((long) mEncoderState.dataEnd - (mBaseOffset + offset), offset); |
| } |
| |
| private Encoder encoderForArray( |
| int elementSizeInByte, int length, int offset, int expectedLength) { |
| if (expectedLength != BindingsHelper.UNSPECIFIED_ARRAY_LENGTH |
| && expectedLength != length) { |
| throw new SerializationException("Trying to encode a fixed array of incorrect length."); |
| } |
| return encoderForArrayByTotalSize(length * elementSizeInByte, length, offset); |
| } |
| |
| private Encoder encoderForArrayByTotalSize(int byteSize, int length, int offset) { |
| encodePointerToNextUnclaimedData(offset); |
| return getEncoderAtDataOffset( |
| new DataHeader(DataHeader.HEADER_SIZE + byteSize, length)); |
| } |
| |
| private void encodeByteArray(byte[] bytes, int length, int offset) { |
| encoderForArrayByTotalSize(bytes.length, length, offset).append(bytes); |
| } |
| |
| private void append(byte[] v) { |
| mEncoderState.byteBuffer.position(mBaseOffset + DataHeader.HEADER_SIZE); |
| mEncoderState.byteBuffer.put(v); |
| } |
| |
| private void append(short[] v) { |
| mEncoderState.byteBuffer.position(mBaseOffset + DataHeader.HEADER_SIZE); |
| mEncoderState.byteBuffer.asShortBuffer().put(v); |
| } |
| |
| private void append(int[] v) { |
| mEncoderState.byteBuffer.position(mBaseOffset + DataHeader.HEADER_SIZE); |
| mEncoderState.byteBuffer.asIntBuffer().put(v); |
| } |
| |
| private void append(float[] v) { |
| mEncoderState.byteBuffer.position(mBaseOffset + DataHeader.HEADER_SIZE); |
| mEncoderState.byteBuffer.asFloatBuffer().put(v); |
| } |
| |
| private void append(double[] v) { |
| mEncoderState.byteBuffer.position(mBaseOffset + DataHeader.HEADER_SIZE); |
| mEncoderState.byteBuffer.asDoubleBuffer().put(v); |
| } |
| |
| private void append(long[] v) { |
| mEncoderState.byteBuffer.position(mBaseOffset + DataHeader.HEADER_SIZE); |
| mEncoderState.byteBuffer.asLongBuffer().put(v); |
| } |
| |
| } |