blob: df431d81a4d1733ff4257a0c2e89c86ea175b7a6 [file] [log] [blame]
/**************************************************************
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*************************************************************/
package com.sun.star.lib.uno.bridges.java_remote;
import com.sun.star.bridge.XBridge;
import com.sun.star.bridge.XInstanceProvider;
import com.sun.star.comp.connections.PipedConnection;
import com.sun.star.connection.XConnection;
import com.sun.star.container.NoSuchElementException;
import com.sun.star.lib.uno.environments.java.java_environment;
import com.sun.star.lib.uno.typeinfo.MethodTypeInfo;
import com.sun.star.lib.uno.typeinfo.TypeInfo;
import com.sun.star.uno.IQueryInterface;
import com.sun.star.uno.Type;
import com.sun.star.uno.UnoRuntime;
import com.sun.star.uno.XInterface;
import complexlib.ComplexTestCase;
import util.WaitUnreachable;
public final class java_remote_bridge_Test extends ComplexTestCase {
public String getTestObjectName() {
return getClass().getName();
}
public String[] getTestMethodNames() {
return new String[] { "test" };
}
public void test() throws Exception {
String protocol = "urp";
XConnection connectionA = new PipedConnection(new Object[0]);
XConnection connectionB = new PipedConnection(
new Object[] { connectionA });
java_remote_bridge bridgeA = new java_remote_bridge(
new java_environment(null), null,
new Object[] { protocol, connectionA, new TestInstanceProvider() });
java_remote_bridge bridgeB = new java_remote_bridge(
new java_environment(null), null,
new Object[] { protocol, connectionB, null });
testGetInstance(bridgeA, bridgeB);
testLifeCycle(bridgeA, bridgeB);
}
private void testGetInstance(XBridge bridgeA, XBridge bridgeB) {
assure("return null",
bridgeB.getInstance(TestInstanceProvider.NAME_NULL) == null);
try {
bridgeB.getInstance(TestInstanceProvider.NAME_RUNTIME_EXCEPTION);
failed("throw RuntimeException");
} catch (com.sun.star.uno.RuntimeException e) {
assure("throw RuntimeException",
e.getMessage().indexOf(
TestInstanceProvider.NAME_RUNTIME_EXCEPTION) != -1);
}
try {
bridgeB.getInstance(
TestInstanceProvider.NAME_NO_SUCH_ELEMENT_EXCEPTION);
failed("throw NoSuchElementException");
} catch (com.sun.star.uno.RuntimeException e) {
assure("throw NoSuchElementException",
e.getMessage().indexOf(
TestInstanceProvider.NAME_NO_SUCH_ELEMENT_EXCEPTION)
!= -1);
}
try {
bridgeA.getInstance(TestInstanceProvider.NAME_ANYTHING);
failed("no instance provider");
} catch (com.sun.star.uno.RuntimeException e) {
assure("no instance provider",
e.getMessage().startsWith("unknown OID "));
}
}
private void testLifeCycle(java_remote_bridge bridgeA,
java_remote_bridge bridgeB)
throws InterruptedException
{
// Repeatedly, objects are mapped from bridgeA to bridgeB, where proxies
// for those objects (for the XInterface and TestInterface facets) are
// created. The proxies at bridgeB keep both bridges alive; after those
// proxies have been garbage-collected, both bridges should be disposed.
// It does not work to map a local object from bridgeA to bridgeB, as
// bridgeB would find this object as a local one, too (via the shared,
// static localObjects Registry in java_environment): bridgeB would not
// create a proxy, would rather send back a "release" to bridgeA, and
// both bridges would be disposed while the first object is being
// mapped. Therefore, a HACK is used to install TestProxy objects
// (which behave as if they got mapped in to bridgeA from somewhere
// else) at bridgeA and map those.
final int COUNT = 100;
XInterface[] proxyBXInterface = new XInterface[COUNT];
TestInterface[] proxyBTestInterface = new TestInterface[COUNT];
for (int i = 0; i < COUNT; ++i) {
String name = "TestOID" + i;
Object proxyA = new TestProxy(name);
bridgeA.getSourceEnvironment().registerInterface(
proxyA, new String[] { name }, new Type(XInterface.class));
proxyBXInterface[i] = (XInterface) bridgeB.getInstance(name);
// map object:
proxyBTestInterface[i] = UnoRuntime.queryInterface(
TestInterface.class, proxyBXInterface[i]);
proxyBTestInterface[i].function();
// remap object once:
TestInterface remapped = UnoRuntime.queryInterface(
TestInterface.class, proxyBXInterface[i]);
remapped.function();
// remap object twice:
remapped = UnoRuntime.queryInterface(
TestInterface.class, proxyBXInterface[i]);
remapped.function();
}
assure("calls of object method", TestProxy.getCount() == 3 * COUNT);
// The following checks rely on the implementation detail that mapping
// different facets of a UNO object (XInterface and TestInterface) leads
// to different proxies:
assure("bridge A life count", bridgeA.getLifeCount() == 2 * COUNT);
assure("bridge B life count", bridgeB.getLifeCount() == 2 * COUNT);
assure("proxy count", ProxyFactory.getDebugCount() == 2 * COUNT);
System.out.println("waiting for proxies to become unreachable:");
for (int i = 0; i < COUNT; ++i) {
WaitUnreachable u1 = new WaitUnreachable(proxyBXInterface[i]);
WaitUnreachable u2 = new WaitUnreachable(proxyBTestInterface[i]);
proxyBXInterface[i] = null;
proxyBTestInterface[i] = null;
u1.waitUnreachable();
u2.waitUnreachable();
}
// For whatever strange reason, this sleep seems to be necessary to
// reliably ensure that even the last proxy's finalization is over
// before the following assure is executed:
Thread.sleep(1000);
assure("proxy count", ProxyFactory.getDebugCount() == 0);
System.out.println("waiting for pending messages to be done");
while (bridgeA.getLifeCount() != 0 || bridgeB.getLifeCount() != 0) {
Thread.sleep(100);
}
assure("Zero bridge A life count", bridgeA.getLifeCount() == 0);
assure("Zero bridge B life count", bridgeB.getLifeCount() == 0);
assure("Zero proxy count", ProxyFactory.getDebugCount() == 0);
}
public interface TestInterface extends XInterface {
void function();
TypeInfo[] UNOTYPEINFO = new TypeInfo[] {
new MethodTypeInfo("function", 0, 0) };
}
private static final class TestInstanceProvider
implements XInstanceProvider
{
public Object getInstance(String name) throws NoSuchElementException {
if (name.equals(NAME_NULL)) {
return null;
} else if (name.equals(NAME_RUNTIME_EXCEPTION)) {
throw new com.sun.star.uno.RuntimeException(
getClass().getName() + ", throwing: " + name);
} else if (name.equals(NAME_NO_SUCH_ELEMENT_EXCEPTION)) {
throw new NoSuchElementException(
getClass().getName() + ", throwing: " + name);
} else {
throw new IllegalStateException();
}
}
public static final String NAME_NULL = "return null";
public static final String NAME_RUNTIME_EXCEPTION
= "throw RuntimeException";
public static final String NAME_NO_SUCH_ELEMENT_EXCEPTION
= "throw NoSuchElementException";
public static final String NAME_ANYTHING = "anything";
}
private static final class TestProxy
implements com.sun.star.lib.uno.Proxy, IQueryInterface, XInterface,
TestInterface
{
public TestProxy(String oid) {
this.oid = oid;
}
public Object queryInterface(Type type) {
// type should be either XInterface or TestInterface...
return this;
}
public boolean isSame(Object object) {
return object instanceof TestProxy
&& oid.equals(((TestProxy) object).oid);
}
public String getOid() {
return oid;
}
public void function() {
synchronized (getClass()) {
++count;
}
}
public static synchronized int getCount() {
return count;
}
private final String oid;
private static int count = 0;
}
}