1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 package com.sun.star.lib.uno.bridges.java_remote;
24 
25 import com.sun.star.bridge.XBridge;
26 import com.sun.star.bridge.XInstanceProvider;
27 import com.sun.star.comp.connections.PipedConnection;
28 import com.sun.star.connection.XConnection;
29 import com.sun.star.container.NoSuchElementException;
30 import com.sun.star.lib.uno.environments.java.java_environment;
31 import com.sun.star.lib.uno.typeinfo.MethodTypeInfo;
32 import com.sun.star.lib.uno.typeinfo.TypeInfo;
33 import com.sun.star.uno.IQueryInterface;
34 import com.sun.star.uno.Type;
35 import com.sun.star.uno.UnoRuntime;
36 import com.sun.star.uno.XInterface;
37 import complexlib.ComplexTestCase;
38 import util.WaitUnreachable;
39 
40 public final class java_remote_bridge_Test extends ComplexTestCase {
getTestObjectName()41     public String getTestObjectName() {
42         return getClass().getName();
43     }
44 
getTestMethodNames()45     public String[] getTestMethodNames() {
46         return new String[] { "test" };
47     }
48 
test()49     public void test() throws Exception {
50         String protocol = "urp";
51 
52         XConnection connectionA = new PipedConnection(new Object[0]);
53         XConnection connectionB = new PipedConnection(
54             new Object[] { connectionA });
55         java_remote_bridge bridgeA = new java_remote_bridge(
56             new java_environment(null), null,
57             new Object[] { protocol, connectionA, new TestInstanceProvider() });
58         java_remote_bridge bridgeB = new java_remote_bridge(
59             new java_environment(null), null,
60             new Object[] { protocol, connectionB, null });
61 
62         testGetInstance(bridgeA, bridgeB);
63         testLifeCycle(bridgeA, bridgeB);
64     }
65 
testGetInstance(XBridge bridgeA, XBridge bridgeB)66     private void testGetInstance(XBridge bridgeA, XBridge bridgeB) {
67         assure("return null",
68                bridgeB.getInstance(TestInstanceProvider.NAME_NULL) == null);
69 
70         try {
71             bridgeB.getInstance(TestInstanceProvider.NAME_RUNTIME_EXCEPTION);
72             failed("throw RuntimeException");
73         } catch (com.sun.star.uno.RuntimeException e) {
74             assure("throw RuntimeException",
75                    e.getMessage().indexOf(
76                        TestInstanceProvider.NAME_RUNTIME_EXCEPTION) != -1);
77         }
78 
79         try {
80             bridgeB.getInstance(
81                 TestInstanceProvider.NAME_NO_SUCH_ELEMENT_EXCEPTION);
82             failed("throw NoSuchElementException");
83         } catch (com.sun.star.uno.RuntimeException e) {
84             assure("throw NoSuchElementException",
85                    e.getMessage().indexOf(
86                        TestInstanceProvider.NAME_NO_SUCH_ELEMENT_EXCEPTION)
87                    != -1);
88         }
89 
90         try {
91             bridgeA.getInstance(TestInstanceProvider.NAME_ANYTHING);
92             failed("no instance provider");
93         } catch (com.sun.star.uno.RuntimeException e) {
94             assure("no instance provider",
95                    e.getMessage().startsWith("unknown OID "));
96         }
97     }
98 
testLifeCycle(java_remote_bridge bridgeA, java_remote_bridge bridgeB)99     private void testLifeCycle(java_remote_bridge bridgeA,
100                                java_remote_bridge bridgeB)
101         throws InterruptedException
102     {
103         // Repeatedly, objects are mapped from bridgeA to bridgeB, where proxies
104         // for those objects (for the XInterface and TestInterface facets) are
105         // created.  The proxies at bridgeB keep both bridges alive; after those
106         // proxies have been garbage-collected, both bridges should be disposed.
107         // It does not work to map a local object from bridgeA to bridgeB, as
108         // bridgeB would find this object as a local one, too (via the shared,
109         // static localObjects Registry in java_environment): bridgeB would not
110         // create a proxy, would rather send back a "release" to bridgeA, and
111         // both bridges would be disposed while the first object is being
112         // mapped.  Therefore, a HACK is used to install TestProxy objects
113         // (which behave as if they got mapped in to bridgeA from somewhere
114         // else) at bridgeA and map those.
115 
116         final int COUNT = 100;
117         XInterface[] proxyBXInterface = new XInterface[COUNT];
118         TestInterface[] proxyBTestInterface = new TestInterface[COUNT];
119         for (int i = 0; i < COUNT; ++i) {
120             String name = "TestOID" + i;
121             Object proxyA = new TestProxy(name);
122             bridgeA.getSourceEnvironment().registerInterface(
123                 proxyA, new String[] { name }, new Type(XInterface.class));
124 
125             proxyBXInterface[i] = (XInterface) bridgeB.getInstance(name);
126 
127             // map object:
128             proxyBTestInterface[i] = UnoRuntime.queryInterface(
129                 TestInterface.class, proxyBXInterface[i]);
130             proxyBTestInterface[i].function();
131 
132             // remap object once:
133             TestInterface remapped = UnoRuntime.queryInterface(
134                 TestInterface.class, proxyBXInterface[i]);
135             remapped.function();
136 
137             // remap object twice:
138             remapped = UnoRuntime.queryInterface(
139                 TestInterface.class, proxyBXInterface[i]);
140             remapped.function();
141         }
142 
143         assure("calls of object method", TestProxy.getCount() == 3 * COUNT);
144 
145         // The following checks rely on the implementation detail that mapping
146         // different facets of a UNO object (XInterface and TestInterface) leads
147         // to different proxies:
148 
149         assure("bridge A life count", bridgeA.getLifeCount() == 2 * COUNT);
150         assure("bridge B life count", bridgeB.getLifeCount() == 2 * COUNT);
151         assure("proxy count", ProxyFactory.getDebugCount() == 2 * COUNT);
152 
153         System.out.println("waiting for proxies to become unreachable:");
154         for (int i = 0; i < COUNT; ++i) {
155             WaitUnreachable u1 = new WaitUnreachable(proxyBXInterface[i]);
156             WaitUnreachable u2 = new WaitUnreachable(proxyBTestInterface[i]);
157             proxyBXInterface[i] = null;
158             proxyBTestInterface[i] = null;
159             u1.waitUnreachable();
160             u2.waitUnreachable();
161         }
162         // For whatever strange reason, this sleep seems to be necessary to
163         // reliably ensure that even the last proxy's finalization is over
164         // before the following assure is executed:
165         Thread.sleep(1000);
166 
167         assure("proxy count", ProxyFactory.getDebugCount() == 0);
168 
169         System.out.println("waiting for pending messages to be done");
170         while (bridgeA.getLifeCount() != 0 || bridgeB.getLifeCount() != 0) {
171             Thread.sleep(100);
172         }
173 
174         assure("Zero bridge A life count", bridgeA.getLifeCount() == 0);
175         assure("Zero bridge B life count", bridgeB.getLifeCount() == 0);
176         assure("Zero proxy count", ProxyFactory.getDebugCount() == 0);
177     }
178 
179     public interface TestInterface extends XInterface {
function()180         void function();
181 
182         TypeInfo[] UNOTYPEINFO = new TypeInfo[] {
183             new MethodTypeInfo("function", 0, 0) };
184     }
185 
186     private static final class TestInstanceProvider
187         implements XInstanceProvider
188     {
getInstance(String name)189         public Object getInstance(String name) throws NoSuchElementException {
190             if (name.equals(NAME_NULL)) {
191                 return null;
192             } else if (name.equals(NAME_RUNTIME_EXCEPTION)) {
193                 throw new com.sun.star.uno.RuntimeException(
194                     getClass().getName() + ", throwing: " + name);
195             } else if (name.equals(NAME_NO_SUCH_ELEMENT_EXCEPTION)) {
196                 throw new NoSuchElementException(
197                     getClass().getName() + ", throwing: " + name);
198             } else {
199                 throw new IllegalStateException();
200             }
201         }
202 
203         public static final String NAME_NULL = "return null";
204         public static final String NAME_RUNTIME_EXCEPTION
205         = "throw RuntimeException";
206         public static final String NAME_NO_SUCH_ELEMENT_EXCEPTION
207         = "throw NoSuchElementException";
208         public static final String NAME_ANYTHING = "anything";
209     }
210 
211     private static final class TestProxy
212         implements com.sun.star.lib.uno.Proxy, IQueryInterface, XInterface,
213             TestInterface
214     {
TestProxy(String oid)215         public TestProxy(String oid) {
216             this.oid = oid;
217         }
218 
queryInterface(Type type)219         public Object queryInterface(Type type) {
220             // type should be either XInterface or TestInterface...
221             return this;
222         }
223 
isSame(Object object)224         public boolean isSame(Object object) {
225             return object instanceof TestProxy
226                 && oid.equals(((TestProxy) object).oid);
227         }
228 
getOid()229         public String getOid() {
230             return oid;
231         }
232 
function()233         public void function() {
234             synchronized (getClass()) {
235                 ++count;
236             }
237         }
238 
getCount()239         public static synchronized int getCount() {
240             return count;
241         }
242 
243         private final String oid;
244 
245         private static int count = 0;
246     }
247 }
248