diff options
Diffstat (limited to 'mojo/public/java')
45 files changed, 5612 insertions, 0 deletions
diff --git a/mojo/public/java/BUILD.gn b/mojo/public/java/BUILD.gn new file mode 100644 index 0000000000..078064114e --- /dev/null +++ b/mojo/public/java/BUILD.gn @@ -0,0 +1,64 @@ +# 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. + +import("//build/config/android/rules.gni") + +android_library("system_java") { + java_files = [ + "system/src/org/chromium/mojo/system/Core.java", + "system/src/org/chromium/mojo/system/DataPipe.java", + "system/src/org/chromium/mojo/system/Flags.java", + "system/src/org/chromium/mojo/system/Handle.java", + "system/src/org/chromium/mojo/system/InvalidHandle.java", + "system/src/org/chromium/mojo/system/MessagePipeHandle.java", + "system/src/org/chromium/mojo/system/MojoException.java", + "system/src/org/chromium/mojo/system/MojoResult.java", + "system/src/org/chromium/mojo/system/Pair.java", + "system/src/org/chromium/mojo/system/ResultAnd.java", + "system/src/org/chromium/mojo/system/SharedBufferHandle.java", + "system/src/org/chromium/mojo/system/UntypedHandle.java", + "system/src/org/chromium/mojo/system/RunLoop.java", + "system/src/org/chromium/mojo/system/Watcher.java", + ] +} + +android_library("bindings_java") { + java_files = [ + "bindings/src/org/chromium/mojo/bindings/AssociatedInterfaceNotSupported.java", + "bindings/src/org/chromium/mojo/bindings/AssociatedInterfaceRequestNotSupported.java", + "bindings/src/org/chromium/mojo/bindings/AutoCloseableRouter.java", + "bindings/src/org/chromium/mojo/bindings/BindingsHelper.java", + "bindings/src/org/chromium/mojo/bindings/Callbacks.java", + "bindings/src/org/chromium/mojo/bindings/ConnectionErrorHandler.java", + "bindings/src/org/chromium/mojo/bindings/Connector.java", + "bindings/src/org/chromium/mojo/bindings/DataHeader.java", + "bindings/src/org/chromium/mojo/bindings/Decoder.java", + "bindings/src/org/chromium/mojo/bindings/DelegatingConnectionErrorHandler.java", + "bindings/src/org/chromium/mojo/bindings/DeserializationException.java", + "bindings/src/org/chromium/mojo/bindings/Encoder.java", + "bindings/src/org/chromium/mojo/bindings/ExecutorFactory.java", + "bindings/src/org/chromium/mojo/bindings/HandleOwner.java", + "bindings/src/org/chromium/mojo/bindings/InterfaceControlMessagesHelper.java", + "bindings/src/org/chromium/mojo/bindings/Interface.java", + "bindings/src/org/chromium/mojo/bindings/InterfaceRequest.java", + "bindings/src/org/chromium/mojo/bindings/MessageHeader.java", + "bindings/src/org/chromium/mojo/bindings/Message.java", + "bindings/src/org/chromium/mojo/bindings/MessageReceiver.java", + "bindings/src/org/chromium/mojo/bindings/MessageReceiverWithResponder.java", + "bindings/src/org/chromium/mojo/bindings/RouterImpl.java", + "bindings/src/org/chromium/mojo/bindings/Router.java", + "bindings/src/org/chromium/mojo/bindings/SerializationException.java", + "bindings/src/org/chromium/mojo/bindings/ServiceMessage.java", + "bindings/src/org/chromium/mojo/bindings/SideEffectFreeCloseable.java", + "bindings/src/org/chromium/mojo/bindings/Struct.java", + "bindings/src/org/chromium/mojo/bindings/Union.java", + ] + + deps = [ + ":system_java", + "//base:base_java", + ] + + srcjar_deps = [ "../interfaces/bindings:bindings_java_sources" ] +} diff --git a/mojo/public/java/bindings/README.md b/mojo/public/java/bindings/README.md new file mode 100644 index 0000000000..821a230ed9 --- /dev/null +++ b/mojo/public/java/bindings/README.md @@ -0,0 +1,12 @@ +# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojo Java Bindings API +This document is a subset of the [Mojo documentation](/mojo). + +[TOC] + +## Overview + +This document provides a brief guide to API usage with example code snippets. +For a detailed API references please consult the class definitions in +[this directory](https://cs.chromium.org/chromium/src/mojo/public/java/bindings/src/org/chromium/mojo/bindings/) + +TODO: Make the contents of this document less non-existent. diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/AssociatedInterfaceNotSupported.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/AssociatedInterfaceNotSupported.java new file mode 100644 index 0000000000..ee8f6310a1 --- /dev/null +++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/AssociatedInterfaceNotSupported.java @@ -0,0 +1,11 @@ +// Copyright 2015 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; + +/** + * Associated interface is not supported yet. + */ +public class AssociatedInterfaceNotSupported { +} diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/AssociatedInterfaceRequestNotSupported.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/AssociatedInterfaceRequestNotSupported.java new file mode 100644 index 0000000000..1b07cd11ba --- /dev/null +++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/AssociatedInterfaceRequestNotSupported.java @@ -0,0 +1,11 @@ +// Copyright 2015 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; + +/** + * Associated interface is not supported yet. + */ +public class AssociatedInterfaceRequestNotSupported { +} diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/AutoCloseableRouter.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/AutoCloseableRouter.java new file mode 100644 index 0000000000..8a83be928e --- /dev/null +++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/AutoCloseableRouter.java @@ -0,0 +1,116 @@ +// 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.system.Core; +import org.chromium.mojo.system.MessagePipeHandle; + +import java.util.concurrent.Executor; + +/** + * Wrapper around {@link Router} that will close the connection when not referenced anymore. + */ +class AutoCloseableRouter implements Router { + + /** + * The underlying router. + */ + private final Router mRouter; + + /** + * The executor to close the underlying router. + */ + private final Executor mExecutor; + + /** + * Flags to keep track if this router has been correctly closed. + */ + private boolean mClosed; + + /** + * Constructor. + */ + public AutoCloseableRouter(Core core, Router router) { + mRouter = router; + mExecutor = ExecutorFactory.getExecutorForCurrentThread(core); + } + + /** + * @see Router#setIncomingMessageReceiver(MessageReceiverWithResponder) + */ + @Override + public void setIncomingMessageReceiver(MessageReceiverWithResponder incomingMessageReceiver) { + mRouter.setIncomingMessageReceiver(incomingMessageReceiver); + } + + /** + * @see HandleOwner#passHandle() + */ + @Override + public MessagePipeHandle passHandle() { + return mRouter.passHandle(); + } + + /** + * @see MessageReceiver#accept(Message) + */ + @Override + public boolean accept(Message message) { + return mRouter.accept(message); + } + + /** + * @see MessageReceiverWithResponder#acceptWithResponder(Message, MessageReceiver) + */ + @Override + public boolean acceptWithResponder(Message message, MessageReceiver responder) { + return mRouter.acceptWithResponder(message, responder); + + } + + /** + * @see Router#start() + */ + @Override + public void start() { + mRouter.start(); + } + + /** + * @see Router#setErrorHandler(ConnectionErrorHandler) + */ + @Override + public void setErrorHandler(ConnectionErrorHandler errorHandler) { + mRouter.setErrorHandler(errorHandler); + } + + /** + * @see java.io.Closeable#close() + */ + @Override + public void close() { + mRouter.close(); + mClosed = true; + } + + /** + * @see Object#finalize() + */ + @Override + protected void finalize() throws Throwable { + if (!mClosed) { + mExecutor.execute(new Runnable() { + + @Override + public void run() { + close(); + } + }); + throw new IllegalStateException("Warning: Router objects should be explicitly closed " + + "when no longer required otherwise you may leak handles."); + } + super.finalize(); + } +} diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/BindingsHelper.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/BindingsHelper.java new file mode 100644 index 0000000000..f77399d1b4 --- /dev/null +++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/BindingsHelper.java @@ -0,0 +1,199 @@ +// 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.system.Handle; +import org.chromium.mojo.system.Watcher; + +/** + * Helper functions. + */ +public class BindingsHelper { + /** + * Alignment in bytes for mojo serialization. + */ + public static final int ALIGNMENT = 8; + + /** + * The size, in bytes, of a serialized handle. A handle is serialized as an int representing the + * offset of the handle in the list of handles. + */ + public static final int SERIALIZED_HANDLE_SIZE = 4; + + /** + * The size, in bytes, of a serialized interface, which consists of a serialized handle (4 + * bytes) and a version number (4 bytes). + */ + public static final int SERIALIZED_INTERFACE_SIZE = 8; + + /** + * The size, in bytes, of a serialized pointer. A pointer is serializaed as an unsigned long + * representing the offset from its position to the pointed elemnt. + */ + public static final int POINTER_SIZE = 8; + + /** + * The size, in bytes, of a serialized union. + */ + public static final int UNION_SIZE = 16; + + /** + * The header for a serialized map element. + */ + public static final DataHeader MAP_STRUCT_HEADER = new DataHeader(24, 0); + + /** + * The value used for the expected length of a non-fixed size array. + */ + public static final int UNSPECIFIED_ARRAY_LENGTH = -1; + + /** + * Passed as |arrayNullability| when neither the array nor its elements are nullable. + */ + public static final int NOTHING_NULLABLE = 0; + + /** + * "Array bit" of |arrayNullability| is set iff the array itself is nullable. + */ + public static final int ARRAY_NULLABLE = (1 << 0); + + /** + * "Element bit" of |arrayNullability| is set iff the array elements are nullable. + */ + public static final int ELEMENT_NULLABLE = (1 << 1); + + public static boolean isArrayNullable(int arrayNullability) { + return (arrayNullability & ARRAY_NULLABLE) > 0; + } + + public static boolean isElementNullable(int arrayNullability) { + return (arrayNullability & ELEMENT_NULLABLE) > 0; + } + + /** + * Align |size| on {@link BindingsHelper#ALIGNMENT}. + */ + public static int align(int size) { + return (size + ALIGNMENT - 1) & ~(ALIGNMENT - 1); + } + + /** + * Align |size| on {@link BindingsHelper#ALIGNMENT}. + */ + public static long align(long size) { + return (size + ALIGNMENT - 1) & ~(ALIGNMENT - 1); + } + + /** + * Compute the size in bytes of the given string encoded as utf8. + */ + public static int utf8StringSizeInBytes(String s) { + int res = 0; + for (int i = 0; i < s.length(); ++i) { + char c = s.charAt(i); + int codepoint = c; + if (isSurrogate(c)) { + i++; + char c2 = s.charAt(i); + codepoint = Character.toCodePoint(c, c2); + } + res += 1; + if (codepoint > 0x7f) { + res += 1; + if (codepoint > 0x7ff) { + res += 1; + if (codepoint > 0xffff) { + res += 1; + if (codepoint > 0x1fffff) { + res += 1; + if (codepoint > 0x3ffffff) { + res += 1; + } + } + } + } + } + } + return res; + } + + /** + * Returns |true| if and only if the two objects are equals, handling |null|. + */ + public static boolean equals(Object o1, Object o2) { + if (o1 == o2) { + return true; + } + if (o1 == null) { + return false; + } + return o1.equals(o2); + } + + /** + * Returns the hash code of the object, handling |null|. + */ + public static int hashCode(Object o) { + if (o == null) { + return 0; + } + return o.hashCode(); + } + + /** + * Returns the hash code of the value. + */ + public static int hashCode(boolean o) { + return o ? 1231 : 1237; + } + + /** + * Returns the hash code of the value. + */ + public static int hashCode(long o) { + return (int) (o ^ (o >>> 32)); + } + + /** + * Returns the hash code of the value. + */ + public static int hashCode(float o) { + return Float.floatToIntBits(o); + } + + /** + * Returns the hash code of the value. + */ + public static int hashCode(double o) { + return hashCode(Double.doubleToLongBits(o)); + } + + /** + * Returns the hash code of the value. + */ + public static int hashCode(int o) { + return o; + } + + /** + * Determines if the given {@code char} value is a Unicode <i>surrogate code unit</i>. See + * {@link Character#isSurrogate}. Extracting here because the method only exists at API level + * 19. + */ + private static boolean isSurrogate(char c) { + return c >= Character.MIN_SURROGATE && c < (Character.MAX_SURROGATE + 1); + } + + /** + * Returns an {@link AsyncWaiter} to use with the given handle, or |null| if none if available. + */ + static Watcher getWatcherForHandle(Handle handle) { + if (handle.getCore() != null) { + return handle.getCore().getWatcher(); + } else { + return null; + } + } +} diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Callbacks.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Callbacks.java new file mode 100644 index 0000000000..c6b14c1494 --- /dev/null +++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Callbacks.java @@ -0,0 +1,130 @@ +// 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. + +// This file was generated using +// mojo/tools/generate_java_callback_interfaces.py + +package org.chromium.mojo.bindings; + +/** + * Contains a generic interface for callbacks. + */ +public interface Callbacks { + + /** + * A generic callback. + */ + interface Callback0 { + /** + * Call the callback. + */ + public void call(); + } + + /** + * A generic 1-argument callback. + * + * @param <T1> the type of argument 1. + */ + interface Callback1<T1> { + /** + * Call the callback. + */ + public void call(T1 arg1); + } + + /** + * A generic 2-argument callback. + * + * @param <T1> the type of argument 1. + * @param <T2> the type of argument 2. + */ + interface Callback2<T1, T2> { + /** + * Call the callback. + */ + public void call(T1 arg1, T2 arg2); + } + + /** + * A generic 3-argument callback. + * + * @param <T1> the type of argument 1. + * @param <T2> the type of argument 2. + * @param <T3> the type of argument 3. + */ + interface Callback3<T1, T2, T3> { + /** + * Call the callback. + */ + public void call(T1 arg1, T2 arg2, T3 arg3); + } + + /** + * A generic 4-argument callback. + * + * @param <T1> the type of argument 1. + * @param <T2> the type of argument 2. + * @param <T3> the type of argument 3. + * @param <T4> the type of argument 4. + */ + interface Callback4<T1, T2, T3, T4> { + /** + * Call the callback. + */ + public void call(T1 arg1, T2 arg2, T3 arg3, T4 arg4); + } + + /** + * A generic 5-argument callback. + * + * @param <T1> the type of argument 1. + * @param <T2> the type of argument 2. + * @param <T3> the type of argument 3. + * @param <T4> the type of argument 4. + * @param <T5> the type of argument 5. + */ + interface Callback5<T1, T2, T3, T4, T5> { + /** + * Call the callback. + */ + public void call(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5); + } + + /** + * A generic 6-argument callback. + * + * @param <T1> the type of argument 1. + * @param <T2> the type of argument 2. + * @param <T3> the type of argument 3. + * @param <T4> the type of argument 4. + * @param <T5> the type of argument 5. + * @param <T6> the type of argument 6. + */ + interface Callback6<T1, T2, T3, T4, T5, T6> { + /** + * Call the callback. + */ + public void call(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6); + } + + /** + * A generic 7-argument callback. + * + * @param <T1> the type of argument 1. + * @param <T2> the type of argument 2. + * @param <T3> the type of argument 3. + * @param <T4> the type of argument 4. + * @param <T5> the type of argument 5. + * @param <T6> the type of argument 6. + * @param <T7> the type of argument 7. + */ + interface Callback7<T1, T2, T3, T4, T5, T6, T7> { + /** + * Call the callback. + */ + public void call(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7); + } + +} diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ConnectionErrorHandler.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ConnectionErrorHandler.java new file mode 100644 index 0000000000..f601fb81b5 --- /dev/null +++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ConnectionErrorHandler.java @@ -0,0 +1,15 @@ +// 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.system.MojoException; + +/** + * A {@link ConnectionErrorHandler} is notified of an error happening while using the bindings over + * message pipes. + */ +public interface ConnectionErrorHandler { + public void onConnectionError(MojoException e); +} diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Connector.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Connector.java new file mode 100644 index 0000000000..2aa5ea690c --- /dev/null +++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Connector.java @@ -0,0 +1,214 @@ +// 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.system.Core; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.MessagePipeHandle.ReadMessageResult; +import org.chromium.mojo.system.MojoException; +import org.chromium.mojo.system.MojoResult; +import org.chromium.mojo.system.ResultAnd; +import org.chromium.mojo.system.Watcher; + +import java.nio.ByteBuffer; + +/** + * A {@link Connector} owns a {@link MessagePipeHandle} and will send any received messages to the + * registered {@link MessageReceiver}. It also acts as a {@link MessageReceiver} and will send any + * message through the handle. + * <p> + * The method |start| must be called before the {@link Connector} will start listening to incoming + * messages. + */ +public class Connector implements MessageReceiver, HandleOwner<MessagePipeHandle> { + + /** + * The callback that is notified when the state of the owned handle changes. + */ + private final WatcherCallback mWatcherCallback = new WatcherCallback(); + + /** + * The owned message pipe. + */ + private final MessagePipeHandle mMessagePipeHandle; + + /** + * A watcher which is notified when a new message is available on the owned message pipe. + */ + private final Watcher mWatcher; + + /** + * The {@link MessageReceiver} to which received messages are sent. + */ + private MessageReceiver mIncomingMessageReceiver; + + /** + * The error handler to notify of errors. + */ + private ConnectionErrorHandler mErrorHandler; + + /** + * Create a new connector over a |messagePipeHandle|. The created connector will use the default + * {@link AsyncWaiter} from the {@link Core} implementation of |messagePipeHandle|. + */ + public Connector(MessagePipeHandle messagePipeHandle) { + this(messagePipeHandle, BindingsHelper.getWatcherForHandle(messagePipeHandle)); + } + + /** + * Create a new connector over a |messagePipeHandle| using the given {@link AsyncWaiter} to get + * notified of changes on the handle. + */ + public Connector(MessagePipeHandle messagePipeHandle, Watcher watcher) { + mMessagePipeHandle = messagePipeHandle; + mWatcher = watcher; + } + + /** + * Set the {@link MessageReceiver} that will receive message from the owned message pipe. + */ + public void setIncomingMessageReceiver(MessageReceiver incomingMessageReceiver) { + mIncomingMessageReceiver = incomingMessageReceiver; + } + + /** + * Set the {@link ConnectionErrorHandler} that will be notified of errors on the owned message + * pipe. + */ + public void setErrorHandler(ConnectionErrorHandler errorHandler) { + mErrorHandler = errorHandler; + } + + /** + * Start listening for incoming messages. + */ + public void start() { + mWatcher.start(mMessagePipeHandle, Core.HandleSignals.READABLE, mWatcherCallback); + } + + /** + * @see MessageReceiver#accept(Message) + */ + @Override + public boolean accept(Message message) { + try { + mMessagePipeHandle.writeMessage(message.getData(), + message.getHandles(), MessagePipeHandle.WriteFlags.NONE); + return true; + } catch (MojoException e) { + onError(e); + return false; + } + } + + /** + * Pass the owned handle of the connector. After this, the connector is disconnected. It cannot + * accept new message and it isn't listening to the handle anymore. + * + * @see org.chromium.mojo.bindings.HandleOwner#passHandle() + */ + @Override + public MessagePipeHandle passHandle() { + cancelIfActive(); + MessagePipeHandle handle = mMessagePipeHandle.pass(); + if (mIncomingMessageReceiver != null) { + mIncomingMessageReceiver.close(); + } + return handle; + } + + /** + * @see java.io.Closeable#close() + */ + @Override + public void close() { + cancelIfActive(); + mMessagePipeHandle.close(); + if (mIncomingMessageReceiver != null) { + MessageReceiver incomingMessageReceiver = mIncomingMessageReceiver; + mIncomingMessageReceiver = null; + incomingMessageReceiver.close(); + } + } + + private class WatcherCallback implements Watcher.Callback { + /** + * @see org.chromium.mojo.system.Watcher.Callback#onResult(int) + */ + @Override + public void onResult(int result) { + Connector.this.onWatcherResult(result); + } + + } + + /** + * @see org.chromium.mojo.system.Watcher.Callback#onResult(int) + */ + private void onWatcherResult(int result) { + if (result == MojoResult.OK) { + readOutstandingMessages(); + } else { + onError(new MojoException(result)); + } + } + + private void onError(MojoException exception) { + close(); + if (mErrorHandler != null) { + mErrorHandler.onConnectionError(exception); + } + } + + /** + * Read all available messages on the owned message pipe. + */ + private void readOutstandingMessages() { + ResultAnd<Boolean> result; + do { + try { + result = readAndDispatchMessage(mMessagePipeHandle, mIncomingMessageReceiver); + } catch (MojoException e) { + onError(e); + return; + } + } while (result.getValue()); + if (result.getMojoResult() != MojoResult.SHOULD_WAIT) { + onError(new MojoException(result.getMojoResult())); + } + } + + private void cancelIfActive() { + mWatcher.cancel(); + mWatcher.destroy(); + } + + /** + * Read a message, and pass it to the given |MessageReceiver| if not null. If the + * |MessageReceiver| is null, the message is lost. + * + * @param receiver The {@link MessageReceiver} that will receive the read {@link Message}. Can + * be <code>null</code>, in which case the message is discarded. + */ + static ResultAnd<Boolean> readAndDispatchMessage( + MessagePipeHandle handle, MessageReceiver receiver) { + // TODO(qsr) Allow usage of a pool of pre-allocated buffer for performance. + ResultAnd<ReadMessageResult> result = + handle.readMessage(null, 0, MessagePipeHandle.ReadFlags.NONE); + if (result.getMojoResult() != MojoResult.RESOURCE_EXHAUSTED) { + return new ResultAnd<Boolean>(result.getMojoResult(), false); + } + ReadMessageResult readResult = result.getValue(); + assert readResult != null; + ByteBuffer buffer = ByteBuffer.allocateDirect(readResult.getMessageSize()); + result = handle.readMessage( + buffer, readResult.getHandlesCount(), MessagePipeHandle.ReadFlags.NONE); + if (receiver != null && result.getMojoResult() == MojoResult.OK) { + boolean accepted = receiver.accept(new Message(buffer, result.getValue().getHandles())); + return new ResultAnd<Boolean>(result.getMojoResult(), accepted); + } + return new ResultAnd<Boolean>(result.getMojoResult(), false); + } +} diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/DataHeader.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/DataHeader.java new file mode 100644 index 0000000000..96acec9438 --- /dev/null +++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/DataHeader.java @@ -0,0 +1,70 @@ +// Copyright 2015 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; + +/** + * The header for a mojo complex element. + */ +public final class DataHeader { + /** + * The size of a serialized header, in bytes. + */ + public static final int HEADER_SIZE = 8; + + /** + * The offset of the size field. + */ + public static final int SIZE_OFFSET = 0; + + /** + * The offset of the number of fields field. + */ + public static final int ELEMENTS_OR_VERSION_OFFSET = 4; + + /** + * The size of the object owning this header. + */ + public final int size; + + /** + * Number of element (for an array) or version (for a struct) of the object owning this + * header. + */ + public final int elementsOrVersion; + + /** + * Constructor. + */ + public DataHeader(int size, int elementsOrVersion) { + super(); + this.size = size; + this.elementsOrVersion = elementsOrVersion; + } + + /** + * @see Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + elementsOrVersion; + result = prime * result + size; + return result; + } + + /** + * @see Object#equals(Object) + */ + @Override + public boolean equals(Object object) { + if (object == this) return true; + if (object == null) return false; + if (getClass() != object.getClass()) return false; + + DataHeader other = (DataHeader) object; + return (elementsOrVersion == other.elementsOrVersion && size == other.size); + } +}
\ No newline at end of file diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Decoder.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Decoder.java new file mode 100644 index 0000000000..64ff1c08a1 --- /dev/null +++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Decoder.java @@ -0,0 +1,776 @@ +// 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; +import org.chromium.mojo.system.DataPipe; +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.InvalidHandle; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.SharedBufferHandle; +import org.chromium.mojo.system.UntypedHandle; + +import java.nio.ByteOrder; +import java.nio.charset.Charset; + +/** + * A Decoder is a helper class for deserializing a mojo struct. It enables deserialization of basic + * types from a {@link Message} object at a given offset into it's byte buffer. + */ +public class Decoder { + + /** + * Helper class to validate the decoded message. + */ + static final class Validator { + + /** + * Minimal value for the next handle to deserialize. + */ + private int mMinNextClaimedHandle; + /** + * Minimal value of the start of the next memory to claim. + */ + private long mMinNextMemory; + /** + * The current nesting level when decoding. + */ + private long mStackDepth; + + /** + * The maximal memory accessible. + */ + private final long mMaxMemory; + + /** + * The number of handles in the message. + */ + private final long mNumberOfHandles; + + /** + * The maximum nesting level when decoding. + */ + private static final int MAX_RECURSION_DEPTH = 100; + + /** + * Constructor. + */ + Validator(long maxMemory, int numberOfHandles) { + mMaxMemory = maxMemory; + mNumberOfHandles = numberOfHandles; + mStackDepth = 0; + } + + public void claimHandle(int handle) { + if (handle < mMinNextClaimedHandle) { + throw new DeserializationException( + "Trying to access handle out of order."); + } + if (handle >= mNumberOfHandles) { + throw new DeserializationException("Trying to access non present handle."); + } + mMinNextClaimedHandle = handle + 1; + } + + public void claimMemory(long start, long end) { + if (start % BindingsHelper.ALIGNMENT != 0) { + throw new DeserializationException("Incorrect starting alignment: " + start + "."); + } + if (start < mMinNextMemory) { + throw new DeserializationException("Trying to access memory out of order."); + } + if (end < start) { + throw new DeserializationException("Incorrect memory range."); + } + if (end > mMaxMemory) { + throw new DeserializationException("Trying to access out of range memory."); + } + mMinNextMemory = BindingsHelper.align(end); + } + + public void increaseStackDepth() { + ++mStackDepth; + if (mStackDepth >= MAX_RECURSION_DEPTH) { + throw new DeserializationException("Recursion depth limit exceeded."); + } + } + + public void decreaseStackDepth() { + --mStackDepth; + } + } + + /** + * The message to deserialize from. + */ + private final Message mMessage; + + /** + * The base offset in the byte buffer. + */ + private final int mBaseOffset; + + /** + * Validator for the decoded message. + */ + private final Validator mValidator; + + /** + * Constructor. + * + * @param message The message to decode. + */ + public Decoder(Message message) { + this(message, new Validator(message.getData().limit(), message.getHandles().size()), 0); + } + + private Decoder(Message message, Validator validator, int baseOffset) { + mMessage = message; + mMessage.getData().order(ByteOrder.LITTLE_ENDIAN); + mBaseOffset = baseOffset; + mValidator = validator; + } + + /** + * Deserializes a {@link DataHeader} at the current position. + */ + public DataHeader readDataHeader() { + // Claim the memory for the header. + mValidator.claimMemory(mBaseOffset, mBaseOffset + DataHeader.HEADER_SIZE); + DataHeader result = readDataHeaderAtOffset(0, false); + // Claim the rest of the memory. + mValidator.claimMemory(mBaseOffset + DataHeader.HEADER_SIZE, mBaseOffset + result.size); + return result; + } + + /** + * Deserializes a {@link DataHeader} for an union at the given offset. + */ + public DataHeader readDataHeaderForUnion(int offset) { + DataHeader result = readDataHeaderAtOffset(offset, true); + if (result.size == 0) { + if (result.elementsOrVersion != 0) { + throw new DeserializationException( + "Unexpected version tag for a null union. Expecting 0, found: " + + result.elementsOrVersion); + } + } else if (result.size != BindingsHelper.UNION_SIZE) { + throw new DeserializationException( + "Unexpected size of an union. The size must be 0 for a null union, or 16 for " + + "a non-null union."); + } + return result; + } + + /** + * @returns a decoder suitable to decode an union defined as the root object of a message. + */ + public Decoder decoderForSerializedUnion() { + mValidator.claimMemory(0, BindingsHelper.UNION_SIZE); + return this; + } + + /** + * Deserializes a {@link DataHeader} at the given offset. + */ + private DataHeader readDataHeaderAtOffset(int offset, boolean isUnion) { + int size = readInt(offset + DataHeader.SIZE_OFFSET); + int elementsOrVersion = readInt(offset + DataHeader.ELEMENTS_OR_VERSION_OFFSET); + if (size < 0) { + throw new DeserializationException( + "Negative size. Unsigned integers are not valid for java."); + } + if (elementsOrVersion < 0 && (!isUnion || elementsOrVersion != -1)) { + throw new DeserializationException( + "Negative elements or version. Unsigned integers are not valid for java."); + } + + return new DataHeader(size, elementsOrVersion); + } + + public DataHeader readAndValidateDataHeader(DataHeader[] versionArray) { + DataHeader header = readDataHeader(); + int maxVersionIndex = versionArray.length - 1; + if (header.elementsOrVersion <= versionArray[maxVersionIndex].elementsOrVersion) { + DataHeader referenceHeader = null; + for (int index = maxVersionIndex; index >= 0; index--) { + DataHeader dataHeader = versionArray[index]; + if (header.elementsOrVersion >= dataHeader.elementsOrVersion) { + referenceHeader = dataHeader; + break; + } + } + if (referenceHeader == null || referenceHeader.size != header.size) { + throw new DeserializationException( + "Header doesn't correspond to any known version."); + } + } else { + if (header.size < versionArray[maxVersionIndex].size) { + throw new DeserializationException("Message newer than the last known version" + + " cannot be shorter than required by the last known version."); + } + } + return header; + } + + /** + * Deserializes a {@link DataHeader} at the given offset and checks if it is correct for an + * array where elements are pointers. + */ + public DataHeader readDataHeaderForPointerArray(int expectedLength) { + return readDataHeaderForArray(BindingsHelper.POINTER_SIZE, expectedLength); + } + + /** + * Deserializes a {@link DataHeader} at the given offset and checks if it is correct for an + * array where elements are unions. + */ + public DataHeader readDataHeaderForUnionArray(int expectedLength) { + return readDataHeaderForArray(BindingsHelper.UNION_SIZE, expectedLength); + } + + /** + * Deserializes a {@link DataHeader} at the given offset and checks if it is correct for a map. + */ + public void readDataHeaderForMap() { + DataHeader si = readDataHeader(); + if (si.size != BindingsHelper.MAP_STRUCT_HEADER.size) { + throw new DeserializationException( + "Incorrect header for map. The size is incorrect."); + } + if (si.elementsOrVersion != BindingsHelper.MAP_STRUCT_HEADER.elementsOrVersion) { + throw new DeserializationException( + "Incorrect header for map. The version is incorrect."); + } + } + + /** + * Deserializes a byte at the given offset. + */ + public byte readByte(int offset) { + validateBufferSize(offset, 1); + return mMessage.getData().get(mBaseOffset + offset); + } + + /** + * Deserializes a boolean at the given offset, re-using any partially read byte. + */ + public boolean readBoolean(int offset, int bit) { + validateBufferSize(offset, 1); + return (readByte(offset) & (1 << bit)) != 0; + } + + /** + * Deserializes a short at the given offset. + */ + public short readShort(int offset) { + validateBufferSize(offset, 2); + return mMessage.getData().getShort(mBaseOffset + offset); + } + + /** + * Deserializes an int at the given offset. + */ + public int readInt(int offset) { + validateBufferSize(offset, 4); + return mMessage.getData().getInt(mBaseOffset + offset); + } + + /** + * Deserializes a float at the given offset. + */ + public float readFloat(int offset) { + validateBufferSize(offset, 4); + return mMessage.getData().getFloat(mBaseOffset + offset); + } + + /** + * Deserializes a long at the given offset. + */ + public long readLong(int offset) { + validateBufferSize(offset, 8); + return mMessage.getData().getLong(mBaseOffset + offset); + } + + /** + * Deserializes a double at the given offset. + */ + public double readDouble(int offset) { + validateBufferSize(offset, 8); + return mMessage.getData().getDouble(mBaseOffset + offset); + } + + /** + * Deserializes a pointer at the given offset. Returns a Decoder suitable to decode the content + * of the pointer. + */ + public Decoder readPointer(int offset, boolean nullable) { + int basePosition = mBaseOffset + offset; + long pointerOffset = readLong(offset); + if (pointerOffset == 0) { + if (!nullable) { + throw new DeserializationException( + "Trying to decode null pointer for a non-nullable type."); + } + return null; + } + int newPosition = (int) (basePosition + pointerOffset); + // The method |getDecoderAtPosition| will validate that the pointer address is valid. + return getDecoderAtPosition(newPosition); + + } + + /** + * Deserializes an array of boolean at the given offset. + */ + public boolean[] readBooleans(int offset, int arrayNullability, int expectedLength) { + Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); + if (d == null) { + return null; + } + DataHeader si = d.readDataHeaderForBooleanArray(expectedLength); + byte[] bytes = new byte[(si.elementsOrVersion + 7) / BindingsHelper.ALIGNMENT]; + d.mMessage.getData().position(d.mBaseOffset + DataHeader.HEADER_SIZE); + d.mMessage.getData().get(bytes); + boolean[] result = new boolean[si.elementsOrVersion]; + for (int i = 0; i < bytes.length; ++i) { + for (int j = 0; j < BindingsHelper.ALIGNMENT; ++j) { + int booleanIndex = i * BindingsHelper.ALIGNMENT + j; + if (booleanIndex < result.length) { + result[booleanIndex] = (bytes[i] & (1 << j)) != 0; + } + } + } + return result; + } + + /** + * Deserializes an array of bytes at the given offset. + */ + public byte[] readBytes(int offset, int arrayNullability, int expectedLength) { + Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); + if (d == null) { + return null; + } + DataHeader si = d.readDataHeaderForArray(1, expectedLength); + byte[] result = new byte[si.elementsOrVersion]; + d.mMessage.getData().position(d.mBaseOffset + DataHeader.HEADER_SIZE); + d.mMessage.getData().get(result); + return result; + } + + /** + * Deserializes an array of shorts at the given offset. + */ + public short[] readShorts(int offset, int arrayNullability, int expectedLength) { + Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); + if (d == null) { + return null; + } + DataHeader si = d.readDataHeaderForArray(2, expectedLength); + short[] result = new short[si.elementsOrVersion]; + d.mMessage.getData().position(d.mBaseOffset + DataHeader.HEADER_SIZE); + d.mMessage.getData().asShortBuffer().get(result); + return result; + } + + /** + * Deserializes an array of ints at the given offset. + */ + public int[] readInts(int offset, int arrayNullability, int expectedLength) { + Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); + if (d == null) { + return null; + } + DataHeader si = d.readDataHeaderForArray(4, expectedLength); + int[] result = new int[si.elementsOrVersion]; + d.mMessage.getData().position(d.mBaseOffset + DataHeader.HEADER_SIZE); + d.mMessage.getData().asIntBuffer().get(result); + return result; + } + + /** + * Deserializes an array of floats at the given offset. + */ + public float[] readFloats(int offset, int arrayNullability, int expectedLength) { + Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); + if (d == null) { + return null; + } + DataHeader si = d.readDataHeaderForArray(4, expectedLength); + float[] result = new float[si.elementsOrVersion]; + d.mMessage.getData().position(d.mBaseOffset + DataHeader.HEADER_SIZE); + d.mMessage.getData().asFloatBuffer().get(result); + return result; + } + + /** + * Deserializes an array of longs at the given offset. + */ + public long[] readLongs(int offset, int arrayNullability, int expectedLength) { + Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); + if (d == null) { + return null; + } + DataHeader si = d.readDataHeaderForArray(8, expectedLength); + long[] result = new long[si.elementsOrVersion]; + d.mMessage.getData().position(d.mBaseOffset + DataHeader.HEADER_SIZE); + d.mMessage.getData().asLongBuffer().get(result); + return result; + } + + /** + * Deserializes an array of doubles at the given offset. + */ + public double[] readDoubles(int offset, int arrayNullability, int expectedLength) { + Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); + if (d == null) { + return null; + } + DataHeader si = d.readDataHeaderForArray(8, expectedLength); + double[] result = new double[si.elementsOrVersion]; + d.mMessage.getData().position(d.mBaseOffset + DataHeader.HEADER_SIZE); + d.mMessage.getData().asDoubleBuffer().get(result); + return result; + } + + /** + * Deserializes an |Handle| at the given offset. + */ + public Handle readHandle(int offset, boolean nullable) { + int index = readInt(offset); + if (index == -1) { + if (!nullable) { + throw new DeserializationException( + "Trying to decode an invalid handle for a non-nullable type."); + } + return InvalidHandle.INSTANCE; + } + mValidator.claimHandle(index); + return mMessage.getHandles().get(index); + } + + /** + * Deserializes an |UntypedHandle| at the given offset. + */ + public UntypedHandle readUntypedHandle(int offset, boolean nullable) { + return readHandle(offset, nullable).toUntypedHandle(); + } + + /** + * Deserializes a |ConsumerHandle| at the given offset. + */ + public DataPipe.ConsumerHandle readConsumerHandle(int offset, boolean nullable) { + return readUntypedHandle(offset, nullable).toDataPipeConsumerHandle(); + } + + /** + * Deserializes a |ProducerHandle| at the given offset. + */ + public DataPipe.ProducerHandle readProducerHandle(int offset, boolean nullable) { + return readUntypedHandle(offset, nullable).toDataPipeProducerHandle(); + } + + /** + * Deserializes a |MessagePipeHandle| at the given offset. + */ + public MessagePipeHandle readMessagePipeHandle(int offset, boolean nullable) { + return readUntypedHandle(offset, nullable).toMessagePipeHandle(); + } + + /** + * Deserializes a |SharedBufferHandle| at the given offset. + */ + public SharedBufferHandle readSharedBufferHandle(int offset, boolean nullable) { + return readUntypedHandle(offset, nullable).toSharedBufferHandle(); + } + + /** + * Deserializes an interface at the given offset. + * + * @return a proxy to the service. + */ + public <P extends Proxy> P readServiceInterface(int offset, boolean nullable, + Interface.Manager<?, P> manager) { + MessagePipeHandle handle = readMessagePipeHandle(offset, nullable); + if (!handle.isValid()) { + return null; + } + int version = readInt(offset + BindingsHelper.SERIALIZED_HANDLE_SIZE); + return manager.attachProxy(handle, version); + } + + /** + * Deserializes a |InterfaceRequest| at the given offset. + */ + public <I extends Interface> InterfaceRequest<I> readInterfaceRequest(int offset, + boolean nullable) { + MessagePipeHandle handle = readMessagePipeHandle(offset, nullable); + if (handle == null) { + return null; + } + return new InterfaceRequest<I>(handle); + } + + /** + * Deserializes an associated interface at the given offset. Not yet supported. + */ + public AssociatedInterfaceNotSupported readAssociatedServiceInterfaceNotSupported(int offset, + boolean nullable) { + return null; + } + + /** + * Deserializes an associated interface request at the given offset. Not yet supported. + */ + public AssociatedInterfaceRequestNotSupported readAssociatedInterfaceRequestNotSupported( + int offset, boolean nullable) { + return null; + } + + /** + * Deserializes a string at the given offset. + */ + public String readString(int offset, boolean nullable) { + final int arrayNullability = nullable ? BindingsHelper.ARRAY_NULLABLE : 0; + byte[] bytes = readBytes(offset, arrayNullability, BindingsHelper.UNSPECIFIED_ARRAY_LENGTH); + if (bytes == null) { + return null; + } + return new String(bytes, Charset.forName("utf8")); + } + + /** + * Deserializes an array of |Handle| at the given offset. + */ + public Handle[] readHandles(int offset, int arrayNullability, int expectedLength) { + Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); + if (d == null) { + return null; + } + DataHeader si = d.readDataHeaderForArray(4, expectedLength); + Handle[] result = new Handle[si.elementsOrVersion]; + for (int i = 0; i < result.length; ++i) { + result[i] = d.readHandle( + DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_HANDLE_SIZE * i, + BindingsHelper.isElementNullable(arrayNullability)); + } + return result; + } + + /** + * Deserializes an array of |UntypedHandle| at the given offset. + */ + public UntypedHandle[] readUntypedHandles( + int offset, int arrayNullability, int expectedLength) { + Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); + if (d == null) { + return null; + } + DataHeader si = d.readDataHeaderForArray(4, expectedLength); + UntypedHandle[] result = new UntypedHandle[si.elementsOrVersion]; + for (int i = 0; i < result.length; ++i) { + result[i] = d.readUntypedHandle( + DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_HANDLE_SIZE * i, + BindingsHelper.isElementNullable(arrayNullability)); + } + return result; + } + + /** + * Deserializes an array of |ConsumerHandle| at the given offset. + */ + public DataPipe.ConsumerHandle[] readConsumerHandles( + int offset, int arrayNullability, int expectedLength) { + Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); + if (d == null) { + return null; + } + DataHeader si = d.readDataHeaderForArray(4, expectedLength); + DataPipe.ConsumerHandle[] result = new DataPipe.ConsumerHandle[si.elementsOrVersion]; + for (int i = 0; i < result.length; ++i) { + result[i] = d.readConsumerHandle( + DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_HANDLE_SIZE * i, + BindingsHelper.isElementNullable(arrayNullability)); + } + return result; + } + + /** + * Deserializes an array of |ProducerHandle| at the given offset. + */ + public DataPipe.ProducerHandle[] readProducerHandles( + int offset, int arrayNullability, int expectedLength) { + Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); + if (d == null) { + return null; + } + DataHeader si = d.readDataHeaderForArray(4, expectedLength); + DataPipe.ProducerHandle[] result = new DataPipe.ProducerHandle[si.elementsOrVersion]; + for (int i = 0; i < result.length; ++i) { + result[i] = d.readProducerHandle( + DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_HANDLE_SIZE * i, + BindingsHelper.isElementNullable(arrayNullability)); + } + return result; + + } + + /** + * Deserializes an array of |MessagePipeHandle| at the given offset. + */ + public MessagePipeHandle[] readMessagePipeHandles( + int offset, int arrayNullability, int expectedLength) { + Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); + if (d == null) { + return null; + } + DataHeader si = d.readDataHeaderForArray(4, expectedLength); + MessagePipeHandle[] result = new MessagePipeHandle[si.elementsOrVersion]; + for (int i = 0; i < result.length; ++i) { + result[i] = d.readMessagePipeHandle( + DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_HANDLE_SIZE * i, + BindingsHelper.isElementNullable(arrayNullability)); + } + return result; + + } + + /** + * Deserializes an array of |SharedBufferHandle| at the given offset. + */ + public SharedBufferHandle[] readSharedBufferHandles( + int offset, int arrayNullability, int expectedLength) { + Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); + if (d == null) { + return null; + } + DataHeader si = d.readDataHeaderForArray(4, expectedLength); + SharedBufferHandle[] result = new SharedBufferHandle[si.elementsOrVersion]; + for (int i = 0; i < result.length; ++i) { + result[i] = d.readSharedBufferHandle( + DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_HANDLE_SIZE * i, + BindingsHelper.isElementNullable(arrayNullability)); + } + return result; + + } + + /** + * Deserializes an array of |ServiceHandle| at the given offset. + */ + public <S extends Interface, P extends Proxy> S[] readServiceInterfaces( + int offset, int arrayNullability, int expectedLength, Interface.Manager<S, P> manager) { + Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); + if (d == null) { + return null; + } + DataHeader si = + d.readDataHeaderForArray(BindingsHelper.SERIALIZED_INTERFACE_SIZE, expectedLength); + S[] result = manager.buildArray(si.elementsOrVersion); + for (int i = 0; i < result.length; ++i) { + // This cast is necessary because java 6 doesn't handle wildcard correctly when using + // Manager<S, ? extends S> + @SuppressWarnings("unchecked") + S value = (S) d.readServiceInterface( + DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_INTERFACE_SIZE * i, + BindingsHelper.isElementNullable(arrayNullability), manager); + result[i] = value; + } + return result; + } + + /** + * Deserializes an array of |InterfaceRequest| at the given offset. + */ + public <I extends Interface> InterfaceRequest<I>[] readInterfaceRequests( + int offset, int arrayNullability, int expectedLength) { + Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); + if (d == null) { + return null; + } + DataHeader si = d.readDataHeaderForArray(4, expectedLength); + @SuppressWarnings("unchecked") + InterfaceRequest<I>[] result = new InterfaceRequest[si.elementsOrVersion]; + for (int i = 0; i < result.length; ++i) { + result[i] = d.readInterfaceRequest( + DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_HANDLE_SIZE * i, + BindingsHelper.isElementNullable(arrayNullability)); + } + return result; + } + + /** + * Deserializes an array of associated interfaces at the given offset. Not yet supported. + */ + public AssociatedInterfaceNotSupported[] readAssociatedServiceInterfaceNotSupporteds( + int offset, int arrayNullability, int expectedLength) { + return null; + } + + /** + * Deserializes an array of associated interface requests at the given offset. Not yet + * supported. + */ + public AssociatedInterfaceRequestNotSupported[] readAssociatedInterfaceRequestNotSupporteds( + int offset, int arrayNullability, int expectedLength) { + return null; + } + + /** + * Returns a view of this decoder at the offset |offset|. + */ + private Decoder getDecoderAtPosition(int offset) { + return new Decoder(mMessage, mValidator, offset); + } + + /** + * Deserializes a {@link DataHeader} at the given offset and checks if it is correct for an + * array of booleans. + */ + private DataHeader readDataHeaderForBooleanArray(int expectedLength) { + DataHeader dataHeader = readDataHeader(); + if (dataHeader.size < DataHeader.HEADER_SIZE + (dataHeader.elementsOrVersion + 7) / 8) { + throw new DeserializationException("Array header is incorrect."); + } + if (expectedLength != BindingsHelper.UNSPECIFIED_ARRAY_LENGTH + && dataHeader.elementsOrVersion != expectedLength) { + throw new DeserializationException("Incorrect array length. Expected: " + expectedLength + + ", but got: " + dataHeader.elementsOrVersion + "."); + } + return dataHeader; + } + + /** + * Deserializes a {@link DataHeader} of an array at the given offset. + */ + private DataHeader readDataHeaderForArray(long elementSize, int expectedLength) { + DataHeader dataHeader = readDataHeader(); + if (dataHeader.size + < (DataHeader.HEADER_SIZE + elementSize * dataHeader.elementsOrVersion)) { + throw new DeserializationException("Array header is incorrect."); + } + if (expectedLength != BindingsHelper.UNSPECIFIED_ARRAY_LENGTH + && dataHeader.elementsOrVersion != expectedLength) { + throw new DeserializationException("Incorrect array length. Expected: " + expectedLength + + ", but got: " + dataHeader.elementsOrVersion + "."); + } + return dataHeader; + } + + private void validateBufferSize(int offset, int size) { + if (mMessage.getData().limit() < offset + size) { + throw new DeserializationException("Buffer is smaller than expected."); + } + } + + public void increaseStackDepth() { + mValidator.increaseStackDepth(); + } + + public void decreaseStackDepth() { + mValidator.decreaseStackDepth(); + } +} diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/DelegatingConnectionErrorHandler.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/DelegatingConnectionErrorHandler.java new file mode 100644 index 0000000000..2ee2ae7493 --- /dev/null +++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/DelegatingConnectionErrorHandler.java @@ -0,0 +1,49 @@ +// 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.system.MojoException; + +import java.util.Collections; +import java.util.Set; +import java.util.WeakHashMap; + +/** + * A {@link ConnectionErrorHandler} that delegate the errors to a list of registered handlers. This + * class will use weak pointers to prevent keeping references to any handlers it delegates to. + */ +public class DelegatingConnectionErrorHandler implements ConnectionErrorHandler { + + /** + * The registered handlers. This uses a {@link WeakHashMap} so that it doesn't prevent the + * handler from being garbage collected. + */ + private final Set<ConnectionErrorHandler> mHandlers = + Collections.newSetFromMap(new WeakHashMap<ConnectionErrorHandler, Boolean>()); + + /** + * @see ConnectionErrorHandler#onConnectionError(MojoException) + */ + @Override + public void onConnectionError(MojoException e) { + for (ConnectionErrorHandler handler : mHandlers) { + handler.onConnectionError(e); + } + } + + /** + * Add a handler that will be notified of any error this object receives. + */ + public void addConnectionErrorHandler(ConnectionErrorHandler handler) { + mHandlers.add(handler); + } + + /** + * Remove a previously registered handler. + */ + public void removeConnectionErrorHandler(ConnectionErrorHandler handler) { + mHandlers.remove(handler); + } +} diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/DeserializationException.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/DeserializationException.java new file mode 100644 index 0000000000..eeb511c520 --- /dev/null +++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/DeserializationException.java @@ -0,0 +1,26 @@ +// 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; + +/** + * Error when deserializing a mojo message. + */ +public class DeserializationException extends RuntimeException { + + /** + * Constructs a new deserialization exception with the specified detail message. + */ + public DeserializationException(String message) { + super(message); + } + + /** + * Constructs a new deserialization exception with the specified cause. + */ + public DeserializationException(Exception cause) { + super(cause); + } + +} diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Encoder.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Encoder.java new file mode 100644 index 0000000000..3c86a8daa4 --- /dev/null +++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Encoder.java @@ -0,0 +1,587 @@ +// 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); + } + + /** + * Encode an associated interface. Not yet supported. + */ + public void encode(AssociatedInterfaceNotSupported v, int offset, boolean nullable) { + } + + /** + * Encode an associated interface request. Not yet supported. + */ + public void encode(AssociatedInterfaceRequestNotSupported v, int offset, boolean 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 an array of associated interfaces. Not yet supported. + */ + public void encode(AssociatedInterfaceNotSupported[] v, int offset, int arrayNullability, + int expectedLength) {} + + /** + * Encodes an array of associated interface requests. Not yet supported. + */ + public void encode(AssociatedInterfaceRequestNotSupported[] v, int offset, int arrayNullability, + int expectedLength) {} + + /** + * 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); + } + +} diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ExecutorFactory.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ExecutorFactory.java new file mode 100644 index 0000000000..bb49cbc4bd --- /dev/null +++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ExecutorFactory.java @@ -0,0 +1,169 @@ +// 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.system.Core; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.MessagePipeHandle.ReadMessageResult; +import org.chromium.mojo.system.MojoException; +import org.chromium.mojo.system.MojoResult; +import org.chromium.mojo.system.Pair; +import org.chromium.mojo.system.ResultAnd; +import org.chromium.mojo.system.Watcher; +import org.chromium.mojo.system.Watcher.Callback; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executor; + +/** + * A factory which provides per-thread executors, which enable execution on the thread from which + * they were obtained. + */ +class ExecutorFactory { + + /** + * A null buffer which is used to send messages without any data on the PipedExecutor's + * signaling handles. + */ + private static final ByteBuffer NOTIFY_BUFFER = null; + + /** + * Implementation of the executor which uses a pair of {@link MessagePipeHandle} for signaling. + * The executor will wait asynchronously on one end of a {@link MessagePipeHandle} on the thread + * on which it was created. Other threads can call execute with a {@link Runnable}, and the + * executor will queue the {@link Runnable} and write a message on the other end of the handle. + * This will wake up the executor which is waiting on the handle, which will then dequeue the + * {@link Runnable} and execute it on the original thread. + */ + private static class PipedExecutor implements Executor, Callback { + + /** + * The handle which is written to. Access to this object must be protected with |mLock|. + */ + private final MessagePipeHandle mWriteHandle; + /** + * The handle which is read from. + */ + private final MessagePipeHandle mReadHandle; + /** + * The list of actions left to be run. Access to this object must be protected with |mLock|. + */ + private final List<Runnable> mPendingActions; + /** + * Lock protecting access to |mWriteHandle| and |mPendingActions|. + */ + private final Object mLock; + /** + * The {@link Watcher} to get notified of new message availability on |mReadHandle|. + */ + private final Watcher mWatcher; + + /** + * Constructor. + */ + public PipedExecutor(Core core) { + mWatcher = core.getWatcher(); + assert mWatcher != null; + mLock = new Object(); + Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe( + new MessagePipeHandle.CreateOptions()); + mReadHandle = handles.first; + mWriteHandle = handles.second; + mPendingActions = new ArrayList<Runnable>(); + mWatcher.start(mReadHandle, Core.HandleSignals.READABLE, this); + } + + /** + * @see Callback#onResult(int) + */ + @Override + public void onResult(int result) { + if (result == MojoResult.OK && readNotifyBufferMessage()) { + runNextAction(); + } else { + close(); + } + } + + /** + * Close the handles. Should only be called on the executor thread. + */ + private void close() { + synchronized (mLock) { + mWriteHandle.close(); + mPendingActions.clear(); + } + mWatcher.cancel(); + mWatcher.destroy(); + mReadHandle.close(); + } + + /** + * Read the next message on |mReadHandle|, and return |true| if successful, |false| + * otherwise. + */ + private boolean readNotifyBufferMessage() { + try { + ResultAnd<ReadMessageResult> readMessageResult = + mReadHandle.readMessage(NOTIFY_BUFFER, 0, MessagePipeHandle.ReadFlags.NONE); + if (readMessageResult.getMojoResult() == MojoResult.OK) { + return true; + } + } catch (MojoException e) { + // Will be closed by the fall back at the end of this method. + } + return false; + } + + /** + * Run the next action in the |mPendingActions| queue. + */ + private void runNextAction() { + Runnable toRun = null; + synchronized (mLock) { + toRun = mPendingActions.remove(0); + } + toRun.run(); + } + + /** + * Execute the given |command| in the executor thread. This can be called on any thread. + * + * @see Executor#execute(Runnable) + */ + @Override + public void execute(Runnable command) { + // Accessing the write handle must be protected by the lock, because it can be closed + // from the executor's thread. + synchronized (mLock) { + if (!mWriteHandle.isValid()) { + throw new IllegalStateException( + "Trying to execute an action on a closed executor."); + } + mPendingActions.add(command); + mWriteHandle.writeMessage(NOTIFY_BUFFER, null, MessagePipeHandle.WriteFlags.NONE); + } + } + } + + /** + * Keep one executor per executor thread. + */ + private static final ThreadLocal<Executor> EXECUTORS = new ThreadLocal<Executor>(); + + /** + * Returns an {@link Executor} that will run all of its actions in the current thread. + */ + public static Executor getExecutorForCurrentThread(Core core) { + Executor executor = EXECUTORS.get(); + if (executor == null) { + executor = new PipedExecutor(core); + EXECUTORS.set(executor); + } + return executor; + } +} diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/HandleOwner.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/HandleOwner.java new file mode 100644 index 0000000000..60fc33ba4e --- /dev/null +++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/HandleOwner.java @@ -0,0 +1,29 @@ +// 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.system.Handle; + +import java.io.Closeable; + +/** + * Describes a class that owns a handle. + * + * @param <H> The type of the owned handle. + */ +public interface HandleOwner<H extends Handle> extends Closeable { + + /** + * Pass the handle owned by this class. + */ + public H passHandle(); + + /** + * @see java.io.Closeable#close() + */ + @Override + public void close(); + +} diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Interface.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Interface.java new file mode 100644 index 0000000000..2699ab899a --- /dev/null +++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Interface.java @@ -0,0 +1,425 @@ +// 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.Callbacks.Callback1; +import org.chromium.mojo.bindings.Interface.AbstractProxy.HandlerImpl; +import org.chromium.mojo.bindings.interfacecontrol.QueryVersion; +import org.chromium.mojo.bindings.interfacecontrol.RequireVersion; +import org.chromium.mojo.bindings.interfacecontrol.RunInput; +import org.chromium.mojo.bindings.interfacecontrol.RunMessageParams; +import org.chromium.mojo.bindings.interfacecontrol.RunOrClosePipeInput; +import org.chromium.mojo.bindings.interfacecontrol.RunOrClosePipeMessageParams; +import org.chromium.mojo.bindings.interfacecontrol.RunOutput; +import org.chromium.mojo.bindings.interfacecontrol.RunResponseMessageParams; +import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.MojoException; +import org.chromium.mojo.system.Pair; + +import java.io.Closeable; + +/** + * Base class for mojo generated interfaces. + */ +public interface Interface extends ConnectionErrorHandler, Closeable { + + /** + * The close method is called when the connection to the interface is closed. + * + * @see java.io.Closeable#close() + */ + @Override + public void close(); + + /** + * A proxy to a mojo interface. This is base class for all generated proxies. It implements the + * Interface and each time a method is called, the parameters are serialized and sent to the + * {@link MessageReceiverWithResponder}, along with the response callback if needed. + */ + public interface Proxy extends Interface { + /** + * Class allowing to interact with the proxy itself. + */ + public interface Handler extends Closeable { + /** + * Sets the {@link ConnectionErrorHandler} that will be notified of errors. + */ + public void setErrorHandler(ConnectionErrorHandler errorHandler); + + /** + * Unbinds the proxy and passes the handle. Can return null if the proxy is not bound or + * if the proxy is not over a message pipe. + */ + public MessagePipeHandle passHandle(); + + /** + * Returns the version number of the interface that the remote side supports. + */ + public int getVersion(); + + /** + * Queries the max version that the remote side supports. On completion, the result will + * be returned as the input of |callback|. The version number of this interface pointer + * will also be updated. + */ + public void queryVersion(Callback1<Integer> callback); + + /** + * If the remote side doesn't support the specified version, it will close its end of + * the message pipe asynchronously. The call does nothing if |version| is no greater + * than getVersion(). + * <p> + * If you make a call to requireVersion() with a version number X which is not supported + * by the remote side, it is guaranteed that all calls to the interface methods after + * requireVersion(X) will be ignored. + */ + public void requireVersion(int version); + } + + /** + * Returns the {@link Handler} object allowing to interact with the proxy itself. + */ + public Handler getProxyHandler(); + } + + /** + * Base implementation of {@link Proxy}. + */ + abstract class AbstractProxy implements Proxy { + /** + * Implementation of {@link Handler}. + */ + protected static class HandlerImpl implements Proxy.Handler, ConnectionErrorHandler { + /** + * The {@link Core} implementation to use. + */ + private final Core mCore; + + /** + * The {@link MessageReceiverWithResponder} that will receive a serialized message for + * each method call. + */ + private final MessageReceiverWithResponder mMessageReceiver; + + /** + * The {@link ConnectionErrorHandler} that will be notified of errors. + */ + private ConnectionErrorHandler mErrorHandler; + + /** + * The currently known version of the interface. + */ + private int mVersion; + + /** + * Constructor. + * + * @param core the Core implementation used to create pipes and access the async waiter. + * @param messageReceiver the message receiver to send message to. + */ + protected HandlerImpl(Core core, MessageReceiverWithResponder messageReceiver) { + this.mCore = core; + this.mMessageReceiver = messageReceiver; + } + + void setVersion(int version) { + mVersion = version; + } + + /** + * Returns the message receiver to send message to. + */ + public MessageReceiverWithResponder getMessageReceiver() { + return mMessageReceiver; + } + + /** + * Returns the Core implementation. + */ + public Core getCore() { + return mCore; + } + + /** + * Sets the {@link ConnectionErrorHandler} that will be notified of errors. + */ + @Override + public void setErrorHandler(ConnectionErrorHandler errorHandler) { + this.mErrorHandler = errorHandler; + } + + /** + * @see ConnectionErrorHandler#onConnectionError(MojoException) + */ + @Override + public void onConnectionError(MojoException e) { + if (mErrorHandler != null) { + mErrorHandler.onConnectionError(e); + } + } + + /** + * @see Closeable#close() + */ + @Override + public void close() { + mMessageReceiver.close(); + } + + /** + * @see Interface.Proxy.Handler#passHandle() + */ + @Override + public MessagePipeHandle passHandle() { + @SuppressWarnings("unchecked") + HandleOwner<MessagePipeHandle> handleOwner = + (HandleOwner<MessagePipeHandle>) mMessageReceiver; + return handleOwner.passHandle(); + } + + /** + * @see Handler#getVersion() + */ + @Override + public int getVersion() { + return mVersion; + } + + /** + * @see Handler#queryVersion(org.chromium.mojo.bindings.Callbacks.Callback1) + */ + @Override + public void queryVersion(final Callback1<Integer> callback) { + RunMessageParams message = new RunMessageParams(); + message.input = new RunInput(); + message.input.setQueryVersion(new QueryVersion()); + + InterfaceControlMessagesHelper.sendRunMessage(getCore(), mMessageReceiver, message, + new Callback1<RunResponseMessageParams>() { + @Override + public void call(RunResponseMessageParams response) { + if (response.output != null + && response.output.which() + == RunOutput.Tag.QueryVersionResult) { + mVersion = response.output.getQueryVersionResult().version; + } + try { + callback.call(mVersion); + } catch (RuntimeException e) { + // TODO(lhchavez): Remove this hack. See b/28986534 for details. + android.util.Log.wtf("org.chromium.mojo.bindings.Interface", + "Uncaught runtime exception", e); + } + } + }); + } + + /** + * @see Handler#requireVersion(int) + */ + @Override + public void requireVersion(int version) { + if (mVersion >= version) { + return; + } + mVersion = version; + RunOrClosePipeMessageParams message = new RunOrClosePipeMessageParams(); + message.input = new RunOrClosePipeInput(); + message.input.setRequireVersion(new RequireVersion()); + message.input.getRequireVersion().version = version; + InterfaceControlMessagesHelper.sendRunOrClosePipeMessage( + getCore(), mMessageReceiver, message); + } + } + + /** + * The handler associated with this proxy. + */ + private final HandlerImpl mHandler; + + protected AbstractProxy(Core core, MessageReceiverWithResponder messageReceiver) { + mHandler = new HandlerImpl(core, messageReceiver); + } + + /** + * @see Interface#close() + */ + @Override + public void close() { + mHandler.close(); + } + + /** + * @see Proxy#getProxyHandler() + */ + @Override + public HandlerImpl getProxyHandler() { + return mHandler; + } + + /** + * @see ConnectionErrorHandler#onConnectionError(org.chromium.mojo.system.MojoException) + */ + @Override + public void onConnectionError(MojoException e) { + mHandler.onConnectionError(e); + } + } + + /** + * Base implementation of Stub. Stubs are message receivers that deserialize the payload and + * call the appropriate method in the implementation. If the method returns result, the stub + * serializes the response and sends it back. + * + * @param <I> the type of the interface to delegate calls to. + */ + abstract class Stub<I extends Interface> implements MessageReceiverWithResponder { + + /** + * The {@link Core} implementation to use. + */ + private final Core mCore; + + /** + * The implementation to delegate calls to. + */ + private final I mImpl; + + /** + * Constructor. + * + * @param core the {@link Core} implementation to use. + * @param impl the implementation to delegate calls to. + */ + public Stub(Core core, I impl) { + mCore = core; + mImpl = impl; + } + + /** + * Returns the Core implementation. + */ + protected Core getCore() { + return mCore; + } + + /** + * Returns the implementation to delegate calls to. + */ + protected I getImpl() { + return mImpl; + } + + /** + * @see org.chromium.mojo.bindings.MessageReceiver#close() + */ + @Override + public void close() { + mImpl.close(); + } + + } + + /** + * The |Manager| object enables building of proxies and stubs for a given interface. + * + * @param <I> the type of the interface the manager can handle. + * @param <P> the type of the proxy the manager can handle. To be noted, P always extends I. + */ + abstract class Manager<I extends Interface, P extends Proxy> { + + /** + * Returns the name of the interface. This is an opaque (but human readable) identifier used + * by the service provider to identify services. + */ + public abstract String getName(); + + /** + * Returns the version of the managed interface. + */ + public abstract int getVersion(); + + /** + * Binds the given implementation to the handle. + */ + public void bind(I impl, MessagePipeHandle handle) { + // The router (and by consequence the handle) is intentionally leaked. It will close + // itself when the connected handle is closed and the proxy receives the connection + // error. + Router router = new RouterImpl(handle); + bind(handle.getCore(), impl, router); + router.start(); + } + + /** + * Binds the given implementation to the InterfaceRequest. + */ + public final void bind(I impl, InterfaceRequest<I> request) { + bind(impl, request.passHandle()); + } + + /** + * Returns a Proxy that will send messages to the given |handle|. This implies that the + * other end of the handle must be bound to an implementation of the interface. + */ + public final P attachProxy(MessagePipeHandle handle, int version) { + RouterImpl router = new RouterImpl(handle); + P proxy = attachProxy(handle.getCore(), router); + DelegatingConnectionErrorHandler handlers = new DelegatingConnectionErrorHandler(); + handlers.addConnectionErrorHandler(proxy); + router.setErrorHandler(handlers); + router.start(); + ((HandlerImpl) proxy.getProxyHandler()).setVersion(version); + return proxy; + } + + /** + * Constructs a new |InterfaceRequest| for the interface. This method returns a Pair where + * the first element is a proxy, and the second element is the request. The proxy can be + * used immediately. + */ + public final Pair<P, InterfaceRequest<I>> getInterfaceRequest(Core core) { + Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null); + P proxy = attachProxy(handles.first, 0); + return Pair.create(proxy, new InterfaceRequest<I>(handles.second)); + } + + public final InterfaceRequest<I> asInterfaceRequest(MessagePipeHandle handle) { + return new InterfaceRequest<I>(handle); + } + + /** + * Binds the implementation to the given |router|. + */ + final void bind(Core core, I impl, Router router) { + router.setErrorHandler(impl); + router.setIncomingMessageReceiver(buildStub(core, impl)); + } + + /** + * Returns a Proxy that will send messages to the given |router|. + */ + final P attachProxy(Core core, Router router) { + return buildProxy(core, new AutoCloseableRouter(core, router)); + } + + /** + * Creates a new array of the given |size|. + */ + protected abstract I[] buildArray(int size); + + /** + * Constructs a Stub delegating to the given implementation. + */ + protected abstract Stub<I> buildStub(Core core, I impl); + + /** + * Constructs a Proxy forwarding the calls to the given message receiver. + */ + protected abstract P buildProxy(Core core, MessageReceiverWithResponder messageReceiver); + + } +} diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/InterfaceControlMessagesHelper.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/InterfaceControlMessagesHelper.java new file mode 100644 index 0000000000..51f543d567 --- /dev/null +++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/InterfaceControlMessagesHelper.java @@ -0,0 +1,105 @@ +// Copyright 2015 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.Callbacks.Callback1; +import org.chromium.mojo.bindings.Interface.Manager; +import org.chromium.mojo.bindings.Interface.Proxy; +import org.chromium.mojo.bindings.interfacecontrol.InterfaceControlMessagesConstants; +import org.chromium.mojo.bindings.interfacecontrol.QueryVersionResult; +import org.chromium.mojo.bindings.interfacecontrol.RunInput; +import org.chromium.mojo.bindings.interfacecontrol.RunMessageParams; +import org.chromium.mojo.bindings.interfacecontrol.RunOrClosePipeInput; +import org.chromium.mojo.bindings.interfacecontrol.RunOrClosePipeMessageParams; +import org.chromium.mojo.bindings.interfacecontrol.RunOutput; +import org.chromium.mojo.bindings.interfacecontrol.RunResponseMessageParams; +import org.chromium.mojo.system.Core; + +/** + * Helper class to handle interface control messages. See + * mojo/public/interfaces/bindings/interface_control_messages.mojom. + */ +public class InterfaceControlMessagesHelper { + /** + * MessageReceiver that forwards a message containing a {@link RunResponseMessageParams} to a + * callback. + */ + private static class RunResponseForwardToCallback + extends SideEffectFreeCloseable implements MessageReceiver { + private final Callback1<RunResponseMessageParams> mCallback; + + RunResponseForwardToCallback(Callback1<RunResponseMessageParams> callback) { + mCallback = callback; + } + + /** + * @see MessageReceiver#accept(Message) + */ + @Override + public boolean accept(Message message) { + RunResponseMessageParams response = + RunResponseMessageParams.deserialize(message.asServiceMessage().getPayload()); + mCallback.call(response); + return true; + } + } + + /** + * Sends the given run message through the receiver, registering the callback. + */ + public static void sendRunMessage(Core core, MessageReceiverWithResponder receiver, + RunMessageParams params, Callback1<RunResponseMessageParams> callback) { + Message message = params.serializeWithHeader( + core, new MessageHeader(InterfaceControlMessagesConstants.RUN_MESSAGE_ID, + MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG, 0)); + receiver.acceptWithResponder(message, new RunResponseForwardToCallback(callback)); + } + + /** + * Sends the given run or close pipe message through the receiver. + */ + public static void sendRunOrClosePipeMessage( + Core core, MessageReceiverWithResponder receiver, RunOrClosePipeMessageParams params) { + Message message = params.serializeWithHeader(core, + new MessageHeader(InterfaceControlMessagesConstants.RUN_OR_CLOSE_PIPE_MESSAGE_ID)); + receiver.accept(message); + } + + /** + * Handles a received run message. + */ + public static <I extends Interface, P extends Proxy> boolean handleRun( + Core core, Manager<I, P> manager, ServiceMessage message, MessageReceiver responder) { + Message payload = message.getPayload(); + RunMessageParams query = RunMessageParams.deserialize(payload); + RunResponseMessageParams response = new RunResponseMessageParams(); + response.output = new RunOutput(); + if (query.input.which() == RunInput.Tag.QueryVersion) { + response.output.setQueryVersionResult(new QueryVersionResult()); + response.output.getQueryVersionResult().version = manager.getVersion(); + } else { + response.output = null; + } + + return responder.accept(response.serializeWithHeader( + core, new MessageHeader(InterfaceControlMessagesConstants.RUN_MESSAGE_ID, + MessageHeader.MESSAGE_IS_RESPONSE_FLAG, + message.getHeader().getRequestId()))); + } + + /** + * Handles a received run or close pipe message. Closing the pipe is handled by returning + * |false|. + */ + public static <I extends Interface, P extends Proxy> boolean handleRunOrClosePipe( + Manager<I, P> manager, ServiceMessage message) { + Message payload = message.getPayload(); + RunOrClosePipeMessageParams query = RunOrClosePipeMessageParams.deserialize(payload); + if (query.input.which() == RunOrClosePipeInput.Tag.RequireVersion) { + return query.input.getRequireVersion().version <= manager.getVersion(); + } + return false; + } +} diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/InterfaceRequest.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/InterfaceRequest.java new file mode 100644 index 0000000000..61899b4467 --- /dev/null +++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/InterfaceRequest.java @@ -0,0 +1,58 @@ +// 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.system.MessagePipeHandle; + +/** + * One end of the message pipe representing a request to create an implementation to be bound to it. + * The other end of the pipe is bound to a proxy, which can be used immediately, while the + * InterfaceRequest is being sent. + * <p> + * InterfaceRequest are built using |Interface.Manager|. + * + * @param <P> the type of the remote interface proxy. + */ +public class InterfaceRequest<P extends Interface> implements HandleOwner<MessagePipeHandle> { + + /** + * The handle which will be sent and will be connected to the implementation. + */ + private final MessagePipeHandle mHandle; + + /** + * Constructor. + * + * @param handle the handle which will be sent and will be connected to the implementation. + */ + InterfaceRequest(MessagePipeHandle handle) { + mHandle = handle; + } + + /** + * @see HandleOwner#passHandle() + */ + @Override + public MessagePipeHandle passHandle() { + return mHandle.pass(); + } + + /** + * @see java.io.Closeable#close() + */ + @Override + public void close() { + mHandle.close(); + } + + /** + * Returns an {@link InterfaceRequest} that wraps the given handle. This method is not type safe + * and should be avoided unless absolutely necessary. + */ + @SuppressWarnings("rawtypes") + public static InterfaceRequest asInterfaceRequestUnsafe(MessagePipeHandle handle) { + return new InterfaceRequest(handle); + } +} diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Message.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Message.java new file mode 100644 index 0000000000..996c457338 --- /dev/null +++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Message.java @@ -0,0 +1,69 @@ +// 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.system.Handle; +import org.chromium.mojo.system.MessagePipeHandle; + +import java.nio.ByteBuffer; +import java.util.List; + +/** + * A raw message to be sent/received from a {@link MessagePipeHandle}. Note that this can contain + * any data, not necessarily a Mojo message with a proper header. See also {@link ServiceMessage}. + */ +public class Message { + + /** + * The data of the message. + */ + private final ByteBuffer mBuffer; + + /** + * The handles of the message. + */ + private final List<? extends Handle> mHandle; + + /** + * This message interpreted as a message for a mojo service with an appropriate header. + */ + private ServiceMessage mWithHeader; + + /** + * Constructor. + * + * @param buffer The buffer containing the bytes to send. This must be a direct buffer. + * @param handles The list of handles to send. + */ + public Message(ByteBuffer buffer, List<? extends Handle> handles) { + assert buffer.isDirect(); + mBuffer = buffer; + mHandle = handles; + } + + /** + * The data of the message. + */ + public ByteBuffer getData() { + return mBuffer; + } + + /** + * The handles of the message. + */ + public List<? extends Handle> getHandles() { + return mHandle; + } + + /** + * Returns the message interpreted as a message for a mojo service. + */ + public ServiceMessage asServiceMessage() { + if (mWithHeader == null) { + mWithHeader = new ServiceMessage(this); + } + return mWithHeader; + } +} diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageHeader.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageHeader.java new file mode 100644 index 0000000000..771d22b5c3 --- /dev/null +++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageHeader.java @@ -0,0 +1,248 @@ +// 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 java.nio.ByteBuffer; + +/** + * Header information for a message. + */ +public class MessageHeader { + + private static final int SIMPLE_MESSAGE_SIZE = 24; + private static final int SIMPLE_MESSAGE_VERSION = 0; + private static final DataHeader SIMPLE_MESSAGE_STRUCT_INFO = + new DataHeader(SIMPLE_MESSAGE_SIZE, SIMPLE_MESSAGE_VERSION); + + private static final int MESSAGE_WITH_REQUEST_ID_SIZE = 32; + private static final int MESSAGE_WITH_REQUEST_ID_VERSION = 1; + private static final DataHeader MESSAGE_WITH_REQUEST_ID_STRUCT_INFO = + new DataHeader(MESSAGE_WITH_REQUEST_ID_SIZE, MESSAGE_WITH_REQUEST_ID_VERSION); + + private static final int INTERFACE_ID_OFFSET = 8; + private static final int TYPE_OFFSET = 12; + private static final int FLAGS_OFFSET = 16; + private static final int REQUEST_ID_OFFSET = 24; + + /** + * Flag for a header of a simple message. + */ + public static final int NO_FLAG = 0; + + /** + * Flag for a header of a message that expected a response. + */ + public static final int MESSAGE_EXPECTS_RESPONSE_FLAG = 1 << 0; + + /** + * Flag for a header of a message that is a response. + */ + public static final int MESSAGE_IS_RESPONSE_FLAG = 1 << 1; + + private final DataHeader mDataHeader; + private final int mType; + private final int mFlags; + private long mRequestId; + + /** + * Constructor for the header of a message which does not have a response. + */ + public MessageHeader(int type) { + mDataHeader = SIMPLE_MESSAGE_STRUCT_INFO; + mType = type; + mFlags = 0; + mRequestId = 0; + } + + /** + * Constructor for the header of a message which have a response or being itself a response. + */ + public MessageHeader(int type, int flags, long requestId) { + assert mustHaveRequestId(flags); + mDataHeader = MESSAGE_WITH_REQUEST_ID_STRUCT_INFO; + mType = type; + mFlags = flags; + mRequestId = requestId; + } + + /** + * Constructor, parsing the header from a message. Should only be used by {@link Message} + * itself. + */ + MessageHeader(Message message) { + Decoder decoder = new Decoder(message); + mDataHeader = decoder.readDataHeader(); + validateDataHeader(mDataHeader); + + // Currently associated interfaces are not supported. + int interfaceId = decoder.readInt(INTERFACE_ID_OFFSET); + if (interfaceId != 0) { + throw new DeserializationException("Non-zero interface ID, expecting zero since " + + "associated interfaces are not yet supported."); + } + + mType = decoder.readInt(TYPE_OFFSET); + mFlags = decoder.readInt(FLAGS_OFFSET); + if (mustHaveRequestId(mFlags)) { + if (mDataHeader.size < MESSAGE_WITH_REQUEST_ID_SIZE) { + throw new DeserializationException("Incorrect message size, expecting at least " + + MESSAGE_WITH_REQUEST_ID_SIZE + + " for a message with a request identifier, but got: " + mDataHeader.size); + + } + mRequestId = decoder.readLong(REQUEST_ID_OFFSET); + } else { + mRequestId = 0; + } + } + + /** + * Returns the size in bytes of the serialization of the header. + */ + public int getSize() { + return mDataHeader.size; + } + + /** + * Returns the type of the message. + */ + public int getType() { + return mType; + } + + /** + * Returns the flags associated to the message. + */ + public int getFlags() { + return mFlags; + } + + /** + * Returns if the message has the given flag. + */ + public boolean hasFlag(int flag) { + return (mFlags & flag) == flag; + } + + /** + * Returns if the message has a request id. + */ + public boolean hasRequestId() { + return mustHaveRequestId(mFlags); + } + + /** + * Return the request id for the message. Must only be called if the message has a request id. + */ + public long getRequestId() { + assert hasRequestId(); + return mRequestId; + } + + /** + * Encode the header. + */ + public void encode(Encoder encoder) { + encoder.encode(mDataHeader); + // Set the interface ID field to 0. + encoder.encode(0, INTERFACE_ID_OFFSET); + encoder.encode(getType(), TYPE_OFFSET); + encoder.encode(getFlags(), FLAGS_OFFSET); + if (hasRequestId()) { + encoder.encode(getRequestId(), REQUEST_ID_OFFSET); + } + } + + /** + * Returns true if the header has the expected flags. Only considers flags this class knows + * about in order to allow this class to work with future version of the header format. + */ + public boolean validateHeader(int expectedFlags) { + int knownFlags = getFlags() & (MESSAGE_EXPECTS_RESPONSE_FLAG | MESSAGE_IS_RESPONSE_FLAG); + return knownFlags == expectedFlags; + } + + /** + * Returns true if the header has the expected type and flags. Only consider flags this class + * knows about in order to allow this class to work with future version of the header format. + */ + public boolean validateHeader(int expectedType, int expectedFlags) { + return getType() == expectedType && validateHeader(expectedFlags); + } + + /** + * @see Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((mDataHeader == null) ? 0 : mDataHeader.hashCode()); + result = prime * result + mFlags; + result = prime * result + (int) (mRequestId ^ (mRequestId >>> 32)); + result = prime * result + mType; + return result; + } + + /** + * @see Object#equals(Object) + */ + @Override + public boolean equals(Object object) { + if (object == this) return true; + if (object == null) return false; + if (getClass() != object.getClass()) return false; + + MessageHeader other = (MessageHeader) object; + return (BindingsHelper.equals(mDataHeader, other.mDataHeader) + && mFlags == other.mFlags + && mRequestId == other.mRequestId + && mType == other.mType); + } + + /** + * Set the request id on the message contained in the given buffer. + */ + void setRequestId(ByteBuffer buffer, long requestId) { + assert mustHaveRequestId(buffer.getInt(FLAGS_OFFSET)); + buffer.putLong(REQUEST_ID_OFFSET, requestId); + mRequestId = requestId; + } + + /** + * Returns whether a message with the given flags must have a request Id. + */ + private static boolean mustHaveRequestId(int flags) { + return (flags & (MESSAGE_EXPECTS_RESPONSE_FLAG | MESSAGE_IS_RESPONSE_FLAG)) != 0; + } + + /** + * Validate that the given {@link DataHeader} can be the data header of a message header. + */ + private static void validateDataHeader(DataHeader dataHeader) { + if (dataHeader.elementsOrVersion < SIMPLE_MESSAGE_VERSION) { + throw new DeserializationException("Incorrect number of fields, expecting at least " + + SIMPLE_MESSAGE_VERSION + ", but got: " + dataHeader.elementsOrVersion); + } + if (dataHeader.size < SIMPLE_MESSAGE_SIZE) { + throw new DeserializationException( + "Incorrect message size, expecting at least " + SIMPLE_MESSAGE_SIZE + + ", but got: " + dataHeader.size); + } + if (dataHeader.elementsOrVersion == SIMPLE_MESSAGE_VERSION + && dataHeader.size != SIMPLE_MESSAGE_SIZE) { + throw new DeserializationException("Incorrect message size for a message with " + + SIMPLE_MESSAGE_VERSION + " fields, expecting " + SIMPLE_MESSAGE_SIZE + + ", but got: " + dataHeader.size); + } + if (dataHeader.elementsOrVersion == MESSAGE_WITH_REQUEST_ID_VERSION + && dataHeader.size != MESSAGE_WITH_REQUEST_ID_SIZE) { + throw new DeserializationException("Incorrect message size for a message with " + + MESSAGE_WITH_REQUEST_ID_VERSION + " fields, expecting " + + MESSAGE_WITH_REQUEST_ID_SIZE + ", but got: " + dataHeader.size); + } + } + +} diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageReceiver.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageReceiver.java new file mode 100644 index 0000000000..4223297914 --- /dev/null +++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageReceiver.java @@ -0,0 +1,25 @@ +// 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 java.io.Closeable; + +/** + * A class which implements this interface can receive {@link Message} objects. + */ +public interface MessageReceiver extends Closeable { + + /** + * Receive a {@link Message}. The {@link MessageReceiver} is allowed to mutate the message. + * Returns |true| if the message has been handled, |false| otherwise. + */ + boolean accept(Message message); + + /** + * @see java.io.Closeable#close() + */ + @Override + public void close(); +} diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageReceiverWithResponder.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageReceiverWithResponder.java new file mode 100644 index 0000000000..e24a5586c6 --- /dev/null +++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageReceiverWithResponder.java @@ -0,0 +1,21 @@ +// 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; + +/** + * A {@link MessageReceiver} that can also handle the handle the response message generated from the + * given message. + */ +public interface MessageReceiverWithResponder extends MessageReceiver { + + /** + * A variant on {@link #accept(Message)} that registers a {@link MessageReceiver} + * (known as the responder) to handle the response message generated from the given message. The + * responder's {@link #accept(Message)} method may be called as part of the call to + * {@link #acceptWithResponder(Message, MessageReceiver)}, or some time after its + * return. + */ + boolean acceptWithResponder(Message message, MessageReceiver responder); +} diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Router.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Router.java new file mode 100644 index 0000000000..ba19ae5ac4 --- /dev/null +++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Router.java @@ -0,0 +1,31 @@ +// 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.system.MessagePipeHandle; + +/** + * A {@link Router} will handle mojo message and forward those to a {@link Connector}. It deals with + * parsing of headers and adding of request ids in order to be able to match a response to a + * request. + */ +public interface Router extends MessageReceiverWithResponder, HandleOwner<MessagePipeHandle> { + + /** + * Start listening for incoming messages. + */ + public void start(); + + /** + * Set the {@link MessageReceiverWithResponder} that will deserialize and use the message + * received from the pipe. + */ + public void setIncomingMessageReceiver(MessageReceiverWithResponder incomingMessageReceiver); + + /** + * Set the handle that will be notified of errors on the message pipe. + */ + public void setErrorHandler(ConnectionErrorHandler errorHandler); +} diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/RouterImpl.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/RouterImpl.java new file mode 100644 index 0000000000..aebc9e21c8 --- /dev/null +++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/RouterImpl.java @@ -0,0 +1,274 @@ +// 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 android.annotation.SuppressLint; + +import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.Watcher; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Executor; + +/** + * Implementation of {@link Router}. + */ +@SuppressLint("UseSparseArrays") // https://crbug.com/600699 +public class RouterImpl implements Router { + + /** + * {@link MessageReceiver} used as the {@link Connector} callback. + */ + private class HandleIncomingMessageThunk implements MessageReceiver { + + /** + * @see MessageReceiver#accept(Message) + */ + @Override + public boolean accept(Message message) { + return handleIncomingMessage(message); + } + + /** + * @see MessageReceiver#close() + */ + @Override + public void close() { + handleConnectorClose(); + } + + } + + /** + * + * {@link MessageReceiver} used to return responses to the caller. + */ + class ResponderThunk implements MessageReceiver { + private boolean mAcceptWasInvoked; + + /** + * @see + * MessageReceiver#accept(Message) + */ + @Override + public boolean accept(Message message) { + mAcceptWasInvoked = true; + return RouterImpl.this.accept(message); + } + + /** + * @see MessageReceiver#close() + */ + @Override + public void close() { + RouterImpl.this.close(); + } + + @Override + protected void finalize() throws Throwable { + if (!mAcceptWasInvoked) { + // We close the pipe here as a way of signaling to the calling application that an + // error condition occurred. Without this the calling application would have no + // way of knowing it should stop waiting for a response. + RouterImpl.this.closeOnHandleThread(); + } + super.finalize(); + } + } + + /** + * The {@link Connector} which is connected to the handle. + */ + private final Connector mConnector; + + /** + * The {@link MessageReceiverWithResponder} that will consume the messages received from the + * pipe. + */ + private MessageReceiverWithResponder mIncomingMessageReceiver; + + /** + * The next id to use for a request id which needs a response. It is auto-incremented. + */ + private long mNextRequestId = 1; + + /** + * The map from request ids to {@link MessageReceiver} of request currently in flight. + */ + private Map<Long, MessageReceiver> mResponders = new HashMap<Long, MessageReceiver>(); + + /** + * An Executor that will run on the thread associated with the MessagePipe to which + * this Router is bound. This may be {@code Null} if the MessagePipeHandle passed + * in to the constructor is not valid. + */ + private final Executor mExecutor; + + /** + * Constructor that will use the default {@link Watcher}. + * + * @param messagePipeHandle The {@link MessagePipeHandle} to route message for. + */ + public RouterImpl(MessagePipeHandle messagePipeHandle) { + this(messagePipeHandle, BindingsHelper.getWatcherForHandle(messagePipeHandle)); + } + + /** + * Constructor. + * + * @param messagePipeHandle The {@link MessagePipeHandle} to route message for. + * @param watcher the {@link Watcher} to use to get notification of new messages on the + * handle. + */ + public RouterImpl(MessagePipeHandle messagePipeHandle, Watcher watcher) { + mConnector = new Connector(messagePipeHandle, watcher); + mConnector.setIncomingMessageReceiver(new HandleIncomingMessageThunk()); + Core core = messagePipeHandle.getCore(); + if (core != null) { + mExecutor = ExecutorFactory.getExecutorForCurrentThread(core); + } else { + mExecutor = null; + } + } + + /** + * @see org.chromium.mojo.bindings.Router#start() + */ + @Override + public void start() { + mConnector.start(); + } + + /** + * @see Router#setIncomingMessageReceiver(MessageReceiverWithResponder) + */ + @Override + public void setIncomingMessageReceiver(MessageReceiverWithResponder incomingMessageReceiver) { + this.mIncomingMessageReceiver = incomingMessageReceiver; + } + + /** + * @see MessageReceiver#accept(Message) + */ + @Override + public boolean accept(Message message) { + // A message without responder is directly forwarded to the connector. + return mConnector.accept(message); + } + + /** + * @see MessageReceiverWithResponder#acceptWithResponder(Message, MessageReceiver) + */ + @Override + public boolean acceptWithResponder(Message message, MessageReceiver responder) { + // The message must have a header. + ServiceMessage messageWithHeader = message.asServiceMessage(); + // Checking the message expects a response. + assert messageWithHeader.getHeader().hasFlag(MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG); + + // Compute a request id for being able to route the response. + // TODO(lhchavez): Remove this hack. See b/28986534 for details. + synchronized (mResponders) { + long requestId = mNextRequestId++; + // Reserve 0 in case we want it to convey special meaning in the future. + if (requestId == 0) { + requestId = mNextRequestId++; + } + if (mResponders.containsKey(requestId)) { + throw new IllegalStateException("Unable to find a new request identifier."); + } + messageWithHeader.setRequestId(requestId); + if (!mConnector.accept(messageWithHeader)) { + return false; + } + // Only keep the responder is the message has been accepted. + mResponders.put(requestId, responder); + } + return true; + } + + /** + * @see org.chromium.mojo.bindings.HandleOwner#passHandle() + */ + @Override + public MessagePipeHandle passHandle() { + return mConnector.passHandle(); + } + + /** + * @see java.io.Closeable#close() + */ + @Override + public void close() { + mConnector.close(); + } + + /** + * @see Router#setErrorHandler(ConnectionErrorHandler) + */ + @Override + public void setErrorHandler(ConnectionErrorHandler errorHandler) { + mConnector.setErrorHandler(errorHandler); + } + + /** + * Receive a message from the connector. Returns |true| if the message has been handled. + */ + private boolean handleIncomingMessage(Message message) { + MessageHeader header = message.asServiceMessage().getHeader(); + if (header.hasFlag(MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG)) { + if (mIncomingMessageReceiver != null) { + return mIncomingMessageReceiver.acceptWithResponder(message, new ResponderThunk()); + } + // If we receive a request expecting a response when the client is not + // listening, then we have no choice but to tear down the pipe. + close(); + return false; + } else if (header.hasFlag(MessageHeader.MESSAGE_IS_RESPONSE_FLAG)) { + long requestId = header.getRequestId(); + MessageReceiver responder; + // TODO(lhchavez): Remove this hack. See b/28986534 for details. + synchronized (mResponders) { + responder = mResponders.get(requestId); + if (responder == null) { + return false; + } + mResponders.remove(requestId); + } + return responder.accept(message); + } else { + if (mIncomingMessageReceiver != null) { + return mIncomingMessageReceiver.accept(message); + } + // OK to drop the message. + } + return false; + } + + private void handleConnectorClose() { + if (mIncomingMessageReceiver != null) { + mIncomingMessageReceiver.close(); + } + } + + /** + * Invokes {@link #close()} asynchronously on the thread associated with + * this Router's Handle. If this Router was constructed with an invalid + * handle then this method does nothing. + */ + private void closeOnHandleThread() { + if (mExecutor != null) { + mExecutor.execute(new Runnable() { + + @Override + public void run() { + close(); + } + }); + } + } +} diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/SerializationException.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/SerializationException.java new file mode 100644 index 0000000000..d4f550227f --- /dev/null +++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/SerializationException.java @@ -0,0 +1,26 @@ +// 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; + +/** + * Error that can be thrown when serializing a mojo message. + */ +public class SerializationException extends RuntimeException { + + /** + * Constructs a new serialization exception with the specified detail message. + */ + public SerializationException(String message) { + super(message); + } + + /** + * Constructs a new serialization exception with the specified cause. + */ + public SerializationException(Exception cause) { + super(cause); + } + +} diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ServiceMessage.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ServiceMessage.java new file mode 100644 index 0000000000..313dc6aea3 --- /dev/null +++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ServiceMessage.java @@ -0,0 +1,73 @@ +// 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 java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Represents a {@link Message} which contains a {@link MessageHeader}. Deals with parsing the + * {@link MessageHeader} for a message. + */ +public class ServiceMessage extends Message { + + private final MessageHeader mHeader; + private Message mPayload; + + /** + * Reinterpret the given |message| as a message with the given |header|. The |message| must + * contain the |header| as the start of its raw data. + */ + public ServiceMessage(Message baseMessage, MessageHeader header) { + super(baseMessage.getData(), baseMessage.getHandles()); + assert header.equals(new org.chromium.mojo.bindings.MessageHeader(baseMessage)); + this.mHeader = header; + } + + /** + * Reinterpret the given |message| as a message with a header. The |message| must contain a + * header as the start of it's raw data, which will be parsed by this constructor. + */ + ServiceMessage(Message baseMessage) { + this(baseMessage, new org.chromium.mojo.bindings.MessageHeader(baseMessage)); + } + + /** + * @see Message#asServiceMessage() + */ + @Override + public ServiceMessage asServiceMessage() { + return this; + } + + /** + * Returns the header of the given message. This will throw a {@link DeserializationException} + * if the start of the message is not a valid header. + */ + public MessageHeader getHeader() { + return mHeader; + } + + /** + * Returns the payload of the message. + */ + public Message getPayload() { + if (mPayload == null) { + ByteBuffer truncatedBuffer = + ((ByteBuffer) getData().position(getHeader().getSize())).slice(); + truncatedBuffer.order(ByteOrder.LITTLE_ENDIAN); + mPayload = new Message(truncatedBuffer, getHandles()); + } + return mPayload; + } + + /** + * Set the request identifier on the message. + */ + void setRequestId(long requestId) { + mHeader.setRequestId(getData(), requestId); + } + +} diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/SideEffectFreeCloseable.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/SideEffectFreeCloseable.java new file mode 100644 index 0000000000..118c991f7b --- /dev/null +++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/SideEffectFreeCloseable.java @@ -0,0 +1,21 @@ +// 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 java.io.Closeable; + +/** + * An implementation of closeable that doesn't do anything. + */ +public class SideEffectFreeCloseable implements Closeable { + + /** + * @see java.io.Closeable#close() + */ + @Override + public void close() { + } + +} diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Struct.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Struct.java new file mode 100644 index 0000000000..14f4e1e726 --- /dev/null +++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Struct.java @@ -0,0 +1,88 @@ +// 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.system.Core; + +import java.nio.ByteBuffer; + +/** + * Base class for all mojo structs. + */ +public abstract class Struct { + /** + * The base size of the encoded struct. + */ + private final int mEncodedBaseSize; + + /** + * The version of the struct. + */ + private final int mVersion; + + /** + * Constructor. + */ + protected Struct(int encodedBaseSize, int version) { + mEncodedBaseSize = encodedBaseSize; + mVersion = version; + } + + /** + * Returns the version of the struct. It is the max version of the struct in the mojom if it has + * been created locally, and the version of the received struct if it has been deserialized. + */ + public int getVersion() { + return mVersion; + } + + /** + * Returns the serialization of the struct. This method can close Handles. + * + * @param core the |Core| implementation used to generate handles. Only used if the data + * structure being encoded contains interfaces, can be |null| otherwise. + */ + public Message serialize(Core core) { + Encoder encoder = new Encoder(core, mEncodedBaseSize); + encode(encoder); + return encoder.getMessage(); + } + + /** + * Similar to the method above, but returns the serialization result as |ByteBuffer|. + * + * @throws UnsupportedOperationException if the struct contains interfaces or handles. + * @throws SerializationException on serialization failure. + */ + public ByteBuffer serialize() { + // If the struct contains interfaces which require a non-null |Core| instance, it will throw + // UnsupportedOperationException. + Message message = serialize(null); + + if (!message.getHandles().isEmpty()) + throw new UnsupportedOperationException("Handles are discarded."); + + return message.getData(); + } + + /** + * Returns the serialization of the struct prepended with the given header. + * + * @param header the header to prepend to the returned message. + * @param core the |Core| implementation used to generate handles. Only used if the |Struct| + * being encoded contains interfaces, can be |null| otherwise. + */ + public ServiceMessage serializeWithHeader(Core core, MessageHeader header) { + Encoder encoder = new Encoder(core, mEncodedBaseSize + header.getSize()); + header.encode(encoder); + encode(encoder); + return new ServiceMessage(encoder.getMessage(), header); + } + + /** + * Use the given encoder to serialize this data structure. + */ + protected abstract void encode(Encoder encoder); +} diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Union.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Union.java new file mode 100644 index 0000000000..90b40ea57b --- /dev/null +++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Union.java @@ -0,0 +1,30 @@ +// 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.system.Core; + +/** + * Base class for all mojo unions. + */ +public abstract class Union { + /** + * Returns the serialization of the union. This method can close Handles. + * + * @param core the |Core| implementation used to generate handles. Only used if the data + * structure being encoded contains interfaces, can be |null| otherwise. + */ + public Message serialize(Core core) { + Encoder encoder = new Encoder(core, BindingsHelper.UNION_SIZE); + encoder.claimMemory(16); + encode(encoder, 0); + return encoder.getMessage(); + } + + /** + * Serializes this data structure using the given encoder. + */ + protected abstract void encode(Encoder encoder, int offset); +} diff --git a/mojo/public/java/system/README.md b/mojo/public/java/system/README.md new file mode 100644 index 0000000000..3213e4c790 --- /dev/null +++ b/mojo/public/java/system/README.md @@ -0,0 +1,25 @@ +# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojo Java System API +This document is a subset of the [Mojo documentation](/mojo). + +[TOC] + +## Overview + +This document provides a brief guide to Java Mojo bindings usage with example +code snippets. + +For a detailed API references please consult the class definitions in +[this directory](https://cs.chromium.org/chromium/src/mojo/public/java/system/src/org/chromium/mojo/system/). + +*TODO: Make the contents of this document less non-existent.* + +## Message Pipes + +## Data Pipes + +## Shared Buffers + +## Native Platform Handles (File Descriptors, Windows Handles, *etc.*) + +## Watchers + diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/Core.java b/mojo/public/java/system/src/org/chromium/mojo/system/Core.java new file mode 100644 index 0000000000..40e4be365d --- /dev/null +++ b/mojo/public/java/system/src/org/chromium/mojo/system/Core.java @@ -0,0 +1,183 @@ +// 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.system; + +/** + * Core mojo interface giving access to the base operations. See |src/mojo/public/c/system/core.h| + * for the underlying api. + */ +public interface Core { + + /** + * Used to indicate an infinite deadline (timeout). + */ + public static final long DEADLINE_INFINITE = -1; + + /** + * Signals for the wait operations on handles. + */ + public static class HandleSignals extends Flags<HandleSignals> { + /** + * Constructor. + * + * @param signals the serialized signals. + */ + public HandleSignals(int signals) { + super(signals); + } + + private static final int FLAG_NONE = 0; + private static final int FLAG_READABLE = 1 << 0; + private static final int FLAG_WRITABLE = 1 << 1; + private static final int FLAG_PEER_CLOSED = 1 << 2; + + /** + * Immutable signals. + */ + public static final HandleSignals NONE = HandleSignals.none().immutable(); + public static final HandleSignals READABLE = + HandleSignals.none().setReadable(true).immutable(); + public static final HandleSignals WRITABLE = + HandleSignals.none().setWritable(true).immutable(); + + /** + * Change the readable bit of this signal. + * + * @param readable the new value of the readable bit. + * @return this. + */ + public HandleSignals setReadable(boolean readable) { + return setFlag(FLAG_READABLE, readable); + } + + /** + * Change the writable bit of this signal. + * + * @param writable the new value of the writable bit. + * @return this. + */ + public HandleSignals setWritable(boolean writable) { + return setFlag(FLAG_WRITABLE, writable); + } + + /** + * Change the peer closed bit of this signal. + * + * @param peerClosed the new value of the peer closed bit. + * @return this. + */ + public HandleSignals setPeerClosed(boolean peerClosed) { + return setFlag(FLAG_PEER_CLOSED, peerClosed); + } + + /** + * Returns a signal with no bit set. + */ + public static HandleSignals none() { + return new HandleSignals(FLAG_NONE); + } + + } + + /** + * Returns a platform-dependent monotonically increasing tick count representing "right now." + */ + public long getTimeTicksNow(); + + /** + * Returned by wait functions to indicate the signaling state of handles. + */ + public static class HandleSignalsState { + /** + * Signals that were satisfied at some time // before the call returned. + */ + private final HandleSignals mSatisfiedSignals; + + /** + * Signals that are possible to satisfy. For example, if the return value was + * |MOJO_RESULT_FAILED_PRECONDITION|, you can use this field to determine which, if any, of + * the signals can still be satisfied. + */ + private final HandleSignals mSatisfiableSignals; + + /** + * Constructor. + */ + public HandleSignalsState( + HandleSignals satisfiedSignals, HandleSignals satisfiableSignals) { + mSatisfiedSignals = satisfiedSignals; + mSatisfiableSignals = satisfiableSignals; + } + + /** + * Returns the satisfiedSignals. + */ + public HandleSignals getSatisfiedSignals() { + return mSatisfiedSignals; + } + + /** + * Returns the satisfiableSignals. + */ + public HandleSignals getSatisfiableSignals() { + return mSatisfiableSignals; + } + } + + /** + * Creates a message pipe, which is a bidirectional communication channel for framed data (i.e., + * messages), with the given options. Messages can contain plain data and/or Mojo handles. + * + * @return the set of handles for the two endpoints (ports) of the message pipe. + */ + public Pair<MessagePipeHandle, MessagePipeHandle> createMessagePipe( + MessagePipeHandle.CreateOptions options); + + /** + * Creates a data pipe, which is a unidirectional communication channel for unframed data, with + * the given options. Data is unframed, but must come as (multiples of) discrete elements, of + * the size given in |options|. See |DataPipe.CreateOptions| for a description of the different + * options available for data pipes. |options| may be set to null for a data pipe with the + * default options (which will have an element size of one byte and have some system-dependent + * capacity). + * + * @return the set of handles for the two endpoints of the data pipe. + */ + public Pair<DataPipe.ProducerHandle, DataPipe.ConsumerHandle> createDataPipe( + DataPipe.CreateOptions options); + + /** + * Creates a buffer that can be shared between applications (by duplicating the handle -- see + * |SharedBufferHandle.duplicate()| -- and passing it over a message pipe). To access the + * buffer, one must call |SharedBufferHandle.map|. + * + * @return the new |SharedBufferHandle|. + */ + public SharedBufferHandle createSharedBuffer(SharedBufferHandle.CreateOptions options, + long numBytes); + + /** + * Acquires a handle from the native side. The handle will be owned by the returned object and + * must not be closed outside of it. + * + * @return a new {@link UntypedHandle} representing the native handle. + */ + public UntypedHandle acquireNativeHandle(int handle); + + /** + * Returns an implementation of {@link Watcher}. + */ + public Watcher getWatcher(); + + /** + * Returns a new run loop. + */ + public RunLoop createDefaultRunLoop(); + + /** + * Returns the current run loop if it exists. + */ + public RunLoop getCurrentRunLoop(); +} diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/DataPipe.java b/mojo/public/java/system/src/org/chromium/mojo/system/DataPipe.java new file mode 100644 index 0000000000..4deaf0975d --- /dev/null +++ b/mojo/public/java/system/src/org/chromium/mojo/system/DataPipe.java @@ -0,0 +1,334 @@ +// 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.system; + +import java.nio.ByteBuffer; + +/** + * Interface for data pipes. A data pipe is a unidirectional communication channel for unframed + * data. Data is unframed, but must come as (multiples of) discrete elements, of the size given at + * creation time. + */ +public interface DataPipe { + + /** + * Flags for the data pipe creation operation. + */ + public static class CreateFlags extends Flags<CreateFlags> { + private static final int FLAG_NONE = 0; + + /** + * Immutable flag with not bit set. + */ + public static final CreateFlags NONE = CreateFlags.none().immutable(); + + /** + * Dedicated constructor. + * + * @param flags initial value of the flags. + */ + protected CreateFlags(int flags) { + super(flags); + } + + /** + * @return flags with no bit set. + */ + public static CreateFlags none() { + return new CreateFlags(FLAG_NONE); + } + + } + + /** + * Used to specify creation parameters for a data pipe to |Core.createDataPipe()|. + */ + public static class CreateOptions { + + /** + * Used to specify different modes of operation, see |DataPipe.CreateFlags|. + */ + private CreateFlags mFlags = CreateFlags.none(); + /** + * The size of an element, in bytes. All transactions and buffers will consist of an + * integral number of elements. Must be nonzero. + */ + private int mElementNumBytes; + /** + * The capacity of the data pipe, in number of bytes; must be a multiple of + * |element_num_bytes|. The data pipe will always be able to queue AT LEAST this much data. + * Set to zero to opt for a system-dependent automatically-calculated capacity (which will + * always be at least one element). + */ + private int mCapacityNumBytes; + + /** + * @return the flags + */ + public CreateFlags getFlags() { + return mFlags; + } + + /** + * @return the elementNumBytes + */ + public int getElementNumBytes() { + return mElementNumBytes; + } + + /** + * @param elementNumBytes the elementNumBytes to set + */ + public void setElementNumBytes(int elementNumBytes) { + mElementNumBytes = elementNumBytes; + } + + /** + * @return the capacityNumBytes + */ + public int getCapacityNumBytes() { + return mCapacityNumBytes; + } + + /** + * @param capacityNumBytes the capacityNumBytes to set + */ + public void setCapacityNumBytes(int capacityNumBytes) { + mCapacityNumBytes = capacityNumBytes; + } + + } + + /** + * Flags for the write operations on MessagePipeHandle . + */ + public static class WriteFlags extends Flags<WriteFlags> { + private static final int FLAG_NONE = 0; + private static final int FLAG_ALL_OR_NONE = 1 << 0; + + /** + * Immutable flag with not bit set. + */ + public static final WriteFlags NONE = WriteFlags.none().immutable(); + + /** + * Dedicated constructor. + * + * @param flags initial value of the flags. + */ + private WriteFlags(int flags) { + super(flags); + } + + /** + * Change the all-or-none bit of those flags. If set, write either all the elements + * requested or none of them. + * + * @param allOrNone the new value of all-or-none bit. + * @return this. + */ + public WriteFlags setAllOrNone(boolean allOrNone) { + return setFlag(FLAG_ALL_OR_NONE, allOrNone); + } + + /** + * @return a flag with no bit set. + */ + public static WriteFlags none() { + return new WriteFlags(FLAG_NONE); + } + } + + /** + * Flags for the read operations on MessagePipeHandle. + */ + public static class ReadFlags extends Flags<ReadFlags> { + private static final int FLAG_NONE = 0; + private static final int FLAG_ALL_OR_NONE = 1 << 0; + private static final int FLAG_QUERY = 1 << 2; + private static final int FLAG_PEEK = 1 << 3; + + /** + * Immutable flag with not bit set. + */ + public static final ReadFlags NONE = ReadFlags.none().immutable(); + + /** + * Dedicated constructor. + * + * @param flags initial value of the flag. + */ + private ReadFlags(int flags) { + super(flags); + } + + /** + * Change the all-or-none bit of this flag. If set, read (or discard) either the requested + * number of elements or none. + * + * @param allOrNone the new value of the all-or-none bit. + * @return this. + */ + public ReadFlags setAllOrNone(boolean allOrNone) { + return setFlag(FLAG_ALL_OR_NONE, allOrNone); + } + + /** + * Change the query bit of this flag. If set query the number of elements available to read. + * Mutually exclusive with |discard| and |allOrNone| is ignored if this flag is set. + * + * @param query the new value of the query bit. + * @return this. + */ + public ReadFlags query(boolean query) { + return setFlag(FLAG_QUERY, query); + } + + /** + * Change the peek bit of this flag. If set, read the requested number of elements, and + * leave those elements in the pipe. A later read will return the same data. + * Mutually exclusive with |discard| and |query|. + * + * @param peek the new value of the peek bit. + * @return this. + */ + public ReadFlags peek(boolean peek) { + return setFlag(FLAG_PEEK, peek); + } + + /** + * @return a flag with no bit set. + */ + public static ReadFlags none() { + return new ReadFlags(FLAG_NONE); + } + + } + + /** + * Handle for the producer part of a data pipe. + */ + public static interface ProducerHandle extends Handle { + + /** + * @see org.chromium.mojo.system.Handle#pass() + */ + @Override + public ProducerHandle pass(); + + /** + * Writes the given data to the data pipe producer. |elements| points to data; the buffer + * must be a direct ByteBuffer and the limit should be a multiple of the data pipe's element + * size. If |allOrNone| is set in |flags|, either all the data will be written or none is. + * <p> + * On success, returns the amount of data that was actually written. + * <p> + * Note: If the data pipe has the "may discard" option flag (specified on creation), this + * will discard as much data as required to write the given data, starting with the earliest + * written data that has not been consumed. However, even with "may discard", if the buffer + * limit is greater than the data pipe's capacity (and |allOrNone| is not set), this will + * write the maximum amount possible (namely, the data pipe's capacity) and return that + * amount. It will *not* discard data from |elements|. + * + * @return number of written bytes. + */ + public ResultAnd<Integer> writeData(ByteBuffer elements, WriteFlags flags); + + /** + * Begins a two-phase write to the data pipe producer . On success, returns a |ByteBuffer| + * to which the caller can write. If flags has |allOrNone| set, then the buffer capacity + * will be at least as large as |numBytes|, which must also be a multiple of the element + * size (if |allOrNone| is not set, |numBytes| is ignored and the caller must check the + * capacity of the buffer). + * <p> + * During a two-phase write, this handle is *not* writable. E.g., if another thread tries to + * write to it, it will throw a |MojoException| with code |MojoResult.BUSY|; that thread can + * then wait for this handle to become writable again. + * <p> + * Once the caller has finished writing data to the buffer, it should call |endWriteData()| + * to specify the amount written and to complete the two-phase write. + * <p> + * Note: If the data pipe has the "may discard" option flag (specified on creation) and + * |flags| has |allOrNone| set, this may discard some data. + * + * @return The buffer to write to. + */ + public ByteBuffer beginWriteData(int numBytes, WriteFlags flags); + + /** + * Ends a two-phase write to the data pipe producer that was begun by a call to + * |beginWriteData()| on the same handle. |numBytesWritten| should indicate the amount of + * data actually written; it must be less than or equal to the capacity of the buffer + * returned by |beginWriteData()| and must be a multiple of the element size. The buffer + * returned from |beginWriteData()| must have been filled with exactly |numBytesWritten| + * bytes of data. + * <p> + * On failure, the two-phase write (if any) is ended (so the handle may become writable + * again, if there's space available) but no data written to the buffer is "put into" the + * data pipe. + */ + public void endWriteData(int numBytesWritten); + } + + /** + * Handle for the consumer part of a data pipe. + */ + public static interface ConsumerHandle extends Handle { + /** + * @see org.chromium.mojo.system.Handle#pass() + */ + @Override + public ConsumerHandle pass(); + + /** + * Discards data on the data pie consumer. This method discards up to |numBytes| (which + * again be a multiple of the element size) bytes of data, returning the amount actually + * discarded. if |flags| has |allOrNone|, it will either discard exactly |numBytes| bytes of + * data or none. In this case, |query| must not be set. + */ + public int discardData(int numBytes, ReadFlags flags); + + /** + * Reads data from the data pipe consumer. May also be used to query the amount of data + * available. If |flags| has not |query| set, this tries to read up to |elements| capacity + * (which must be a multiple of the data pipe's element size) bytes of data to |elements| + * and returns the amount actually read. |elements| must be a direct ByteBuffer. If flags + * has |allOrNone| set, it will either read exactly |elements| capacity bytes of data or + * none. + * <p> + * If flags has |query| set, it queries the amount of data available, returning the number + * of bytes available. In this case |allOrNone| is ignored, as are |elements|. + */ + public ResultAnd<Integer> readData(ByteBuffer elements, ReadFlags flags); + + /** + * Begins a two-phase read from the data pipe consumer. On success, returns a |ByteBuffer| + * from which the caller can read up to its limit bytes of data. If flags has |allOrNone| + * set, then the limit will be at least as large as |numBytes|, which must also be a + * multiple of the element size (if |allOrNone| is not set, |numBytes| is ignored). |flags| + * must not have |query| set. + * <p> + * During a two-phase read, this handle is *not* readable. E.g., if another thread tries to + * read from it, it will throw a |MojoException| with code |MojoResult.BUSY|; that thread + * can then wait for this handle to become readable again. + * <p> + * Once the caller has finished reading data from the buffer, it should call |endReadData()| + * to specify the amount read and to complete the two-phase read. + */ + public ByteBuffer beginReadData(int numBytes, ReadFlags flags); + + /** + * Ends a two-phase read from the data pipe consumer that was begun by a call to + * |beginReadData()| on the same handle. |numBytesRead| should indicate the amount of data + * actually read; it must be less than or equal to the limit of the buffer returned by + * |beginReadData()| and must be a multiple of the element size. + * <p> + * On failure, the two-phase read (if any) is ended (so the handle may become readable + * again) but no data is "removed" from the data pipe. + */ + public void endReadData(int numBytesRead); + } + +} diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/Flags.java b/mojo/public/java/system/src/org/chromium/mojo/system/Flags.java new file mode 100644 index 0000000000..30ff07f710 --- /dev/null +++ b/mojo/public/java/system/src/org/chromium/mojo/system/Flags.java @@ -0,0 +1,83 @@ +// 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.system; + +/** + * Base class for bit field used as flags. + * + * @param <F> the type of the flags. + */ +public abstract class Flags<F extends Flags<F>> { + private int mFlags; + private boolean mImmutable; + + /** + * Dedicated constructor. + * + * @param flags initial value of the flag. + */ + protected Flags(int flags) { + mImmutable = false; + mFlags = flags; + } + + /** + * @return the computed flag. + */ + public int getFlags() { + return mFlags; + } + + /** + * Change the given bit of this flag. + * + * @param value the new value of given bit. + * @return this. + */ + protected F setFlag(int flag, boolean value) { + if (mImmutable) { + throw new UnsupportedOperationException("Flags is immutable."); + } + if (value) { + mFlags |= flag; + } else { + mFlags &= ~flag; + } + @SuppressWarnings("unchecked") + F f = (F) this; + return f; + } + + /** + * Makes this flag immutable. This is a non-reversable operation. + */ + protected F immutable() { + mImmutable = true; + @SuppressWarnings("unchecked") + F f = (F) this; + return f; + } + + /** + * @see Object#hashCode() + */ + @Override + public int hashCode() { + return mFlags; + } + + /** + * @see Object#equals(Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + Flags<?> other = (Flags<?>) obj; + if (mFlags != other.mFlags) return false; + return true; + } +} diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/Handle.java b/mojo/public/java/system/src/org/chromium/mojo/system/Handle.java new file mode 100644 index 0000000000..903f36d677 --- /dev/null +++ b/mojo/public/java/system/src/org/chromium/mojo/system/Handle.java @@ -0,0 +1,61 @@ +// 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.system; + +import org.chromium.mojo.system.Core.HandleSignalsState; + +import java.io.Closeable; + +/** + * A generic mojo handle. + */ +public interface Handle extends Closeable { + + /** + * Closes the given |handle|. + * <p> + * Concurrent operations on |handle| may succeed (or fail as usual) if they happen before the + * close, be cancelled with result |MojoResult.CANCELLED| if they properly overlap (this is + * likely the case with |wait()|, etc.), or fail with |MojoResult.INVALID_ARGUMENT| if they + * happen after. + */ + @Override + public void close(); + + /** + * @return the last known signaling state of the handle. + */ + public HandleSignalsState querySignalsState(); + + /** + * @return whether the handle is valid. A handle is valid until it has been explicitly closed or + * send through a message pipe via |MessagePipeHandle.writeMessage|. + */ + public boolean isValid(); + + /** + * Converts this handle into an {@link UntypedHandle}, invalidating this handle. + */ + public UntypedHandle toUntypedHandle(); + + /** + * Returns the {@link Core} implementation for this handle. Can be null if this handle is + * invalid. + */ + public Core getCore(); + + /** + * Passes ownership of the handle from this handle to the newly created Handle object, + * invalidating this handle object in the process. + */ + public Handle pass(); + + /** + * Releases the native handle backed by this {@link Handle}. The caller owns the handle and must + * close it. + */ + public int releaseNativeHandle(); + +} diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/InvalidHandle.java b/mojo/public/java/system/src/org/chromium/mojo/system/InvalidHandle.java new file mode 100644 index 0000000000..f8b99c6d66 --- /dev/null +++ b/mojo/public/java/system/src/org/chromium/mojo/system/InvalidHandle.java @@ -0,0 +1,219 @@ +// 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.system; + +import org.chromium.mojo.system.Core.HandleSignalsState; +import org.chromium.mojo.system.DataPipe.ConsumerHandle; +import org.chromium.mojo.system.DataPipe.ProducerHandle; + +import java.nio.ByteBuffer; +import java.util.List; + +/** + * A handle that will always be invalid. + */ +public class InvalidHandle implements UntypedHandle, MessagePipeHandle, ConsumerHandle, + ProducerHandle, SharedBufferHandle { + + /** + * Instance singleton. + */ + public static final InvalidHandle INSTANCE = new InvalidHandle(); + + /** + * Private constructor. + */ + private InvalidHandle() { + } + + /** + * @see Handle#close() + */ + @Override + public void close() { + // Do nothing. + } + + /** + * @see Handle#querySignalsState() + */ + @Override + public HandleSignalsState querySignalsState() { + throw new MojoException(MojoResult.INVALID_ARGUMENT); + } + + /** + * @see Handle#isValid() + */ + @Override + public boolean isValid() { + return false; + } + + /** + * @see Handle#getCore() + */ + @Override + public Core getCore() { + return null; + } + + /** + * @see org.chromium.mojo.system.Handle#pass() + */ + @Override + public InvalidHandle pass() { + return this; + } + + /** + * @see Handle#toUntypedHandle() + */ + @Override + public UntypedHandle toUntypedHandle() { + return this; + } + + /** + * @see Handle#releaseNativeHandle() + */ + @Override + public int releaseNativeHandle() { + return 0; + } + + /** + * @see UntypedHandle#toMessagePipeHandle() + */ + @Override + public MessagePipeHandle toMessagePipeHandle() { + return this; + } + + /** + * @see UntypedHandle#toDataPipeConsumerHandle() + */ + @Override + public ConsumerHandle toDataPipeConsumerHandle() { + return this; + } + + /** + * @see UntypedHandle#toDataPipeProducerHandle() + */ + @Override + public ProducerHandle toDataPipeProducerHandle() { + return this; + } + + /** + * @see UntypedHandle#toSharedBufferHandle() + */ + @Override + public SharedBufferHandle toSharedBufferHandle() { + return this; + } + + /** + * @see SharedBufferHandle#duplicate(SharedBufferHandle.DuplicateOptions) + */ + @Override + public SharedBufferHandle duplicate(DuplicateOptions options) { + throw new MojoException(MojoResult.INVALID_ARGUMENT); + } + + /** + * @see SharedBufferHandle#map(long, long, SharedBufferHandle.MapFlags) + */ + @Override + public ByteBuffer map(long offset, long numBytes, MapFlags flags) { + throw new MojoException(MojoResult.INVALID_ARGUMENT); + } + + /** + * @see SharedBufferHandle#unmap(java.nio.ByteBuffer) + */ + @Override + public void unmap(ByteBuffer buffer) { + throw new MojoException(MojoResult.INVALID_ARGUMENT); + } + + /** + * @see DataPipe.ProducerHandle#writeData(java.nio.ByteBuffer, DataPipe.WriteFlags) + */ + @Override + public ResultAnd<Integer> writeData(ByteBuffer elements, DataPipe.WriteFlags flags) { + throw new MojoException(MojoResult.INVALID_ARGUMENT); + } + + /** + * @see DataPipe.ProducerHandle#beginWriteData(int, DataPipe.WriteFlags) + */ + @Override + public ByteBuffer beginWriteData(int numBytes, + DataPipe.WriteFlags flags) { + throw new MojoException(MojoResult.INVALID_ARGUMENT); + } + + /** + * @see DataPipe.ProducerHandle#endWriteData(int) + */ + @Override + public void endWriteData(int numBytesWritten) { + throw new MojoException(MojoResult.INVALID_ARGUMENT); + } + + /** + * @see DataPipe.ConsumerHandle#discardData(int, DataPipe.ReadFlags) + */ + @Override + public int discardData(int numBytes, DataPipe.ReadFlags flags) { + throw new MojoException(MojoResult.INVALID_ARGUMENT); + } + + /** + * @see DataPipe.ConsumerHandle#readData(java.nio.ByteBuffer, DataPipe.ReadFlags) + */ + @Override + public ResultAnd<Integer> readData(ByteBuffer elements, DataPipe.ReadFlags flags) { + throw new MojoException(MojoResult.INVALID_ARGUMENT); + } + + /** + * @see DataPipe.ConsumerHandle#beginReadData(int, DataPipe.ReadFlags) + */ + @Override + public ByteBuffer beginReadData(int numBytes, + DataPipe.ReadFlags flags) { + throw new MojoException(MojoResult.INVALID_ARGUMENT); + } + + /** + * @see DataPipe.ConsumerHandle#endReadData(int) + */ + @Override + public void endReadData(int numBytesRead) { + throw new MojoException(MojoResult.INVALID_ARGUMENT); + } + + /** + * @see MessagePipeHandle#writeMessage(java.nio.ByteBuffer, java.util.List, + * MessagePipeHandle.WriteFlags) + */ + @Override + public void writeMessage(ByteBuffer bytes, List<? extends Handle> handles, WriteFlags flags) { + throw new MojoException(MojoResult.INVALID_ARGUMENT); + } + + /** + * @see MessagePipeHandle#readMessage(java.nio.ByteBuffer, int, MessagePipeHandle.ReadFlags) + */ + @Override + public ResultAnd<ReadMessageResult> readMessage( + ByteBuffer bytes, int maxNumberOfHandles, ReadFlags flags) { + throw new MojoException(MojoResult.INVALID_ARGUMENT); + } + +} diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/MessagePipeHandle.java b/mojo/public/java/system/src/org/chromium/mojo/system/MessagePipeHandle.java new file mode 100644 index 0000000000..deb6ac0f01 --- /dev/null +++ b/mojo/public/java/system/src/org/chromium/mojo/system/MessagePipeHandle.java @@ -0,0 +1,224 @@ +// 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.system; + +import java.nio.ByteBuffer; +import java.util.List; + +/** + * Message pipes are bidirectional communication channel for framed data (i.e., messages). Messages + * can contain plain data and/or Mojo handles. + */ +public interface MessagePipeHandle extends Handle { + + /** + * Flags for the message pipe creation operation. + */ + public static class CreateFlags extends Flags<CreateFlags> { + private static final int FLAG_NONE = 0; + + /** + * Immutable flag with not bit set. + */ + public static final CreateFlags NONE = CreateFlags.none().immutable(); + + /** + * Dedicated constructor. + * + * @param flags initial value of the flags. + */ + protected CreateFlags(int flags) { + super(flags); + } + + /** + * @return flags with no bit set. + */ + public static CreateFlags none() { + return new CreateFlags(FLAG_NONE); + } + + } + + /** + * Used to specify creation parameters for a message pipe to |Core#createMessagePipe()|. + */ + public static class CreateOptions { + private CreateFlags mFlags = CreateFlags.NONE; + + /** + * @return the flags + */ + public CreateFlags getFlags() { + return mFlags; + } + + } + + /** + * Flags for the write operations on MessagePipeHandle . + */ + public static class WriteFlags extends Flags<WriteFlags> { + private static final int FLAG_NONE = 0; + + /** + * Immutable flag with no bit set. + */ + public static final WriteFlags NONE = WriteFlags.none().immutable(); + + /** + * Dedicated constructor. + * + * @param flags initial value of the flag. + */ + private WriteFlags(int flags) { + super(flags); + } + + /** + * @return a flag with no bit set. + */ + public static WriteFlags none() { + return new WriteFlags(FLAG_NONE); + } + } + + /** + * Flags for the read operations on MessagePipeHandle. + */ + public static class ReadFlags extends Flags<ReadFlags> { + private static final int FLAG_NONE = 0; + private static final int FLAG_MAY_DISCARD = 1 << 0; + + /** + * Immutable flag with no bit set. + */ + public static final ReadFlags NONE = ReadFlags.none().immutable(); + + /** + * Dedicated constructor. + * + * @param flags initial value of the flag. + */ + private ReadFlags(int flags) { + super(flags); + } + + /** + * Change the may-discard bit of this flag. If set, if the message is unable to be read for + * whatever reason (e.g., the caller-supplied buffer is too small), discard the message + * (i.e., simply dequeue it). + * + * @param mayDiscard the new value of the may-discard bit. + * @return this. + */ + public ReadFlags setMayDiscard(boolean mayDiscard) { + return setFlag(FLAG_MAY_DISCARD, mayDiscard); + } + + /** + * @return a flag with no bit set. + */ + public static ReadFlags none() { + return new ReadFlags(FLAG_NONE); + } + + } + + /** + * Result of the |readMessage| method. + */ + public static class ReadMessageResult { + /** + * If a message was read, the size in bytes of the message, otherwise the size in bytes of + * the next message. + */ + private int mMessageSize; + /** + * If a message was read, the number of handles contained in the message, otherwise the + * number of handles contained in the next message. + */ + private int mHandlesCount; + /** + * If a message was read, the handles contained in the message, undefined otherwise. + */ + private List<UntypedHandle> mHandles; + + /** + * @return the messageSize + */ + public int getMessageSize() { + return mMessageSize; + } + + /** + * @param messageSize the messageSize to set + */ + public void setMessageSize(int messageSize) { + mMessageSize = messageSize; + } + + /** + * @return the handlesCount + */ + public int getHandlesCount() { + return mHandlesCount; + } + + /** + * @param handlesCount the handlesCount to set + */ + public void setHandlesCount(int handlesCount) { + mHandlesCount = handlesCount; + } + + /** + * @return the handles + */ + public List<UntypedHandle> getHandles() { + return mHandles; + } + + /** + * @param handles the handles to set + */ + public void setHandles(List<UntypedHandle> handles) { + mHandles = handles; + } + } + + /** + * @see org.chromium.mojo.system.Handle#pass() + */ + @Override + public MessagePipeHandle pass(); + + /** + * Writes a message to the message pipe endpoint, with message data specified by |bytes| and + * attached handles specified by |handles|, and options specified by |flags|. If there is no + * message data, |bytes| may be null, otherwise it must be a direct ByteBuffer. If there are no + * attached handles, |handles| may be null. + * <p> + * If handles are attached, on success the handles will no longer be valid (the receiver will + * receive equivalent, but logically different, handles). Handles to be sent should not be in + * simultaneous use (e.g., on another thread). + */ + void writeMessage(ByteBuffer bytes, List<? extends Handle> handles, WriteFlags flags); + + /** + * Reads a message from the message pipe endpoint; also usable to query the size of the next + * message or discard the next message. |bytes| indicate the buffer/buffer size to receive the + * message data (if any) and |maxNumberOfHandles| indicate the maximum handle count to receive + * the attached handles (if any). |bytes| is optional. If null, |maxNumberOfHandles| must be + * zero, and the return value will indicate the size of the next message. If non-null, it must + * be a direct ByteBuffer and the return value will indicate if the message was read or not. If + * the message was read its content will be in |bytes|, and the attached handles will be in the + * return value. Partial reads are NEVER done. Either a full read is done and |wasMessageRead| + * will be true, or the read is NOT done and |wasMessageRead| will be false (if |mayDiscard| was + * set, the message is also discarded in this case). + */ + ResultAnd<ReadMessageResult> readMessage( + ByteBuffer bytes, int maxNumberOfHandles, ReadFlags flags); +} diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/MojoException.java b/mojo/public/java/system/src/org/chromium/mojo/system/MojoException.java new file mode 100644 index 0000000000..4e0e3e9597 --- /dev/null +++ b/mojo/public/java/system/src/org/chromium/mojo/system/MojoException.java @@ -0,0 +1,44 @@ +// 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.system; + +/** + * Exception for the core mojo API. + */ +public class MojoException extends RuntimeException { + + private final int mCode; + + /** + * Constructor. + */ + public MojoException(int code) { + mCode = code; + } + + /** + * Constructor. + */ + public MojoException(Throwable cause) { + super(cause); + mCode = MojoResult.UNKNOWN; + } + + /** + * The mojo result code associated with this exception. See {@link MojoResult} for possible + * values. + */ + public int getMojoResult() { + return mCode; + } + + /** + * @see Object#toString() + */ + @Override + public String toString() { + return "MojoResult(" + mCode + "): " + MojoResult.describe(mCode); + } +} diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/MojoResult.java b/mojo/public/java/system/src/org/chromium/mojo/system/MojoResult.java new file mode 100644 index 0000000000..2602cb5e1e --- /dev/null +++ b/mojo/public/java/system/src/org/chromium/mojo/system/MojoResult.java @@ -0,0 +1,82 @@ +// 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.system; + +/** + * The different mojo result codes. + */ +public final class MojoResult { + public static final int OK = 0; + public static final int CANCELLED = 1; + public static final int UNKNOWN = 2; + public static final int INVALID_ARGUMENT = 3; + public static final int DEADLINE_EXCEEDED = 4; + public static final int NOT_FOUND = 5; + public static final int ALREADY_EXISTS = 6; + public static final int PERMISSION_DENIED = 7; + public static final int RESOURCE_EXHAUSTED = 8; + public static final int FAILED_PRECONDITION = 9; + public static final int ABORTED = 10; + public static final int OUT_OF_RANGE = 11; + public static final int UNIMPLEMENTED = 12; + public static final int INTERNAL = 13; + public static final int UNAVAILABLE = 14; + public static final int DATA_LOSS = 15; + public static final int BUSY = 16; + public static final int SHOULD_WAIT = 17; + + /** + * never instantiate. + */ + private MojoResult() { + } + + /** + * Describes the given result code. + */ + public static String describe(int mCode) { + switch (mCode) { + case OK: + return "OK"; + case CANCELLED: + return "CANCELLED"; + case UNKNOWN: + return "UNKNOWN"; + case INVALID_ARGUMENT: + return "INVALID_ARGUMENT"; + case DEADLINE_EXCEEDED: + return "DEADLINE_EXCEEDED"; + case NOT_FOUND: + return "NOT_FOUND"; + case ALREADY_EXISTS: + return "ALREADY_EXISTS"; + case PERMISSION_DENIED: + return "PERMISSION_DENIED"; + case RESOURCE_EXHAUSTED: + return "RESOURCE_EXHAUSTED"; + case FAILED_PRECONDITION: + return "FAILED_PRECONDITION"; + case ABORTED: + return "ABORTED"; + case OUT_OF_RANGE: + return "OUT_OF_RANGE"; + case UNIMPLEMENTED: + return "UNIMPLEMENTED"; + case INTERNAL: + return "INTERNAL"; + case UNAVAILABLE: + return "UNAVAILABLE"; + case DATA_LOSS: + return "DATA_LOSS"; + case BUSY: + return "BUSY"; + case SHOULD_WAIT: + return "SHOULD_WAIT"; + default: + return "UNKNOWN"; + } + + } +} diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/Pair.java b/mojo/public/java/system/src/org/chromium/mojo/system/Pair.java new file mode 100644 index 0000000000..2ead0204f9 --- /dev/null +++ b/mojo/public/java/system/src/org/chromium/mojo/system/Pair.java @@ -0,0 +1,67 @@ +// 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.system; + + +/** + * A pair of object. + * + * @param <F> Type of the first element. + * @param <S> Type of the second element. + */ +public class Pair<F, S> { + + public final F first; + public final S second; + + /** + * Dedicated constructor. + * + * @param first the first element of the pair. + * @param second the second element of the pair. + */ + public Pair(F first, S second) { + this.first = first; + this.second = second; + } + + /** + * equals() that handles null values. + */ + private boolean equals(Object o1, Object o2) { + return o1 == null ? o2 == null : o1.equals(o2); + } + + /** + * @see Object#equals(Object) + */ + @Override + public boolean equals(Object o) { + if (!(o instanceof Pair)) { + return false; + } + Pair<?, ?> p = (Pair<?, ?>) o; + return equals(first, p.first) && equals(second, p.second); + } + + /** + * @see Object#hashCode() + */ + @Override + public int hashCode() { + return (first == null ? 0 : first.hashCode()) ^ (second == null ? 0 : second.hashCode()); + } + + /** + * Helper method for creating a pair. + * + * @param a the first element of the pair. + * @param b the second element of the pair. + * @return the pair containing a and b. + */ + public static <A, B> Pair<A, B> create(A a, B b) { + return new Pair<A, B>(a, b); + } +} diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/ResultAnd.java b/mojo/public/java/system/src/org/chromium/mojo/system/ResultAnd.java new file mode 100644 index 0000000000..656d0d64e4 --- /dev/null +++ b/mojo/public/java/system/src/org/chromium/mojo/system/ResultAnd.java @@ -0,0 +1,34 @@ +// 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.system; + +/** + * Container that contains a mojo result and a value. + * + * @param <A> the type of the value. + */ +public class ResultAnd<A> { + private final int mMojoResult; + private final A mValue; + + public ResultAnd(int result, A value) { + this.mMojoResult = result; + this.mValue = value; + } + + /** + * Returns the mojo result. + */ + public int getMojoResult() { + return mMojoResult; + } + + /** + * Returns the value. + */ + public A getValue() { + return mValue; + } +} diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/RunLoop.java b/mojo/public/java/system/src/org/chromium/mojo/system/RunLoop.java new file mode 100644 index 0000000000..4038b2954e --- /dev/null +++ b/mojo/public/java/system/src/org/chromium/mojo/system/RunLoop.java @@ -0,0 +1,41 @@ +// 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.system; + +import java.io.Closeable; + +/** + * Definition of a run loop. + */ +public interface RunLoop extends Closeable { + /** + * Start the run loop. It will continue until quit() is called. + */ + public void run(); + + /** + * Start the run loop and stop it as soon as no task is present in the work queue. + */ + public void runUntilIdle(); + + /* + * Quit the currently running run loop. + */ + public void quit(); + + /** + * Add a runnable to the queue of tasks. + * @param runnable Callback to be executed by the run loop. + * @param delay Delay, in MojoTimeTicks (microseconds) before the callback should + * be executed. + */ + public void postDelayedTask(Runnable runnable, long delay); + + /** + * Destroy the run loop and deregister it from Core. + */ + @Override + public abstract void close(); +} diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/SharedBufferHandle.java b/mojo/public/java/system/src/org/chromium/mojo/system/SharedBufferHandle.java new file mode 100644 index 0000000000..df317d134d --- /dev/null +++ b/mojo/public/java/system/src/org/chromium/mojo/system/SharedBufferHandle.java @@ -0,0 +1,160 @@ +// 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.system; + +import java.nio.ByteBuffer; + +/** + * A buffer that can be shared between applications. + */ +public interface SharedBufferHandle extends Handle { + + /** + * Flags for the shared buffer creation operation. + */ + public static class CreateFlags extends Flags<CreateFlags> { + private static final int FLAG_NONE = 0; + + /** + * Immutable flag with not bit set. + */ + public static final CreateFlags NONE = CreateFlags.none().immutable(); + + /** + * Dedicated constructor. + * + * @param flags initial value of the flags. + */ + protected CreateFlags(int flags) { + super(flags); + } + + /** + * @return flags with no bit set. + */ + public static CreateFlags none() { + return new CreateFlags(FLAG_NONE); + } + + } + + /** + * Used to specify creation parameters for a shared buffer to |Core#createSharedBuffer()|. + */ + public static class CreateOptions { + private CreateFlags mFlags = CreateFlags.NONE; + + /** + * @return the flags + */ + public CreateFlags getFlags() { + return mFlags; + } + + } + + /** + * Flags for the shared buffer duplication operation. + */ + public static class DuplicateFlags extends Flags<DuplicateFlags> { + private static final int FLAG_NONE = 0; + + /** + * Immutable flag with not bit set. + */ + public static final DuplicateFlags NONE = DuplicateFlags.none().immutable(); + + /** + * Dedicated constructor. + * + * @param flags initial value of the flags. + */ + protected DuplicateFlags(int flags) { + super(flags); + } + + /** + * @return flags with no bit set. + */ + public static DuplicateFlags none() { + return new DuplicateFlags(FLAG_NONE); + } + + } + + /** + * Used to specify parameters in duplicating access to a shared buffer to + * |SharedBufferHandle#duplicate| + */ + public static class DuplicateOptions { + private DuplicateFlags mFlags = DuplicateFlags.NONE; + + /** + * @return the flags + */ + public DuplicateFlags getFlags() { + return mFlags; + } + + } + + /** + * Flags for the shared buffer map operation. + */ + public static class MapFlags extends Flags<MapFlags> { + private static final int FLAG_NONE = 0; + + /** + * Immutable flag with not bit set. + */ + public static final MapFlags NONE = MapFlags.none().immutable(); + + /** + * Dedicated constructor. + * + * @param flags initial value of the flags. + */ + protected MapFlags(int flags) { + super(flags); + } + + /** + * @return flags with no bit set. + */ + public static MapFlags none() { + return new MapFlags(FLAG_NONE); + } + + } + + /** + * @see org.chromium.mojo.system.Handle#pass() + */ + @Override + public SharedBufferHandle pass(); + + /** + * Duplicates the handle. This creates another handle (returned on success), which can then be + * sent to another application over a message pipe, while retaining access to this handle (and + * any mappings that it may have). + */ + public SharedBufferHandle duplicate(DuplicateOptions options); + + /** + * Map the part (at offset |offset| of length |numBytes|) of the buffer given by this handle + * into memory. |offset + numBytes| must be less than or equal to the size of the buffer. On + * success, the returned buffer points to memory with the requested part of the buffer. A single + * buffer handle may have multiple active mappings (possibly depending on the buffer type). The + * permissions (e.g., writable or executable) of the returned memory may depend on the + * properties of the buffer and properties attached to the buffer handle as well as |flags|. + */ + public ByteBuffer map(long offset, long numBytes, MapFlags flags); + + /** + * Unmap a buffer pointer that was mapped by |map()|. + */ + public void unmap(ByteBuffer buffer); + +} diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/UntypedHandle.java b/mojo/public/java/system/src/org/chromium/mojo/system/UntypedHandle.java new file mode 100644 index 0000000000..199b0a1c04 --- /dev/null +++ b/mojo/public/java/system/src/org/chromium/mojo/system/UntypedHandle.java @@ -0,0 +1,45 @@ +// 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.system; + +import org.chromium.mojo.system.DataPipe.ConsumerHandle; +import org.chromium.mojo.system.DataPipe.ProducerHandle; + +/** + * A mojo handle of unknown type. This handle can be typed by using one of its methods, which will + * return a handle of the requested type and invalidate this object. No validation is made when the + * conversion operation is called. + */ +public interface UntypedHandle extends Handle { + + /** + * @see org.chromium.mojo.system.Handle#pass() + */ + @Override + public UntypedHandle pass(); + + /** + * Returns the underlying handle, as a {@link MessagePipeHandle}, invalidating this + * representation. + */ + public MessagePipeHandle toMessagePipeHandle(); + + /** + * Returns the underlying handle, as a {@link ConsumerHandle}, invalidating this representation. + */ + public ConsumerHandle toDataPipeConsumerHandle(); + + /** + * Returns the underlying handle, as a {@link ProducerHandle}, invalidating this representation. + */ + public ProducerHandle toDataPipeProducerHandle(); + + /** + * Returns the underlying handle, as a {@link SharedBufferHandle}, invalidating this + * representation. + */ + public SharedBufferHandle toSharedBufferHandle(); + +} diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/Watcher.java b/mojo/public/java/system/src/org/chromium/mojo/system/Watcher.java new file mode 100644 index 0000000000..9c70161067 --- /dev/null +++ b/mojo/public/java/system/src/org/chromium/mojo/system/Watcher.java @@ -0,0 +1,38 @@ +// Copyright 2016 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.system; + +import org.chromium.mojo.system.Core.HandleSignals; + +/** + * Watches a handle for signals being satisfied. + */ +public interface Watcher { + /** + * Callback passed to {@link Watcher#start}. + */ + public interface Callback { + /** + * Called when the handle is ready. + */ + public void onResult(int result); + } + + /** + * Starts watching a handle. + */ + int start(Handle handle, HandleSignals signals, Callback callback); + + /** + * Cancels an already-started watch. + */ + void cancel(); + + /** + * Destroys the underlying implementation. Other methods will fail after destroy has been + * called. + */ + void destroy(); +} |