Commit 063e1e3a authored by qsr's avatar qsr Committed by Commit bot

mojo: generate Proxies and Stubs for java bindings.

This CL allows to generate Stub and Proxy of mojo service in java. This
allows to call interfaces method through message pipes.

Committed: https://chromium.googlesource.com/chromium/src/+/b2ef91ee4abe584e712db7a51d47a1b6d9a96e57

Review URL: https://codereview.chromium.org/411913002

Cr-Commit-Position: refs/heads/master@{#292154}
parent fbcfb844
...@@ -28,7 +28,7 @@ def print_landmines(): ...@@ -28,7 +28,7 @@ def print_landmines():
builder() == 'ninja'): builder() == 'ninja'):
print 'Need to clobber winja goma due to backend cwd cache fix.' print 'Need to clobber winja goma due to backend cwd cache fix.'
if platform() == 'android': if platform() == 'android':
print 'Clobber: To delete generated class files (we just use jars now).' print 'Clobber: To delete generated mojo class files.'
if platform() == 'win' and builder() == 'ninja': if platform() == 'win' and builder() == 'ninja':
print 'Compile on cc_unittests fails due to symbols removed in r185063.' print 'Compile on cc_unittests fails due to symbols removed in r185063.'
if platform() == 'linux' and builder() == 'ninja': if platform() == 'linux' and builder() == 'ninja':
......
...@@ -21,7 +21,8 @@ public class BindingsTestUtils { ...@@ -21,7 +21,8 @@ public class BindingsTestUtils {
/** /**
* {@link MessageReceiver} that records any message it receives. * {@link MessageReceiver} that records any message it receives.
*/ */
public static class RecordingMessageReceiver implements MessageReceiver { public static class RecordingMessageReceiver extends SideEffectFreeCloseable
implements MessageReceiver {
public final List<MessageWithHeader> messages = new ArrayList<MessageWithHeader>(); public final List<MessageWithHeader> messages = new ArrayList<MessageWithHeader>();
...@@ -60,15 +61,23 @@ public class BindingsTestUtils { ...@@ -60,15 +61,23 @@ public class BindingsTestUtils {
*/ */
public static class CapturingErrorHandler implements ConnectionErrorHandler { public static class CapturingErrorHandler implements ConnectionErrorHandler {
public MojoException exception = null; private MojoException mLastMojoException = null;
/** /**
* @see ConnectionErrorHandler#onConnectionError(MojoException) * @see ConnectionErrorHandler#onConnectionError(MojoException)
*/ */
@Override @Override
public void onConnectionError(MojoException e) { public void onConnectionError(MojoException e) {
exception = e; mLastMojoException = e;
} }
/**
* Returns the last recorded exception.
*/
public MojoException getLastMojoException() {
return mLastMojoException;
}
} }
/** /**
......
...@@ -51,7 +51,7 @@ public class ConnectorTest extends MojoTestCase { ...@@ -51,7 +51,7 @@ public class ConnectorTest extends MojoTestCase {
mConnector.setErrorHandler(mErrorHandler); mConnector.setErrorHandler(mErrorHandler);
mConnector.start(); mConnector.start();
mTestMessage = BindingsTestUtils.newRandomMessageWithHeader(DATA_LENGTH); mTestMessage = BindingsTestUtils.newRandomMessageWithHeader(DATA_LENGTH);
assertNull(mErrorHandler.exception); assertNull(mErrorHandler.getLastMojoException());
assertEquals(0, mReceiver.messages.size()); assertEquals(0, mReceiver.messages.size());
} }
...@@ -71,7 +71,7 @@ public class ConnectorTest extends MojoTestCase { ...@@ -71,7 +71,7 @@ public class ConnectorTest extends MojoTestCase {
@SmallTest @SmallTest
public void testSendingMessage() { public void testSendingMessage() {
mConnector.accept(mTestMessage); mConnector.accept(mTestMessage);
assertNull(mErrorHandler.exception); assertNull(mErrorHandler.getLastMojoException());
ByteBuffer received = ByteBuffer.allocateDirect(DATA_LENGTH); ByteBuffer received = ByteBuffer.allocateDirect(DATA_LENGTH);
MessagePipeHandle.ReadMessageResult result = mHandle.readMessage(received, 0, MessagePipeHandle.ReadMessageResult result = mHandle.readMessage(received, 0,
MessagePipeHandle.ReadFlags.NONE); MessagePipeHandle.ReadFlags.NONE);
...@@ -88,7 +88,7 @@ public class ConnectorTest extends MojoTestCase { ...@@ -88,7 +88,7 @@ public class ConnectorTest extends MojoTestCase {
mHandle.writeMessage(mTestMessage.getMessage().buffer, new ArrayList<Handle>(), mHandle.writeMessage(mTestMessage.getMessage().buffer, new ArrayList<Handle>(),
MessagePipeHandle.WriteFlags.NONE); MessagePipeHandle.WriteFlags.NONE);
nativeRunLoop(RUN_LOOP_TIMEOUT_MS); nativeRunLoop(RUN_LOOP_TIMEOUT_MS);
assertNull(mErrorHandler.exception); assertNull(mErrorHandler.getLastMojoException());
assertEquals(1, mReceiver.messages.size()); assertEquals(1, mReceiver.messages.size());
MessageWithHeader received = mReceiver.messages.get(0); MessageWithHeader received = mReceiver.messages.get(0);
assertEquals(0, received.getMessage().handles.size()); assertEquals(0, received.getMessage().handles.size());
...@@ -102,7 +102,8 @@ public class ConnectorTest extends MojoTestCase { ...@@ -102,7 +102,8 @@ public class ConnectorTest extends MojoTestCase {
public void testErrors() { public void testErrors() {
mHandle.close(); mHandle.close();
nativeRunLoop(RUN_LOOP_TIMEOUT_MS); nativeRunLoop(RUN_LOOP_TIMEOUT_MS);
assertNotNull(mErrorHandler.exception); assertNotNull(mErrorHandler.getLastMojoException());
assertEquals(MojoResult.FAILED_PRECONDITION, mErrorHandler.exception.getMojoResult()); assertEquals(MojoResult.FAILED_PRECONDITION,
mErrorHandler.getLastMojoException().getMojoResult());
} }
} }
...@@ -23,27 +23,29 @@ android_library("system") { ...@@ -23,27 +23,29 @@ android_library("system") {
android_library("bindings") { android_library("bindings") {
java_files = [ java_files = [
"bindings/src/org/chromium/mojo/bindings/InterfaceRequest.java", "bindings/src/org/chromium/mojo/bindings/AutoCloseableRouter.java",
"bindings/src/org/chromium/mojo/bindings/Interface.java", "bindings/src/org/chromium/mojo/bindings/BindingsHelper.java",
"bindings/src/org/chromium/mojo/bindings/Struct.java", "bindings/src/org/chromium/mojo/bindings/Callbacks.java",
"bindings/src/org/chromium/mojo/bindings/ConnectionErrorHandler.java", "bindings/src/org/chromium/mojo/bindings/ConnectionErrorHandler.java",
"bindings/src/org/chromium/mojo/bindings/DeserializationException.java",
"bindings/src/org/chromium/mojo/bindings/Connector.java", "bindings/src/org/chromium/mojo/bindings/Connector.java",
"bindings/src/org/chromium/mojo/bindings/MessageHeader.java",
"bindings/src/org/chromium/mojo/bindings/MessageReceiverWithResponder.java",
"bindings/src/org/chromium/mojo/bindings/BindingsHelper.java",
"bindings/src/org/chromium/mojo/bindings/Router.java",
"bindings/src/org/chromium/mojo/bindings/Decoder.java", "bindings/src/org/chromium/mojo/bindings/Decoder.java",
"bindings/src/org/chromium/mojo/bindings/Message.java", "bindings/src/org/chromium/mojo/bindings/DelegatingConnectionErrorHandler.java",
"bindings/src/org/chromium/mojo/bindings/RouterImpl.java", "bindings/src/org/chromium/mojo/bindings/DeserializationException.java",
"bindings/src/org/chromium/mojo/bindings/Encoder.java", "bindings/src/org/chromium/mojo/bindings/Encoder.java",
"bindings/src/org/chromium/mojo/bindings/InterfaceWithClient.java",
"bindings/src/org/chromium/mojo/bindings/ExecutorFactory.java", "bindings/src/org/chromium/mojo/bindings/ExecutorFactory.java",
"bindings/src/org/chromium/mojo/bindings/AutoCloseableRouter.java",
"bindings/src/org/chromium/mojo/bindings/MessageWithHeader.java",
"bindings/src/org/chromium/mojo/bindings/Callbacks.java",
"bindings/src/org/chromium/mojo/bindings/HandleOwner.java", "bindings/src/org/chromium/mojo/bindings/HandleOwner.java",
"bindings/src/org/chromium/mojo/bindings/Interface.java",
"bindings/src/org/chromium/mojo/bindings/InterfaceRequest.java",
"bindings/src/org/chromium/mojo/bindings/InterfaceWithClient.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/MessageReceiver.java",
"bindings/src/org/chromium/mojo/bindings/MessageReceiverWithResponder.java",
"bindings/src/org/chromium/mojo/bindings/MessageWithHeader.java",
"bindings/src/org/chromium/mojo/bindings/RouterImpl.java",
"bindings/src/org/chromium/mojo/bindings/Router.java",
"bindings/src/org/chromium/mojo/bindings/SideEffectFreeCloseable.java",
"bindings/src/org/chromium/mojo/bindings/Struct.java",
] ]
deps = [ ":system" ] deps = [ ":system" ]
......
...@@ -115,7 +115,11 @@ public class Connector implements MessageReceiver, HandleOwner<MessagePipeHandle ...@@ -115,7 +115,11 @@ public class Connector implements MessageReceiver, HandleOwner<MessagePipeHandle
@Override @Override
public MessagePipeHandle passHandle() { public MessagePipeHandle passHandle() {
cancelIfActive(); cancelIfActive();
return mMessagePipeHandle.pass(); MessagePipeHandle handle = mMessagePipeHandle.pass();
if (mIncomingMessageReceiver != null) {
mIncomingMessageReceiver.close();
}
return handle;
} }
/** /**
...@@ -125,6 +129,9 @@ public class Connector implements MessageReceiver, HandleOwner<MessagePipeHandle ...@@ -125,6 +129,9 @@ public class Connector implements MessageReceiver, HandleOwner<MessagePipeHandle
public void close() { public void close() {
cancelIfActive(); cancelIfActive();
mMessagePipeHandle.close(); mMessagePipeHandle.close();
if (mIncomingMessageReceiver != null) {
mIncomingMessageReceiver.close();
}
} }
private class AsyncWaiterCallback implements AsyncWaiter.Callback { private class AsyncWaiterCallback implements AsyncWaiter.Callback {
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
package org.chromium.mojo.bindings; package org.chromium.mojo.bindings;
import org.chromium.mojo.bindings.Interface.Proxy;
import org.chromium.mojo.bindings.Struct.DataHeader; import org.chromium.mojo.bindings.Struct.DataHeader;
import org.chromium.mojo.system.DataPipe; import org.chromium.mojo.system.DataPipe;
import org.chromium.mojo.system.Handle; import org.chromium.mojo.system.Handle;
...@@ -40,11 +41,17 @@ public class Decoder { ...@@ -40,11 +41,17 @@ public class Decoder {
*/ */
private final long mMaxMemory; private final long mMaxMemory;
/**
* The number of handles in the message.
*/
private final long mNumberOfHandles;
/** /**
* Constructor. * Constructor.
*/ */
Validator(long maxMemory) { Validator(long maxMemory, int numberOfHandles) {
mMaxMemory = maxMemory; mMaxMemory = maxMemory;
mNumberOfHandles = numberOfHandles;
} }
public void claimHandle(int handle) { public void claimHandle(int handle) {
...@@ -52,6 +59,9 @@ public class Decoder { ...@@ -52,6 +59,9 @@ public class Decoder {
throw new DeserializationException( throw new DeserializationException(
"Trying to access handle out of order."); "Trying to access handle out of order.");
} }
if (handle >= mNumberOfHandles) {
throw new DeserializationException("Trying to access non present handle.");
}
mMinNextClaimedHandle = handle + 1; mMinNextClaimedHandle = handle + 1;
} }
...@@ -93,7 +103,7 @@ public class Decoder { ...@@ -93,7 +103,7 @@ public class Decoder {
* @param message The message to decode. * @param message The message to decode.
*/ */
public Decoder(Message message) { public Decoder(Message message) {
this(message, new Validator(message.buffer.limit()), 0); this(message, new Validator(message.buffer.limit(), message.handles.size()), 0);
} }
private Decoder(Message message, Validator validator, int baseOffset) { private Decoder(Message message, Validator validator, int baseOffset) {
...@@ -319,44 +329,53 @@ public class Decoder { ...@@ -319,44 +329,53 @@ public class Decoder {
* Deserializes a |ConsumerHandle| at the given offset. * Deserializes a |ConsumerHandle| at the given offset.
*/ */
public DataPipe.ConsumerHandle readConsumerHandle(int offset) { public DataPipe.ConsumerHandle readConsumerHandle(int offset) {
return readHandle(offset).toUntypedHandle().toDataPipeConsumerHandle(); return readUntypedHandle(offset).toDataPipeConsumerHandle();
} }
/** /**
* Deserializes a |ProducerHandle| at the given offset. * Deserializes a |ProducerHandle| at the given offset.
*/ */
public DataPipe.ProducerHandle readProducerHandle(int offset) { public DataPipe.ProducerHandle readProducerHandle(int offset) {
return readHandle(offset).toUntypedHandle().toDataPipeProducerHandle(); return readUntypedHandle(offset).toDataPipeProducerHandle();
} }
/** /**
* Deserializes a |MessagePipeHandle| at the given offset. * Deserializes a |MessagePipeHandle| at the given offset.
*/ */
public MessagePipeHandle readMessagePipeHandle(int offset) { public MessagePipeHandle readMessagePipeHandle(int offset) {
return readHandle(offset).toUntypedHandle().toMessagePipeHandle(); return readUntypedHandle(offset).toMessagePipeHandle();
} }
/** /**
* Deserializes a |SharedBufferHandle| at the given offset. * Deserializes a |SharedBufferHandle| at the given offset.
*/ */
public SharedBufferHandle readSharedBufferHandle(int offset) { public SharedBufferHandle readSharedBufferHandle(int offset) {
return readHandle(offset).toUntypedHandle().toSharedBufferHandle(); return readUntypedHandle(offset).toSharedBufferHandle();
} }
/** /**
* Deserializes a |ServiceHandle| at the given offset. * Deserializes an interface at the given offset.
*
* @return a proxy to the service.
*/ */
public <S extends Interface> S readServiceInterface(int offset, Object builder) { public <P extends Proxy> P readServiceInterface(int offset,
// TODO(qsr): To be implemented when interfaces proxy and stubs are implemented. Interface.Manager<?, P> manager) {
throw new UnsupportedOperationException("Unimplemented operation"); MessagePipeHandle handle = readMessagePipeHandle(offset);
if (!handle.isValid()) {
return null;
}
return manager.attachProxy(handle);
} }
/** /**
* Deserializes a |InterfaceRequest| at the given offset. * Deserializes a |InterfaceRequest| at the given offset.
*/ */
public <S extends Interface> InterfaceRequest<S> readInterfaceRequest(int offset) { public <I extends Interface> InterfaceRequest<I> readInterfaceRequest(int offset) {
// TODO(qsr): To be implemented when interfaces proxy and stubs are implemented. MessagePipeHandle handle = readMessagePipeHandle(offset);
throw new UnsupportedOperationException("Unimplemented operation"); if (handle == null) {
return null;
}
return new InterfaceRequest<I>(handle);
} }
/** /**
...@@ -387,6 +406,23 @@ public class Decoder { ...@@ -387,6 +406,23 @@ public class Decoder {
return result; return result;
} }
/**
* Deserializes an array of |UntypedHandle| at the given offset.
*/
public UntypedHandle[] readUntypedHandles(int offset) {
Decoder d = readPointer(offset);
if (d == null) {
return null;
}
DataHeader si = d.readDataHeader();
UntypedHandle[] result = new UntypedHandle[si.numFields];
for (int i = 0; i < result.length; ++i) {
result[i] = d.readUntypedHandle(
DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_HANDLE_SIZE * i);
}
return result;
}
/** /**
* Deserializes an array of |ConsumerHandle| at the given offset. * Deserializes an array of |ConsumerHandle| at the given offset.
*/ */
...@@ -461,22 +497,36 @@ public class Decoder { ...@@ -461,22 +497,36 @@ public class Decoder {
/** /**
* Deserializes an array of |ServiceHandle| at the given offset. * Deserializes an array of |ServiceHandle| at the given offset.
*/ */
public <S extends Interface> S[] readServiceInterfaces(int offset, Object builder) { public <S extends Interface, P extends Proxy> S[] readServiceInterfaces(int offset,
// TODO(qsr): To be implemented when interfaces proxy and stubs are implemented. Interface.Manager<S, P> manager) {
throw new UnsupportedOperationException("Unimplemented operation"); Decoder d = readPointer(offset);
if (d == null) {
return null;
}
DataHeader si = d.readDataHeader();
S[] result = manager.buildArray(si.numFields);
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_HANDLE_SIZE * i, manager);
result[i] = value;
}
return result;
} }
/** /**
* Deserializes an array of |InterfaceRequest| at the given offset. * Deserializes an array of |InterfaceRequest| at the given offset.
*/ */
public <S extends Interface> InterfaceRequest<S>[] readInterfaceRequests(int offset) { public <I extends Interface> InterfaceRequest<I>[] readInterfaceRequests(int offset) {
Decoder d = readPointer(offset); Decoder d = readPointer(offset);
if (d == null) { if (d == null) {
return null; return null;
} }
DataHeader si = d.readDataHeader(); DataHeader si = d.readDataHeader();
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
InterfaceRequest<S>[] result = new InterfaceRequest[si.numFields]; InterfaceRequest<I>[] result = new InterfaceRequest[si.numFields];
for (int i = 0; i < result.length; ++i) { for (int i = 0; i < result.length; ++i) {
result[i] = d.readInterfaceRequest( result[i] = d.readInterfaceRequest(
DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_HANDLE_SIZE * i); DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_HANDLE_SIZE * i);
......
// 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);
}
}
...@@ -7,6 +7,8 @@ package org.chromium.mojo.bindings; ...@@ -7,6 +7,8 @@ package org.chromium.mojo.bindings;
import org.chromium.mojo.bindings.Struct.DataHeader; import org.chromium.mojo.bindings.Struct.DataHeader;
import org.chromium.mojo.system.Core; import org.chromium.mojo.system.Core;
import org.chromium.mojo.system.Handle; 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.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
...@@ -139,7 +141,6 @@ public class Encoder { ...@@ -139,7 +141,6 @@ public class Encoder {
Encoder result = new Encoder(mEncoderState); Encoder result = new Encoder(mEncoderState);
result.encode(dataHeader); result.encode(dataHeader);
return result; return result;
} }
/** /**
...@@ -243,25 +244,43 @@ public class Encoder { ...@@ -243,25 +244,43 @@ public class Encoder {
/** /**
* Encode an {@link Interface}. * Encode an {@link Interface}.
*/ */
public <T extends Interface> void encode(T v, int offset, Object builder) { public <T extends Interface> void encode(T v, int offset, Interface.Manager<T, ?> manager) {
if (v == null) {
encode(-1, offset);
return;
}
if (mEncoderState.core == null) { if (mEncoderState.core == null) {
throw new UnsupportedOperationException( throw new UnsupportedOperationException(
"The encoder has been created without a Core. It can't encode an interface."); "The encoder has been created without a Core. It can't encode an interface.");
} }
// TODO(qsr): To be implemented when interfaces proxy and stubs are implemented. // If the instance is a proxy, pass the proxy's handle instead of creating a new stub.
throw new UnsupportedOperationException("Unimplemented operation"); if (v instanceof Interface.AbstractProxy) {
Interface.AbstractProxy proxy = (Interface.AbstractProxy) v;
if (proxy.getMessageReceiver() instanceof HandleOwner) {
encode(((HandleOwner<?>) proxy.getMessageReceiver()).passHandle(), offset);
return;
}
// If the proxy is not over a message pipe, the default case applies.
}
Pair<MessagePipeHandle, MessagePipeHandle> handles =
mEncoderState.core.createMessagePipe(null);
manager.bind(v, handles.first);
encode(handles.second, offset);
} }
/** /**
* Encode an {@link InterfaceRequest}. * Encode an {@link InterfaceRequest}.
*/ */
public <T extends Interface> void encode(InterfaceRequest<T> v, int offset) { public <I extends Interface> void encode(InterfaceRequest<I> v, int offset) {
if (v == null) {
encode(-1, offset);
return;
}
if (mEncoderState.core == null) { if (mEncoderState.core == null) {
throw new UnsupportedOperationException( throw new UnsupportedOperationException(
"The encoder has been created without a Core. It can't encode an interface."); "The encoder has been created without a Core. It can't encode an interface.");
} }
// TODO(qsr): To be implemented when interfaces proxy and stubs are implemented. encode(v.passHandle(), offset);
throw new UnsupportedOperationException("Unimplemented operation");
} }
/** /**
...@@ -374,7 +393,8 @@ public class Encoder { ...@@ -374,7 +393,8 @@ public class Encoder {
/** /**
* Encodes an array of {@link Interface}. * Encodes an array of {@link Interface}.
*/ */
public <T extends Interface> void encode(T[] v, int offset, Object builder) { public <T extends Interface> void encode(T[] v, int offset,
Interface.Manager<T, ?> manager) {
if (v == null) { if (v == null) {
encodeNullPointer(offset); encodeNullPointer(offset);
return; return;
...@@ -382,14 +402,14 @@ public class Encoder { ...@@ -382,14 +402,14 @@ public class Encoder {
Encoder e = encoderForArray(BindingsHelper.SERIALIZED_HANDLE_SIZE, v.length, offset); Encoder e = encoderForArray(BindingsHelper.SERIALIZED_HANDLE_SIZE, v.length, offset);
for (int i = 0; i < v.length; ++i) { for (int i = 0; i < v.length; ++i) {
e.encode(v[i], DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_HANDLE_SIZE * i, e.encode(v[i], DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_HANDLE_SIZE * i,
builder); manager);
} }
} }
/** /**
* Encodes an array of {@link Interface}. * Encodes an array of {@link InterfaceRequest}.
*/ */
public <T extends Interface> void encode(InterfaceRequest<T>[] v, int offset) { public <I extends Interface> void encode(InterfaceRequest<I>[] v, int offset) {
if (v == null) { if (v == null) {
encodeNullPointer(offset); encodeNullPointer(offset);
return; return;
......
...@@ -19,4 +19,11 @@ public interface HandleOwner<H extends Handle> extends Closeable { ...@@ -19,4 +19,11 @@ public interface HandleOwner<H extends Handle> extends Closeable {
* Pass the handle owned by this class. * Pass the handle owned by this class.
*/ */
public H passHandle(); public H passHandle();
/**
* @see java.io.Closeable#close()
*/
@Override
public void close();
} }
...@@ -4,9 +4,248 @@ ...@@ -4,9 +4,248 @@
package org.chromium.mojo.bindings; package org.chromium.mojo.bindings;
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. * Base class for mojo generated interfaces.
*/ */
public interface Interface { 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 {
/**
* Set the {@link ConnectionErrorHandler} that will be notified of errors.
*/
public void setErrorHandler(ConnectionErrorHandler errorHandler);
}
/**
* Base implementation of {@link Proxy}.
*/
abstract class AbstractProxy implements Proxy {
/**
* 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 = null;
/**
* 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 AbstractProxy(Core core, MessageReceiverWithResponder messageReceiver) {
this.mCore = core;
this.mMessageReceiver = messageReceiver;
}
/**
* Returns the message receiver to send message to.
*/
protected MessageReceiverWithResponder getMessageReceiver() {
return mMessageReceiver;
}
/**
* Returns the Core implementation.
*/
protected Core getCore() {
return mCore;
}
/**
* @see Proxy#setErrorHandler(ConnectionErrorHandler)
*/
@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();
}
}
/**
* 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> {
/**
* 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 binded to an implementation of the interface.
*/
public final P attachProxy(MessagePipeHandle handle) {
RouterImpl router = new RouterImpl(handle);
P proxy = attachProxy(handle.getCore(), router);
DelegatingConnectionErrorHandler handlers = new DelegatingConnectionErrorHandler();
handlers.addConnectionErrorHandler(proxy);
router.setErrorHandler(handlers);
router.start();
return proxy;
}
/**
* Construct 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);
return Pair.create(proxy, new InterfaceRequest<I>(handles.second));
}
/**
* 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);
}
} }
...@@ -4,12 +4,47 @@ ...@@ -4,12 +4,47 @@
package org.chromium.mojo.bindings; package org.chromium.mojo.bindings;
import org.chromium.mojo.system.MessagePipeHandle;
/** /**
* Used in methods that return instances of remote objects. Allows to start using the remote object * One end of the message pipe representing a request to create an implementation to be bound to it.
* immediately, while sending the handle that will be bind to the implementation. * 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 <I> the type of the remote interface. * @param <P> the type of the remote interface proxy.
*/ */
public final class InterfaceRequest<I extends Interface> { 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();
}
} }
...@@ -4,16 +4,98 @@ ...@@ -4,16 +4,98 @@
package org.chromium.mojo.bindings; package org.chromium.mojo.bindings;
import org.chromium.mojo.system.Core;
import org.chromium.mojo.system.MessagePipeHandle;
/** /**
* Base class for mojo generated interfaces that have a client. * Base class for mojo generated interfaces that have a client.
* *
* @param <C> the type of the client interface. * @param <CI> the type of the client interface.
*/ */
public interface InterfaceWithClient<C extends Interface> extends Interface { public interface InterfaceWithClient<CI extends Interface> extends Interface {
/**
* Proxy class for interfaces with a client.
*/
interface Proxy<CI extends Interface> extends Interface.Proxy, InterfaceWithClient<CI> {
}
/**
* Base implementation of Proxy.
*
* @param <CI> the type of the client interface.
*/
abstract class AbstractProxy<CI extends Interface> extends Interface.AbstractProxy
implements Proxy<CI> {
/**
* Constructor.
*
* @param core the Core implementation used to create pipes and access the async waiter.
* @param messageReceiver the message receiver to send message to.
*/
public AbstractProxy(Core core, MessageReceiverWithResponder messageReceiver) {
super(core, messageReceiver);
}
/**
* @see InterfaceWithClient#setClient(Interface)
*/
@Override
public void setClient(CI client) {
throw new UnsupportedOperationException(
"Setting the client on a proxy is not supported");
}
}
/** /**
* Set the client associated with this interface. * Base manager implementation for interfaces that have a client.
*
* @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.
* @param <CI> the type of the client interface.
*/ */
public void setClient(C client); abstract class Manager<I extends InterfaceWithClient<CI>, P extends Proxy<CI>,
CI extends Interface> extends Interface.Manager<I, P> {
/**
* @see Interface.Manager#bind(Interface, MessagePipeHandle)
*/
@Override
public final void bind(I impl, MessagePipeHandle handle) {
Router router = new RouterImpl(handle);
super.bind(handle.getCore(), impl, router);
@SuppressWarnings("unchecked")
CI client = (CI) getClientManager().attachProxy(handle.getCore(), router);
impl.setClient(client);
router.start();
}
/**
* Returns a Proxy that will send messages to the given |handle|. This implies that the
* other end of the handle must be connected to an implementation of the interface. |client|
* is the implementation of the client interface.
*/
public P attachProxy(MessagePipeHandle handle, CI client) {
Router router = new RouterImpl(handle);
DelegatingConnectionErrorHandler handlers = new DelegatingConnectionErrorHandler();
handlers.addConnectionErrorHandler(client);
router.setErrorHandler(handlers);
getClientManager().bind(handle.getCore(), client, router);
P proxy = super.attachProxy(handle.getCore(), router);
handlers.addConnectionErrorHandler(proxy);
router.start();
return proxy;
}
/**
* Returns a manager for the client inetrafce.
*/
protected abstract Interface.Manager<CI, ?> getClientManager();
}
/**
* Set the client implementation for this interface.
*/
public void setClient(CI client);
} }
...@@ -27,6 +27,11 @@ public class MessageHeader { ...@@ -27,6 +27,11 @@ public class MessageHeader {
private static final int FLAGS_OFFSET = 12; private static final int FLAGS_OFFSET = 12;
private static final int REQUEST_ID_OFFSET = 16; private static final int REQUEST_ID_OFFSET = 16;
/**
* 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. * Flag for a header of a message that expected a response.
*/ */
......
...@@ -4,14 +4,22 @@ ...@@ -4,14 +4,22 @@
package org.chromium.mojo.bindings; package org.chromium.mojo.bindings;
import java.io.Closeable;
/** /**
* A class which implements this interface can receive {@link Message} objects. * A class which implements this interface can receive {@link Message} objects.
*/ */
public interface MessageReceiver { public interface MessageReceiver extends Closeable {
/** /**
* Receive a {@link MessageWithHeader}. The {@link MessageReceiver} is allowed to mutable the * Receive a {@link MessageWithHeader}. The {@link MessageReceiver} is allowed to mutable the
* message. Returns |true| if the message has been handled, |false| otherwise. * message. Returns |true| if the message has been handled, |false| otherwise.
*/ */
boolean accept(MessageWithHeader message); boolean accept(MessageWithHeader message);
/**
* @see java.io.Closeable#close()
*/
@Override
public void close();
} }
...@@ -28,10 +28,4 @@ public interface Router extends MessageReceiverWithResponder, HandleOwner<Messag ...@@ -28,10 +28,4 @@ public interface Router extends MessageReceiverWithResponder, HandleOwner<Messag
* Set the handle that will be notified of errors on the message pipe. * Set the handle that will be notified of errors on the message pipe.
*/ */
public void setErrorHandler(ConnectionErrorHandler errorHandler); public void setErrorHandler(ConnectionErrorHandler errorHandler);
/**
* @see java.io.Closeable#close()
*/
@Override
public void close();
} }
...@@ -28,6 +28,14 @@ public class RouterImpl implements Router { ...@@ -28,6 +28,14 @@ public class RouterImpl implements Router {
return handleIncomingMessage(message); return handleIncomingMessage(message);
} }
/**
* @see org.chromium.mojo.bindings.MessageReceiver#close()
*/
@Override
public void close() {
handleConnectorClose();
}
} }
/** /**
...@@ -175,4 +183,10 @@ public class RouterImpl implements Router { ...@@ -175,4 +183,10 @@ public class RouterImpl implements Router {
} }
return false; return false;
} }
private void handleConnectorClose() {
if (mIncomingMessageReceiver != null) {
mIncomingMessageReceiver.close();
}
}
} }
// 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() {
}
}
{% from "interface_definition.tmpl" import interface_internal_def %}
{% include "header.java.tmpl" %}
{{ interface_internal_def(interface, client) }}
...@@ -153,9 +153,9 @@ def DecodeMethod(context, kind, offset, bit): ...@@ -153,9 +153,9 @@ def DecodeMethod(context, kind, offset, bit):
if (kind == mojom.BOOL): if (kind == mojom.BOOL):
additionalParams = ', %d' % bit additionalParams = ', %d' % bit
if mojom.IsInterfaceKind(kind): if mojom.IsInterfaceKind(kind):
additionalParams = ', %s.BUILDER' % GetJavaType(context, kind) additionalParams = ', %s.MANAGER' % GetJavaType(context, kind)
if mojom.IsAnyArrayKind(kind) and mojom.IsInterfaceKind(kind.kind): if mojom.IsAnyArrayKind(kind) and mojom.IsInterfaceKind(kind.kind):
additionalParams = ', %s.BUILDER' % GetJavaType(context, kind.kind) additionalParams = ', %s.MANAGER' % GetJavaType(context, kind.kind)
return '%s(%s%s)' % (methodName, offset, additionalParams) return '%s(%s%s)' % (methodName, offset, additionalParams)
@contextfilter @contextfilter
...@@ -164,9 +164,9 @@ def EncodeMethod(context, kind, variable, offset, bit): ...@@ -164,9 +164,9 @@ def EncodeMethod(context, kind, variable, offset, bit):
if (kind == mojom.BOOL): if (kind == mojom.BOOL):
additionalParams = ', %d' % bit additionalParams = ', %d' % bit
if mojom.IsInterfaceKind(kind): if mojom.IsInterfaceKind(kind):
additionalParams = ', %s.BUILDER' % GetJavaType(context, kind) additionalParams = ', %s.MANAGER' % GetJavaType(context, kind)
if mojom.IsAnyArrayKind(kind) and mojom.IsInterfaceKind(kind.kind): if mojom.IsAnyArrayKind(kind) and mojom.IsInterfaceKind(kind.kind):
additionalParams = ', %s.BUILDER' % GetJavaType(context, kind.kind) additionalParams = ', %s.MANAGER' % GetJavaType(context, kind.kind)
return 'encode(%s, %s%s)' % (variable, offset, additionalParams) return 'encode(%s, %s%s)' % (variable, offset, additionalParams)
def GetPackage(module): def GetPackage(module):
...@@ -281,6 +281,14 @@ def IsPointerArrayKind(kind): ...@@ -281,6 +281,14 @@ def IsPointerArrayKind(kind):
sub_kind = kind.kind sub_kind = kind.kind
return mojom.IsObjectKind(sub_kind) return mojom.IsObjectKind(sub_kind)
def GetResponseStructFromMethod(method):
return generator.GetDataHeader(
False, generator.GetResponseStructFromMethod(method))
def GetStructFromMethod(method):
return generator.GetDataHeader(
False, generator.GetStructFromMethod(method))
def GetConstantsMainEntityName(module): def GetConstantsMainEntityName(module):
if 'JavaConstantsClassName' in module.attributes: if 'JavaConstantsClassName' in module.attributes:
return ParseStringAttribute(module.attributes['JavaConstantsClassName']) return ParseStringAttribute(module.attributes['JavaConstantsClassName'])
...@@ -289,6 +297,21 @@ def GetConstantsMainEntityName(module): ...@@ -289,6 +297,21 @@ def GetConstantsMainEntityName(module):
return (UpperCamelCase(module.path.split('/')[-1].rsplit('.', 1)[0]) + return (UpperCamelCase(module.path.split('/')[-1].rsplit('.', 1)[0]) +
'Constants') 'Constants')
def GetMethodOrdinalName(method):
return ConstantStyle(method.name) + "_ORDINAL"
def HasMethodWithResponse(interface):
for method in interface.methods:
if method.response_parameters:
return True
return False
def HasMethodWithoutResponse(interface):
for method in interface.methods:
if not method.response_parameters:
return True
return False
class Generator(generator.Generator): class Generator(generator.Generator):
java_filters = { java_filters = {
...@@ -298,12 +321,17 @@ class Generator(generator.Generator): ...@@ -298,12 +321,17 @@ class Generator(generator.Generator):
"decode_method": DecodeMethod, "decode_method": DecodeMethod,
"expression_to_text": ExpressionToText, "expression_to_text": ExpressionToText,
"encode_method": EncodeMethod, "encode_method": EncodeMethod,
"has_method_with_response": HasMethodWithResponse,
"has_method_without_response": HasMethodWithoutResponse,
"is_handle": mojom.IsNonInterfaceHandleKind, "is_handle": mojom.IsNonInterfaceHandleKind,
"is_pointer_array_kind": IsPointerArrayKind, "is_pointer_array_kind": IsPointerArrayKind,
"is_struct_kind": mojom.IsStructKind, "is_struct_kind": mojom.IsStructKind,
"java_type": GetJavaType, "java_type": GetJavaType,
"method_ordinal_name": GetMethodOrdinalName,
"name": GetNameForElement, "name": GetNameForElement,
"new_array": NewArray, "new_array": NewArray,
"response_struct_from_method": GetResponseStructFromMethod,
"struct_from_method": GetStructFromMethod,
"struct_size": lambda ps: ps.GetTotalSize() + _HEADER_SIZE, "struct_size": lambda ps: ps.GetTotalSize() + _HEADER_SIZE,
} }
...@@ -313,6 +341,15 @@ class Generator(generator.Generator): ...@@ -313,6 +341,15 @@ class Generator(generator.Generator):
"package": GetPackage(self.module), "package": GetPackage(self.module),
} }
def GetJinjaExportsForInterface(self, interface):
exports = self.GetJinjaExports()
exports.update({"interface": interface})
if interface.client:
for client in self.module.interfaces:
if client.name == interface.client:
exports.update({"client": client})
return exports
@UseJinja("java_templates/enum.java.tmpl", filters=java_filters) @UseJinja("java_templates/enum.java.tmpl", filters=java_filters)
def GenerateEnumSource(self, enum): def GenerateEnumSource(self, enum):
exports = self.GetJinjaExports() exports = self.GetJinjaExports()
...@@ -327,13 +364,11 @@ class Generator(generator.Generator): ...@@ -327,13 +364,11 @@ class Generator(generator.Generator):
@UseJinja("java_templates/interface.java.tmpl", filters=java_filters) @UseJinja("java_templates/interface.java.tmpl", filters=java_filters)
def GenerateInterfaceSource(self, interface): def GenerateInterfaceSource(self, interface):
exports = self.GetJinjaExports() return self.GetJinjaExportsForInterface(interface)
exports.update({"interface": interface})
if interface.client: @UseJinja("java_templates/interface_internal.java.tmpl", filters=java_filters)
for client in self.module.interfaces: def GenerateInterfaceInternalSource(self, interface):
if client.name == interface.client: return self.GetJinjaExportsForInterface(interface)
exports.update({"client": client})
return exports
@UseJinja("java_templates/constants.java.tmpl", filters=java_filters) @UseJinja("java_templates/constants.java.tmpl", filters=java_filters)
def GenerateConstantsSource(self, module): def GenerateConstantsSource(self, module):
...@@ -367,6 +402,8 @@ class Generator(generator.Generator): ...@@ -367,6 +402,8 @@ class Generator(generator.Generator):
for interface in self.module.interfaces: for interface in self.module.interfaces:
self.Write(self.GenerateInterfaceSource(interface), self.Write(self.GenerateInterfaceSource(interface),
"%s.java" % GetNameForElement(interface)) "%s.java" % GetNameForElement(interface))
self.Write(self.GenerateInterfaceInternalSource(interface),
"%s_Internal.java" % GetNameForElement(interface))
if self.module.constants: if self.module.constants:
self.Write(self.GenerateConstantsSource(self.module), self.Write(self.GenerateConstantsSource(self.module),
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
'<(DEPTH)/mojo/public/tools/bindings/mojom_bindings_generator.py', '<(DEPTH)/mojo/public/tools/bindings/mojom_bindings_generator.py',
'java_out_dir': '<(PRODUCT_DIR)/java_mojo/<(_target_name)/src', 'java_out_dir': '<(PRODUCT_DIR)/java_mojo/<(_target_name)/src',
'mojom_import_args%': [ 'mojom_import_args%': [
'-I<(DEPTH)' '-I<(DEPTH)'
], ],
}, },
'inputs': [ 'inputs': [
...@@ -43,8 +43,9 @@ ...@@ -43,8 +43,9 @@
'<(DEPTH)/mojo/public/tools/bindings/generators/java_templates/enum_definition.tmpl', '<(DEPTH)/mojo/public/tools/bindings/generators/java_templates/enum_definition.tmpl',
'<(DEPTH)/mojo/public/tools/bindings/generators/java_templates/enum.java.tmpl', '<(DEPTH)/mojo/public/tools/bindings/generators/java_templates/enum.java.tmpl',
'<(DEPTH)/mojo/public/tools/bindings/generators/java_templates/header.java.tmpl', '<(DEPTH)/mojo/public/tools/bindings/generators/java_templates/header.java.tmpl',
'<(DEPTH)/mojo/public/tools/bindings/generators/java_templates/interface.java.tmpl',
'<(DEPTH)/mojo/public/tools/bindings/generators/java_templates/interface_definition.tmpl', '<(DEPTH)/mojo/public/tools/bindings/generators/java_templates/interface_definition.tmpl',
'<(DEPTH)/mojo/public/tools/bindings/generators/java_templates/interface.java.tmpl',
'<(DEPTH)/mojo/public/tools/bindings/generators/java_templates/interface_internal.java.tmpl',
'<(DEPTH)/mojo/public/tools/bindings/generators/java_templates/struct_definition.tmpl', '<(DEPTH)/mojo/public/tools/bindings/generators/java_templates/struct_definition.tmpl',
'<(DEPTH)/mojo/public/tools/bindings/generators/java_templates/struct.java.tmpl', '<(DEPTH)/mojo/public/tools/bindings/generators/java_templates/struct.java.tmpl',
'<(DEPTH)/mojo/public/tools/bindings/generators/js_templates/enum_definition.tmpl', '<(DEPTH)/mojo/public/tools/bindings/generators/js_templates/enum_definition.tmpl',
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment