/************************************************************** * * 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; } }