diff options
author | Samuel Mehrbrodt <Samuel.Mehrbrodt@cib.de> | 2020-02-05 12:19:06 +0100 |
---|---|---|
committer | Samuel Mehrbrodt <Samuel.Mehrbrodt@cib.de> | 2020-02-10 09:46:43 +0100 |
commit | 57eee86bab2efd63b7eec9d73a86b1cc0422de29 (patch) | |
tree | cbbb072e8ff9e63e664ea697360eee5f51eb5f2b /ridljar/com/sun/star/lib/uno | |
parent | 8cbd7f59bb99282c4bcb60639da1263c8eb3a5e3 (diff) |
Cleanup: Move files to ridljar
These files are now part of ridl.jar instead of jurt.jar,
so move them accordingly.
Follow-up cleanup for ae855bf48163ff64d94cfc34aff8e37abdb5518d
Change-Id: I01df60d99f5296b6252b260f52160c3e62f4b8dc
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/88007
Tested-by: Jenkins
Reviewed-by: Samuel Mehrbrodt <Samuel.Mehrbrodt@cib.de>
Diffstat (limited to 'ridljar/com/sun/star/lib/uno')
26 files changed, 4832 insertions, 0 deletions
diff --git a/ridljar/com/sun/star/lib/uno/Proxy.java b/ridljar/com/sun/star/lib/uno/Proxy.java new file mode 100644 index 000000000000..7d3612758fc5 --- /dev/null +++ b/ridljar/com/sun/star/lib/uno/Proxy.java @@ -0,0 +1,34 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ + +package com.sun.star.lib.uno; + +/** + * Marker interface implemented by proxies for UNO objects. + * + * <p>Currently, this interface is used internally by + * <code>com.sun.star.lib.uno.environments.java.java_environment</code> to + * distinguish between proxies and local objects. Any proxy object that shall + * be registered at the <code>java_environment</code> must implement this marker + * interface.</p> + */ +public interface Proxy { +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ridljar/com/sun/star/lib/uno/bridges/java_remote/BridgedObject.java b/ridljar/com/sun/star/lib/uno/bridges/java_remote/BridgedObject.java new file mode 100644 index 000000000000..0a724f059382 --- /dev/null +++ b/ridljar/com/sun/star/lib/uno/bridges/java_remote/BridgedObject.java @@ -0,0 +1,43 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ + +package com.sun.star.lib.uno.bridges.java_remote; + +import com.sun.star.bridge.XBridge; + +/** + * A back door to access the bridge associated with a bridged object. + */ +public final class BridgedObject { + /** + * Obtains the bridge associated with a bridged object. + * + * @param obj a reference to a (Java representation of a) UNO object; + * must not be null. + * @return the bridge associated with the given object, if it is indeed + * bridged; otherwise, null is returned. + */ + public static XBridge getBridge(Object obj) { + return ProxyFactory.getBridge(obj); + } + + private BridgedObject() {} // do not instantiate +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ridljar/com/sun/star/lib/uno/bridges/java_remote/ProxyFactory.java b/ridljar/com/sun/star/lib/uno/bridges/java_remote/ProxyFactory.java new file mode 100644 index 000000000000..1b3848983554 --- /dev/null +++ b/ridljar/com/sun/star/lib/uno/bridges/java_remote/ProxyFactory.java @@ -0,0 +1,198 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ + +package com.sun.star.lib.uno.bridges.java_remote; + +import com.sun.star.bridge.XBridge; +import com.sun.star.lib.util.AsynchronousFinalizer; +import com.sun.star.uno.IQueryInterface; +import com.sun.star.uno.Type; +import com.sun.star.uno.UnoRuntime; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +/** + * A factory for proxies specific to the <code>java_remote_bridge</code>. + * + * <p>Eventually, this class should be united with all other proxy classes + * specific to certain bridges (for example, the JNI bridge), resulting in a + * generic proxy class.</p> + */ +final class ProxyFactory { + public ProxyFactory(RequestHandler requestHandler, XBridge bridge) { + this.requestHandler = requestHandler; + this.bridge = bridge; + } + + public Object create(String oid, Type type) { + return Proxy.newProxyInstance( + getClass().getClassLoader(), + new Class[] { com.sun.star.lib.uno.Proxy.class, + IQueryInterface.class, type.getZClass() }, + new Handler(oid, type)); + } + + public boolean isProxy(Object obj) { + if (Proxy.isProxyClass(obj.getClass())) { + InvocationHandler h = Proxy.getInvocationHandler(obj); + return h instanceof Handler && ((Handler) h).matches(this); + } else { + return false; + } + } + + public void dispose() throws InterruptedException { + asynchronousFinalizer.drain(); + } + + public static XBridge getBridge(Object obj) { + if (Proxy.isProxyClass(obj.getClass())) { + InvocationHandler h = Proxy.getInvocationHandler(obj); + if (h instanceof Handler) { + return ((Handler) h).getBridge(); + } + } + return null; + } + + static int getDebugCount() { + synchronized (debugCountLock) { + return debugCount; + } + } + + private static void incrementDebugCount() { + synchronized (debugCountLock) { + ++debugCount; + } + } + + private static void decrementDebugCount() { + synchronized (debugCountLock) { + --debugCount; + } + } + + private final class Handler implements InvocationHandler { + public Handler(String oid, Type type) { + this.oid = oid; + this.type = type; + incrementDebugCount(); + } + + public boolean matches(ProxyFactory factory) { + return ProxyFactory.this == factory; + } + + public XBridge getBridge() { + return bridge; + } + + public Object invoke(Object proxy, Method method, Object[] args) + throws Throwable + { + if (method.equals(METHOD_EQUALS) || method.equals(METHOD_IS_SAME)) { + return Boolean.valueOf(args[0] != null + && oid.equals(UnoRuntime.generateOid(args[0]))); + } else if (method.equals(METHOD_HASH_CODE)) { + return Integer.valueOf(oid.hashCode()); + } else if (method.equals(METHOD_TO_STRING)) { + return "[Proxy:" + System.identityHashCode(proxy) + "," + oid + + "," + type + "]"; + } else if (method.equals(METHOD_QUERY_INTERFACE)) { + // See the comment in java_remote_bridge.mapInterfaceTo for one + // reason why this implementation must not satisfy a request for + // a super-interface with a proxy itself: + return args[0].equals(type) ? proxy + : request("queryInterface", args); + } else if (method.equals(METHOD_GET_OID)) { + return oid; + } else { + return request(method.getName(), args); + } + } + + @Override + protected void finalize() { + decrementDebugCount(); + asynchronousFinalizer.add(new AsynchronousFinalizer.Job() { + public void run() throws Throwable { + request("release", null); + } + }); + } + + private Object request(String operation, Object[] args) throws Throwable + { + Object res = requestHandler.sendRequest(oid, type, operation, args); + // Avoid early finalization of this object, while an invoke -> + // request call is still ongoing; as finalize also calls request, + // this should fulfil the condition from The Java Language + // Specification, 3rd ed., that "if an object's finalizer can result + // in synchronization on that object, then that object must be alive + // and considered reachable whenever a lock is held on it:" + synchronized (this) { + ++dummy; + } + return res; + } + + private final String oid; + private final Type type; + @SuppressWarnings("unused") + private int dummy = 0; + } + + private static final Method METHOD_EQUALS; + private static final Method METHOD_HASH_CODE; + private static final Method METHOD_TO_STRING; + private static final Method METHOD_QUERY_INTERFACE; + private static final Method METHOD_IS_SAME; + private static final Method METHOD_GET_OID; + static { + try { + METHOD_EQUALS = Object.class.getMethod( + "equals", new Class[] { Object.class }); + METHOD_HASH_CODE = Object.class.getMethod( + "hashCode", (Class[]) null); + METHOD_TO_STRING = Object.class.getMethod( + "toString", (Class[]) null); + METHOD_QUERY_INTERFACE = IQueryInterface.class.getMethod( + "queryInterface", new Class[] { Type.class }); + METHOD_IS_SAME = IQueryInterface.class.getMethod( + "isSame", new Class[] { Object.class }); + METHOD_GET_OID = IQueryInterface.class.getMethod( + "getOid", (Class[]) null); + } catch (NoSuchMethodException e) { + throw new ExceptionInInitializerError(e); + } + } + + private static final Object debugCountLock = new Object(); + private static int debugCount = 0; + + private final RequestHandler requestHandler; + private final XBridge bridge; + private final AsynchronousFinalizer asynchronousFinalizer = + new AsynchronousFinalizer(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ridljar/com/sun/star/lib/uno/bridges/java_remote/RequestHandler.java b/ridljar/com/sun/star/lib/uno/bridges/java_remote/RequestHandler.java new file mode 100644 index 000000000000..d5246bf26c21 --- /dev/null +++ b/ridljar/com/sun/star/lib/uno/bridges/java_remote/RequestHandler.java @@ -0,0 +1,35 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ + +package com.sun.star.lib.uno.bridges.java_remote; + +import com.sun.star.uno.Type; + +/** + * The link between the proxies generated by <code>ProxyFactory</code> (which + * receive requests in the form of method calls) and + * <code>java_remote_bridge</code> (which passes those requests on to the remote + * side). + */ +interface RequestHandler { + Object sendRequest(String oid, Type type, String operation, Object[] args) + throws Throwable; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ridljar/com/sun/star/lib/uno/bridges/java_remote/XConnectionInputStream_Adapter.java b/ridljar/com/sun/star/lib/uno/bridges/java_remote/XConnectionInputStream_Adapter.java new file mode 100644 index 000000000000..8d660e88cacd --- /dev/null +++ b/ridljar/com/sun/star/lib/uno/bridges/java_remote/XConnectionInputStream_Adapter.java @@ -0,0 +1,76 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ + +package com.sun.star.lib.uno.bridges.java_remote; + + +import java.io.IOException; +import java.io.InputStream; + +import com.sun.star.connection.XConnection; + + +class XConnectionInputStream_Adapter extends InputStream { + private static final boolean DEBUG = false; + + protected XConnection _xConnection; + protected byte _bytes[][] = new byte[1][]; + + XConnectionInputStream_Adapter(XConnection xConnection) { + if(xConnection == null) throw new NullPointerException("the XConnection must not be null"); + + if(DEBUG) System.err.println("#### " + getClass().getName() + " - instantiated "); + + _xConnection = xConnection; + } + + @Override + public int read() throws IOException { + int len; + + try { + len = _xConnection.read(_bytes, 1); + } catch(com.sun.star.io.IOException ioException) { + IOException ex = new IOException(ioException.getMessage()); + ex.initCause(ioException); + throw ex; + } + + if(DEBUG) System.err.println("#### " + getClass().getName() + " - one byte read:" + _bytes[0][0]); + + return len == 0 ? -1 : _bytes[0][0] & 0xff; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + try { + len = _xConnection.read(_bytes, len - off); + } catch(com.sun.star.io.IOException ioException) { + IOException ex = new IOException(ioException.getMessage()); + ex.initCause(ioException); + throw ex; + } + + System.arraycopy(_bytes[0], 0, b, off, len); + + return len == 0 ? -1 : len; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ridljar/com/sun/star/lib/uno/bridges/java_remote/XConnectionOutputStream_Adapter.java b/ridljar/com/sun/star/lib/uno/bridges/java_remote/XConnectionOutputStream_Adapter.java new file mode 100644 index 000000000000..ac198f8fdd77 --- /dev/null +++ b/ridljar/com/sun/star/lib/uno/bridges/java_remote/XConnectionOutputStream_Adapter.java @@ -0,0 +1,89 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ + +package com.sun.star.lib.uno.bridges.java_remote; + + +import java.io.IOException; +import java.io.OutputStream; + +import com.sun.star.connection.XConnection; + + +class XConnectionOutputStream_Adapter extends OutputStream { + private static final boolean DEBUG = false; + + protected XConnection _xConnection; + protected byte _bytes[] = new byte[1]; + + XConnectionOutputStream_Adapter(XConnection xConnection) { + if(DEBUG) System.err.println("#### " + this.getClass() + " - instantiated "); + + _xConnection = xConnection; + } + + @Override + public void write(int b) throws IOException { + _bytes[0] = (byte)b; + + try { + _xConnection.write(_bytes); + } catch(com.sun.star.io.IOException ioException) { + IOException ex = new IOException(ioException.getMessage()); + ex.initCause(ioException); + throw ex; + } + + if(DEBUG) System.err.println("#### " + this.getClass() + " - one byte written:" + _bytes[0]); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + byte bytes[] ; + + if(off == 0 && len == b.length) { + bytes = b; + } else { + bytes = new byte[len]; + + System.arraycopy(b, off, bytes, 0, len); + } + + try { + _xConnection.write(bytes); + } catch(com.sun.star.io.IOException ioException) { + IOException ex = new IOException(ioException.getMessage()); + ex.initCause(ioException); + throw ex; + } + } + + @Override + public void flush() throws IOException { + try { + _xConnection.flush(); + } catch(com.sun.star.io.IOException ioException) { + IOException ex = new IOException(ioException.getMessage()); + ex.initCause(ioException); + throw ex; + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ridljar/com/sun/star/lib/uno/bridges/java_remote/java_remote_bridge.java b/ridljar/com/sun/star/lib/uno/bridges/java_remote/java_remote_bridge.java new file mode 100644 index 000000000000..392e031e3063 --- /dev/null +++ b/ridljar/com/sun/star/lib/uno/bridges/java_remote/java_remote_bridge.java @@ -0,0 +1,700 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ +package com.sun.star.lib.uno.bridges.java_remote; + + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +import com.sun.star.bridge.XBridge; +import com.sun.star.bridge.XInstanceProvider; +import com.sun.star.connection.XConnection; +import com.sun.star.lang.DisposedException; +import com.sun.star.lang.EventObject; +import com.sun.star.lang.XComponent; +import com.sun.star.lang.XEventListener; +import com.sun.star.lib.uno.environments.java.java_environment; +import com.sun.star.lib.uno.environments.remote.IProtocol; +import com.sun.star.lib.uno.environments.remote.IReceiver; +import com.sun.star.lib.uno.environments.remote.IThreadPool; +import com.sun.star.lib.uno.environments.remote.Job; +import com.sun.star.lib.uno.environments.remote.Message; +import com.sun.star.lib.uno.environments.remote.ThreadId; +import com.sun.star.lib.uno.environments.remote.ThreadPoolManager; +import com.sun.star.lib.uno.typedesc.MethodDescription; +import com.sun.star.lib.uno.typedesc.TypeDescription; +import com.sun.star.lib.util.DisposeListener; +import com.sun.star.lib.util.DisposeNotifier; +import com.sun.star.uno.Any; +import com.sun.star.uno.IBridge; +import com.sun.star.uno.IEnvironment; +import com.sun.star.uno.Type; +import com.sun.star.uno.TypeClass; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XInterface; + +/** + * This class implements a remote bridge. + * + * <p>Therefore various interfaces are implemented.</p> + * + * <p>The protocol to used is passed by name, the bridge + * then looks for it under <code>com.sun.star.lib.uno.protocols</code>.</p> + * + * @since UDK1.0 + */ +public class java_remote_bridge + implements IBridge, IReceiver, RequestHandler, XBridge, XComponent, + DisposeNotifier +{ + /** + * When set to true, enables various debugging output. + */ + private static final boolean DEBUG = false; + + private final class MessageDispatcher extends Thread { + public MessageDispatcher() { + super("MessageDispatcher"); + } + + @Override + public void run() { + try { + for (;;) { + synchronized (this) { + if (terminate) { + break; + } + } + Message msg = _iProtocol.readMessage(); + Object obj = null; + if (msg.isRequest()) { + String oid = msg.getObjectId(); + Type type = new Type(msg.getType()); + int fid = msg.getMethod().getIndex(); + if (fid == MethodDescription.ID_RELEASE) { + _java_environment.revokeInterface(oid, type); + remRefHolder(type, oid); + if (msg.isSynchronous()) { + sendReply(false, msg.getThreadId(), null); + } + continue; + } + obj = _java_environment.getRegisteredInterface( + oid, type); + if (obj == null + && fid == MethodDescription.ID_QUERY_INTERFACE) + { + if (_xInstanceProvider == null) { + sendReply( + true, msg.getThreadId(), + new com.sun.star.uno.RuntimeException( + "unknown OID " + oid)); + continue; + } else { + UnoRuntime.setCurrentContext( + msg.getCurrentContext()); + try { + obj = _xInstanceProvider.getInstance(oid); + } catch (com.sun.star.uno.RuntimeException e) { + sendReply(true, msg.getThreadId(), e); + continue; + } catch (Exception e) { + sendReply( + true, msg.getThreadId(), + new com.sun.star.uno.RuntimeException( + e.toString())); + continue; + } finally { + UnoRuntime.setCurrentContext(null); + } + } + } + } + _iThreadPool.putJob( + new Job(obj, java_remote_bridge.this, msg)); + } + } catch (Throwable e) { + dispose(e); + } + } + + public synchronized void terminate() { + terminate = true; + } + + private boolean terminate = false; + } + + protected XConnection _xConnection; + + protected XInstanceProvider _xInstanceProvider; + + protected String _name = "remote"; + private final String protocol; + protected IProtocol _iProtocol; + protected IEnvironment _java_environment; + protected MessageDispatcher _messageDispatcher; + protected final AtomicInteger _life_count = new AtomicInteger(); // determines if this bridge is alive, which is controlled by acquire and release calls + + private final ArrayList<XEventListener> _listeners = new ArrayList<XEventListener>(); + + protected IThreadPool _iThreadPool; + + // Variable disposed must only be used while synchronized on this object: + private boolean disposed = false; + + /** + * This method is for testing only. + */ + int getLifeCount() { + return _life_count.get(); + } + + /** + * This method is for testing only. + */ + IProtocol getProtocol() { + return _iProtocol; + } + + /** + * The ref holder stuff strongly holds objects mapped out via this bridge + * (the java_environment only holds them weakly). + * + * <p>When this bridge is disposed, all remaining ref holder entries are + * released.</p> + */ + private static final class RefHolder { + public RefHolder(Type type, Object object) { + this.type = type; + this.object = object; + } + + public Type getType() { + return type; + } + + public void acquire() { + ++count; + } + + public boolean release() { + return --count == 0; + } + + private final Type type; + @SuppressWarnings("unused") + private final Object object; + private int count = 1; + } + + private final HashMap<String, LinkedList<RefHolder>> refHolders = new HashMap<String, LinkedList<RefHolder>>(); + // from OID (String) to LinkedList of RefHolder + + private boolean hasRefHolder(String oid, Type type) { + synchronized (refHolders) { + LinkedList<RefHolder> l = refHolders.get(oid); + if (l != null) { + for (RefHolder rh : l) { + if (type.isSupertypeOf(rh.getType())) { + return true; + } + } + } + } + return false; + } + + final void addRefHolder(Object obj, Type type, String oid) { + synchronized (refHolders) { + LinkedList<RefHolder> l = refHolders.get(oid); + if (l == null) { + l = new LinkedList<RefHolder>(); + refHolders.put(oid, l); + } + boolean found = false; + for (Iterator<RefHolder> i = l.iterator(); !found && i.hasNext();) { + RefHolder rh = i.next(); + if (rh.getType().equals(type)) { + found = true; + rh.acquire(); + } + } + if (!found) { + l.add(new RefHolder(type, obj)); + } + } + acquire(); + } + + final void remRefHolder(Type type, String oid) { + synchronized (refHolders) { + LinkedList<RefHolder> l = refHolders.get(oid); + if (l == null) { + return; + } + for (RefHolder rh : l) { + if (rh.getType().equals(type)) { + try { + if (rh.release()) { + l.remove(rh); + if (l.isEmpty()) { + refHolders.remove(oid); + } + } + } finally { + release(); + } + break; + } + } + } + } + + final void freeHolders() { + synchronized (refHolders) { + for (Iterator<Map.Entry<String,LinkedList<RefHolder>>> i1 = refHolders.entrySet().iterator(); i1.hasNext();) + { + Map.Entry<String,LinkedList<RefHolder>> e = i1.next(); + String oid = e.getKey(); + LinkedList<RefHolder> l = e.getValue(); + for (Iterator<RefHolder> i2 = l.iterator(); i2.hasNext();) { + RefHolder rh = i2.next(); + for (boolean done = false; !done;) { + done = rh.release(); + _java_environment.revokeInterface(oid, rh.getType()); + release(); + } + } + } + refHolders.clear(); + } + } + + public java_remote_bridge( + IEnvironment java_environment, IEnvironment remote_environment, + Object[] args) + throws Exception + { + _java_environment = java_environment; + String proto = (String) args[0]; + _xConnection = (XConnection) args[1]; + _xInstanceProvider = (XInstanceProvider) args[2]; + if (args.length > 3) { + _name = (String) args[3]; + } + String attr; + int i = proto.indexOf(','); + if (i >= 0) { + protocol = proto.substring(0, i); + attr = proto.substring(i + 1); + } else { + protocol = proto; + attr = null; + } + _iProtocol = (IProtocol) Class.forName( + "com.sun.star.lib.uno.protocols." + protocol + "." + protocol). + getConstructor( + new Class[] { + IBridge.class, String.class, InputStream.class, + OutputStream.class }). + newInstance( + new Object[] { + this, attr, + new XConnectionInputStream_Adapter(_xConnection), + new XConnectionOutputStream_Adapter(_xConnection) }); + proxyFactory = new ProxyFactory(this, this); + _iThreadPool = ThreadPoolManager.create(); + _messageDispatcher = new MessageDispatcher(); + _messageDispatcher.start(); + _iProtocol.init(); + } + + private void notifyListeners() { + EventObject eventObject = new EventObject(this); + + Iterator<XEventListener> elements = _listeners.iterator(); + while(elements.hasNext()) { + XEventListener xEventListener = elements.next(); + + try { + xEventListener.disposing(eventObject); + } + catch(com.sun.star.uno.RuntimeException runtimeException) { + // we are here not interested in any exceptions + } + } + } + + /** + * Constructs a new bridge. + * <p> This method is not part of the provided <code>api</code> + * and should only be used by the UNO runtime.</p> + * + * @param args the custom parameters: arg[0] == protocol_name, + * arg[1] == xConnection, arg[2] == xInstanceProvider. + * + * @deprecated as of UDK 1.0 + */ + @Deprecated + public java_remote_bridge(Object args[]) throws Exception { + this(UnoRuntime.getEnvironment("java", null), UnoRuntime.getEnvironment("remote", null), args); + } + + /** + * + * @see com.sun.star.uno.IBridge#mapInterfaceTo + */ + public Object mapInterfaceTo(Object object, Type type) { + checkDisposed(); + if (object == null) { + return null; + } else { + String[] oid = new String[1]; + object = _java_environment.registerInterface(object, oid, type); + if (!proxyFactory.isProxy(object)) { + // This branch must be taken iff object either is no proxy at + // all or a proxy from some other bridge. There are objects + // that behave like objects for this bridge but that are not + // detected as such by proxyFactory.isProxy. The only known + // case of such objects is com.sun.star.comp.beans.Wrapper, + // which implements com.sun.star.lib.uno.Proxy and effectively + // is a second proxy around a proxy that can be from this + // bridge. For that case, there is no problem, however: Since + // the proxies generated by ProxyFactory send each + // queryInterface to the original object (i.e., they do not + // short-circuit requests for a super-interface to themselves), + // there will always be an appropriate ProxyFactory-proxy + // registered at the _java_environment, so that the object + // returned by _java_environment.registerInterface will never be + // a com.sun.star.comp.beans.Wrapper. + addRefHolder(object, type, oid[0]); + } + return oid[0]; + } + } + + /** + * Maps an object from destination environment to the source environment. + * + * @param oId the object to map. + * @param type the interface under which is to be mapped. + * @return the object in the source environment. + * + * @see com.sun.star.uno.IBridge#mapInterfaceFrom + */ + public Object mapInterfaceFrom(Object oId, Type type) { + checkDisposed(); + // TODO What happens if an exception is thrown after the call to + // acquire, but before it is guaranteed that a pairing release will be + // called eventually? + acquire(); + String oid = (String) oId; + Object object = _java_environment.getRegisteredInterface(oid, type); + if (object == null) { + object = _java_environment.registerInterface( + proxyFactory.create(oid, type), new String[] { oid }, type); + // the proxy sends a release when finalized + } else if (!hasRefHolder(oid, type)) { + sendInternalRequest(oid, type, "release", null); + } + return object; + } + + /** + * Gives the source environment. + * + * @return the source environment of this bridge. + * @see com.sun.star.uno.IBridge#getSourceEnvironment + */ + public IEnvironment getSourceEnvironment() { + return _java_environment; + } + + /** + * Gives the destination environment. + * + * @return the destination environment of this bridge. + * @see com.sun.star.uno.IBridge#getTargetEnvironment + */ + public IEnvironment getTargetEnvironment() { + return null; + } + + /** + * Increases the life count. + * + * @see com.sun.star.uno.IBridge#acquire + */ + public void acquire() { + if(DEBUG) { + int x = _life_count.incrementAndGet(); + System.err.println("##### " + getClass().getName() + ".acquire:" + x); + } else { + _life_count.incrementAndGet(); + } + } + + /** + * Decreases the life count. + * + * <p>If the life count drops to zero, the bridge disposes itself.</p> + * + * @see com.sun.star.uno.IBridge#release + */ + public void release() { + int x = _life_count.decrementAndGet(); + if (x <= 0) { + dispose(new Throwable("end of life")); + } + } + + public void dispose() { + dispose(new Throwable("user dispose")); + } + + private void dispose(Throwable throwable) { + synchronized (this) { + if (disposed) { + return; + } + disposed = true; + } + + notifyListeners(); + for (Iterator<DisposeListener> i = disposeListeners.iterator(); i.hasNext();) { + i.next().notifyDispose(this); + } + + _iProtocol.terminate(); + + try { + _messageDispatcher.terminate(); + + try { + _xConnection.close(); + } catch (com.sun.star.io.IOException e) { + System.err.println( + getClass().getName() + ".dispose - IOException:" + e); + } + + if (Thread.currentThread() != _messageDispatcher + && _messageDispatcher.isAlive()) + { + _messageDispatcher.join(1000); + if (_messageDispatcher.isAlive()) { + _messageDispatcher.interrupt(); + _messageDispatcher.join(); + } + } + + // interrupt all jobs queued by this bridge + _iThreadPool.dispose(throwable); + + // release all out-mapped objects and all in-mapped proxies: + freeHolders(); + // assert _java_environment instanceof java_environment; + ((java_environment) _java_environment).revokeAllProxies(); + + proxyFactory.dispose(); + + if (DEBUG) { + if (_life_count.get() != 0) { + System.err.println(getClass().getName() + + ".dispose - life count (proxies left):" + + _life_count); + } + _java_environment.list(); + } + + // clear members + _xConnection = null; + _java_environment = null; + _messageDispatcher = null; + } catch (InterruptedException e) { + System.err.println(getClass().getName() + + ".dispose - InterruptedException:" + e); + } + } + + /** + * + * @see com.sun.star.bridge.XBridge#getInstance + */ + public Object getInstance(String instanceName) { + Type t = new Type(XInterface.class); + return sendInternalRequest( + instanceName, t, "queryInterface", new Object[] { t }); + } + + /** + * Gives the name of this bridge. + * + * @return the name of this bridge. + * @see com.sun.star.bridge.XBridge#getName + */ + public String getName() { + return _name; + } + + /** + * Gives a description of the connection type and protocol used. + * + * @return connection type and protocol. + * @see com.sun.star.bridge.XBridge#getDescription + */ + public String getDescription() { + return protocol + "," + _xConnection.getDescription(); + } + + public void sendReply(boolean exception, ThreadId threadId, Object result) { + if (DEBUG) { + System.err.println("##### " + getClass().getName() + ".sendReply: " + + exception + " " + result); + } + + checkDisposed(); + + try { + _iProtocol.writeReply(exception, threadId, result); + } catch (IOException e) { + dispose(e); + throw (DisposedException) + (new DisposedException("unexpected " + e).initCause(e)); + } catch (RuntimeException e) { + dispose(e); + throw e; + } catch (Error e) { + dispose(e); + throw e; + } + } + + public Object sendRequest( + String oid, Type type, String operation, Object[] params) + throws Throwable + { + Object result = null; + + checkDisposed(); + + ThreadId threadId = _iThreadPool.getThreadId(); + Object handle = _iThreadPool.attach(threadId); + try { + boolean sync; + try { + sync = _iProtocol.writeRequest( + oid, TypeDescription.getTypeDescription(type), operation, + threadId, params); + } catch (IOException e) { + dispose(e); + throw (DisposedException) + new DisposedException(e.toString()).initCause(e); + } + if (sync && Thread.currentThread() != _messageDispatcher) { + result = _iThreadPool.enter(handle, threadId); + } + } finally { + _iThreadPool.detach(handle, threadId); + if(operation.equals("release")) + release(); // kill this bridge, if this was the last proxy + } + + if(DEBUG) System.err.println("##### " + getClass().getName() + ".sendRequest left:" + result); + + // On the wire (at least in URP), the result of queryInterface is + // transported as an ANY, but in Java it shall be transported as a + // direct reference to the UNO object (represented as a Java Object), + // never boxed in a com.sun.star.uno.Any: + if (operation.equals("queryInterface") && result instanceof Any) { + Any a = (Any) result; + if (a.getType().getTypeClass() == TypeClass.INTERFACE) { + result = a.getObject(); + } else { + result = null; // should never happen + } + } + + return result; + } + + private Object sendInternalRequest( + String oid, Type type, String operation, Object[] arguments) + { + try { + return sendRequest(oid, type, operation, arguments); + } catch (Error e) { + throw e; + } catch (RuntimeException e) { + throw e; + } catch (Throwable e) { + throw new RuntimeException("Unexpected " + e); + } + } + + /** + * Methods XComponent. + */ + public void addEventListener(XEventListener xEventListener) { + _listeners.add(xEventListener); + } + + public void removeEventListener(XEventListener xEventListener) { + _listeners.remove(xEventListener); + } + + /** + * + * @see DisposeNotifier#addDisposeListener + */ + public void addDisposeListener(DisposeListener listener) { + synchronized (this) { + if (!disposed) { + disposeListeners.add(listener); + return; + } + } + listener.notifyDispose(this); + } + + /** + * This function must only be called while synchronized on this object. + */ + private synchronized void checkDisposed() { + if (disposed) { + throw new DisposedException("java_remote_bridge " + this + + " is disposed"); + } + } + + private final ProxyFactory proxyFactory; + + // Access to disposeListeners must be synchronized on <CODE>this</CODE>: + private final ArrayList<DisposeListener> disposeListeners = new ArrayList<DisposeListener>(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ridljar/com/sun/star/lib/uno/environments/java/java_environment.java b/ridljar/com/sun/star/lib/uno/environments/java/java_environment.java new file mode 100644 index 000000000000..89e4b10880ca --- /dev/null +++ b/ridljar/com/sun/star/lib/uno/environments/java/java_environment.java @@ -0,0 +1,312 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ + +package com.sun.star.lib.uno.environments.java; + +import com.sun.star.uno.IEnvironment; +import com.sun.star.uno.Type; +import com.sun.star.uno.UnoRuntime; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.HashMap; +import java.util.Iterator; + +/** + * The java_environment is the environment where objects and + * interfaces are registered, which are mapped out of java or + * into java. + * + * <p>The java_environment implements the <code>IEnvironment</code> interface + * defined in the uno runtime.</p> + * + * @see com.sun.star.uno.UnoRuntime + * @see com.sun.star.uno.IEnvironment + * @since UDK1.0 + */ +public final class java_environment implements IEnvironment { + public java_environment(Object context) { + this.context = context; + } + + /** + * + * @see com.sun.star.uno.IEnvironment#getContext + */ + public Object getContext() { + return context; + } + + /** + * + * @see com.sun.star.uno.IEnvironment#getName + */ + public String getName() { + return "java"; + } + + /** + * + * @see com.sun.star.uno.IEnvironment#registerInterface + */ + public Object registerInterface(Object object, String[] oid, Type type) { + if (oid[0] == null) { + oid[0] = UnoRuntime.generateOid(object); + } + return (isProxy(object) ? proxies : localObjects).register( + object, oid[0], type); + } + + /** + * You have to revoke ANY interface that has been registered via this + * method. + * + * @param oid object id of interface to be revoked. + * @param type the type description of the interface. + * @see com.sun.star.uno.IEnvironment#revokeInterface + */ + public void revokeInterface(String oid, Type type) { + if (!proxies.revoke(oid, type)) { + localObjects.revoke(oid, type); + } + } + + /** + * Retrieves an interface identified by its object id and type from this + * environment. + * + * @param oid object id of interface to be retrieved. + * @param type the type description of the interface to be retrieved. + * @see com.sun.star.uno.IEnvironment#getRegisteredInterface + */ + public Object getRegisteredInterface(String oid, Type type) { + Object o = proxies.get(oid, type); + if (o == null) { + o = localObjects.get(oid, type); + } + return o; + } + + /** + * Retrieves the object identifier for a registered interface from this + * environment. + * + * @param object a registered interface. + * @see com.sun.star.uno.IEnvironment#getRegisteredObjectIdentifier + */ + public String getRegisteredObjectIdentifier(Object object) { + return UnoRuntime.generateOid(object); + } + + /** + * + * @see com.sun.star.uno.IEnvironment#list + */ + public void list() { +// TODO??? + } + + /** + * Revokes all registered proxy interfaces. + * + * <p>This method should be part of <code>IEnvironment</code>. It is called + * from <code>com.sun.star.lib.uno.bridges.java_remote.<!-- + * -->java_remote_bridge.dispose</code>.</p> + */ + public void revokeAllProxies() { + proxies.clear(); + } + + // TODO What's this??? java.lang.Object#equals requires reflexivity... + // + // Maybe this was hacked in so that different bridges use different + // instances of java_environment. That is desirable for the following + // reason: An OID is bridged in over bridge A, a proxy is created on the + // Java side, and recorded in the java_environment. The same OID is then + // bridged in over another bridge B. If there were only one + // java_environment shared by both bridges, the proxy from bridge A would be + // reused. If now bridge A is taken down programmatically (e.g., because + // some controlling code somehow deduced that no objects are mapped over + // that bridge any longer), but the proxy is still used by bridge B, using + // the proxy would now result in errors. The explicit API to control + // bridges forbids to transparently share proxies between bridges, and using + // different java_environment instances for different bridges is the way to + // enforce this. + @Override + public boolean equals(Object obj) { + return false; + } + + private static final class Registry { + public synchronized Object register( + Object object, String oid, Type type) + { + cleanUp(); + Level1Entry l1 = level1map.get(oid); + if (l1 != null) { + Level2Entry l2 = l1.level2map.get(type); + if (l2 != null) { + Object o = l2.get(); + if (o != null) { + l2.acquire(); + return o; + } + } + } + // TODO If a holder references an unreachable object, but still has + // a positive count, it is replaced with a new holder (referencing a + // reachable object, and with a count of 1). Any later calls to + // revoke that should decrement the count of the previous holder + // would now decrement the count of the new holder, removing it + // prematurely. This is a design flaw that will be fixed when + // IEnvironment.revokeInterface is changed to no longer use + // counting. (And this problem is harmless, as currently a holder + // either references a strongly held object and uses register/revoke + // to control it, or references a weakly held proxy and never + // revokes it.) + if (l1 == null) { + l1 = new Level1Entry(); + level1map.put(oid, l1); + } + l1.level2map.put(type, new Level2Entry(oid, type, object, queue)); + return object; + } + + public synchronized boolean revoke(String oid, Type type) { + Level1Entry l1 = level1map.get(oid); + Level2Entry l2 = null; + if (l1 != null) { + l2 = l1.level2map.get(type); + if (l2 != null && l2.release()) { + removeLevel2Entry(l1, oid, type); + } + } + cleanUp(); + return l2 != null; + } + + public synchronized Object get(String oid, Type type) { + Level1Entry l1 = level1map.get(oid); + return l1 == null ? null : l1.find(type); + } + + public synchronized void clear() { + level1map.clear(); + cleanUp(); + } + + // must only be called while synchronized on this Registry: + private void cleanUp() { + for (;;) { + Object tmp = queue.poll(); + Level2Entry l2 = (Level2Entry) tmp; + if (l2 == null) { + break; + } + // It is possible that a Level2Entry e1 for the OID/type pair + // (o,t) becomes weakly reachable, then another Level2Entry e2 + // is registered for the same pair (o,t) (a new Level2Entry is + // created since now e1.get() == null), and only then e1 is + // enqueued. To not erroneously remove the new e2 in that case, + // check whether the map still contains e1: + Level1Entry l1 = level1map.get(l2.oid); + if (l1 != null && l1.level2map.get(l2.type) == l2) { + removeLevel2Entry(l1, l2.oid, l2.type); + } + } + } + + // must only be called while synchronized on this Registry: + private void removeLevel2Entry(Level1Entry l1, String oid, Type type) { + l1.level2map.remove(type); + if (l1.level2map.isEmpty()) { + level1map.remove(oid); + } + } + + private static final class Level1Entry { + // must only be called while synchronized on enclosing Registry: + public Object find(Type type) { + // First, look for an exactly matching entry; then, look for an + // arbitrary entry for a subtype of the request type: + Level2Entry l2 = level2map.get(type); + if (l2 != null) { + Object o = l2.get(); + if (o != null) { + return o; + } + } + for (Iterator<Level2Entry> i = level2map.values().iterator(); + i.hasNext();) + { + l2 = i.next(); + if (type.isSupertypeOf(l2.type)) { + Object o = l2.get(); + if (o != null) { + return o; + } + } + } + return null; + } + + public final HashMap<Type, Level2Entry> level2map = + new HashMap<Type, Level2Entry>(); + } + + private static final class Level2Entry extends WeakReference<Object> { + public Level2Entry( + String oid, Type type, Object object, ReferenceQueue<Object> queue) + { + super(object, queue); + this.oid = oid; + this.type = type; + } + + // must only be called while synchronized on enclosing Registry: + public void acquire() { + ++count; + } + + // must only be called while synchronized on enclosing Registry: + public boolean release() { + return --count == 0; + } + + public final String oid; + public final Type type; + + private int count = 1; + } + + private final HashMap<String, Level1Entry> level1map = + new HashMap<String, Level1Entry>(); + private final ReferenceQueue<Object> queue = new ReferenceQueue<Object>(); + } + + private boolean isProxy(Object object) { + return object instanceof com.sun.star.lib.uno.Proxy; + } + + private static final Registry localObjects = new Registry(); + + private final Object context; + private final Registry proxies = new Registry(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ridljar/com/sun/star/lib/uno/environments/remote/IProtocol.java b/ridljar/com/sun/star/lib/uno/environments/remote/IProtocol.java new file mode 100644 index 000000000000..c2ecbf9a09c2 --- /dev/null +++ b/ridljar/com/sun/star/lib/uno/environments/remote/IProtocol.java @@ -0,0 +1,93 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ + +package com.sun.star.lib.uno.environments.remote; + +import com.sun.star.lib.uno.typedesc.TypeDescription; +import java.io.IOException; + +/** + * An abstraction of remote bridge protocols. + * + * <p>A class implementing a given protocol <var>prot</var> must be named + * <code>com.sun.star.lib.uno.protocols.<var>prot</var>.<var>prot</var></code> + * and must have a public constructor that takes four arguments: The first + * argument of type <code>com.sun.star.uno.IBridge</code> must not be null. The + * second argument of type <code>String</code> represents any attributes; it may + * be null if there are no attributes. The third argument of type + * <code>java.io.InputStream</code> must not be null. The fourth argument of + * type <code>java.io.OutputStream</code> must not be null.</p> + */ +public interface IProtocol { + /** + * Initializes the connection. + * + * <p>This method must be called exactly once, after the + * <code>readMessage</code> loop has already been established.</p> + */ + void init() throws IOException; + + void terminate(); + + /** + * Reads a request or reply message. + * + * <p>Access to this method from multiple threads must be properly + * synchronized.</p> + * + * @return a non-null message; if the input stream is exhausted, a + * <code>java.io.IOException</code> is thrown instead. + */ + Message readMessage() throws IOException; + + /** + * Writes a request message. + * + * @param oid a non-null OID. + * @param type a non-null UNO type. + * @param function a non-null function (the name of a UNO interface method + * or attribute compatible with the given <code>type</code>, or either + * <code>"queryInterface"</code> or <code>"release"</code>). + * @param tid a non-null TID. + * @param arguments a list of UNO arguments compatible with the given + * <code>type</code> and <code>function</code>; may be null to represent + * an empty list. + * @return <code>true</code> if the request message is sent as a synchronous + * request. + */ + boolean writeRequest( + String oid, TypeDescription type, String function, ThreadId tid, + Object[] arguments) + throws IOException; + + /** + * Writes a reply message. + * + * @param exception <code>true</code> if the reply corresponds to a raised + * exception. + * @param tid a non-null TID. + * @param result if <code>exception</code> is <code>true</code>, a non-null + * UNO exception; otherwise, a UNO return value, which may be null to + * represent a <code>VOID</code> return value. + */ + void writeReply(boolean exception, ThreadId tid, Object result) + throws IOException; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ridljar/com/sun/star/lib/uno/environments/remote/IReceiver.java b/ridljar/com/sun/star/lib/uno/environments/remote/IReceiver.java new file mode 100644 index 000000000000..e39ae3d4d50c --- /dev/null +++ b/ridljar/com/sun/star/lib/uno/environments/remote/IReceiver.java @@ -0,0 +1,40 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ + +package com.sun.star.lib.uno.environments.remote; + +/** + * An abstraction for giving back a reply for a request. + * + * @see com.sun.star.uno.IQueryInterface + */ +public interface IReceiver { + /** + * Send back a reply for a request. + * + * @param exception <CODE>true</CODE> if an exception (instead of a normal + * result) is sent back. + * @param threadId the thread ID of the request. + * @param result the result of executing the request, or an exception thrown + * while executing the request. + */ + void sendReply(boolean exception, ThreadId threadId, Object result); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ridljar/com/sun/star/lib/uno/environments/remote/IThreadPool.java b/ridljar/com/sun/star/lib/uno/environments/remote/IThreadPool.java new file mode 100644 index 000000000000..1de31ad04c4c --- /dev/null +++ b/ridljar/com/sun/star/lib/uno/environments/remote/IThreadPool.java @@ -0,0 +1,115 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ + +package com.sun.star.lib.uno.environments.remote; + +/** + * This interface is an abstraction of the various threadpool implementations. + * + * @see com.sun.star.lib.uno.environments.remote.ThreadPoolManager + * @since UDK1.0 + */ +public interface IThreadPool { + /** + * Retrieves the global threadId for the current thread. + * + * @return the thread id. + */ + ThreadId getThreadId(); + + /** + * Attaches this thread to the thread pool. + * + * @see #enter + */ + void attach(); + + /** + * As above, but hands in an already existing instance of the threadid of + * the current thread. + * + * <p>The function exists for performance.</p> + * + * @return Returns a handle which can be used in enter and detach calls. + * @see #attach + */ + Object attach( ThreadId id ); + + /** + * Detaches this thread from the thread pool. + * @see #enter + */ + void detach(); + + /** + * As above, but hands in an already existing instance of the threadid of + * the current thread and a handle returned by attach. + * + * <p>The function exists for performance.</p> + * + * @see #attach() + * @see #detach() + */ + void detach( Object handle, ThreadId id ); + + /** + * Lets this thread enter the thread pool. + * + * <p>This thread then executes all jobs put via <code>putJob</code> until + * a reply job arrives.</p> + * + * @see #putJob + */ + Object enter() throws Throwable; + + /** + * As above but hands in an already existing instance of the threadid of + * the current thread and a handle returned by attach. + * + * <p>This thread then executes all jobs put via <code>putJob</code> until + * a reply job arrives.</p> + * + * @see #putJob + */ + Object enter( Object handle, ThreadId id ) throws Throwable; + + /** + * Queues a job into the jobQueue of the thread belonging to the jobs + * threadId. + * + * @param job the job + */ + void putJob(Job job); + + /** + * Disposes this thread pool, thus releasing all threads by throwing a + * <code>DisposedException</code> with the given <code>Throwable</code> cause. + * + * @param throwable the cause + */ + void dispose(Throwable throwable); + + + /** + * Destroys the thread pool and tries to join all created threads immediately. + */ + void destroy(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ridljar/com/sun/star/lib/uno/environments/remote/JavaThreadPool.java b/ridljar/com/sun/star/lib/uno/environments/remote/JavaThreadPool.java new file mode 100644 index 000000000000..332306be0e3d --- /dev/null +++ b/ridljar/com/sun/star/lib/uno/environments/remote/JavaThreadPool.java @@ -0,0 +1,124 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ + +package com.sun.star.lib.uno.environments.remote; + +/** + * This class implements a java thread pool. + * + * @see com.sun.star.uno.UnoRuntime + * @see com.sun.star.lib.uno.environments.remote.NativeThreadPool + * @see com.sun.star.lib.uno.environments.remote.IThreadPool + * @see com.sun.star.lib.uno.environments.remote.Job + * @see com.sun.star.lib.uno.environments.remote.JobQueue + * @since UDK1.0 + */ +public class JavaThreadPool implements IThreadPool { + /** + * When set to true, enables various debugging output. + */ + private static final boolean DEBUG = false; + + JavaThreadPoolFactory _javaThreadPoolFactory; + + JavaThreadPool(JavaThreadPoolFactory javaThreadPoolFactory) { + _javaThreadPoolFactory = javaThreadPoolFactory; + } + + public ThreadId getThreadId() { + return JavaThreadPoolFactory.getThreadId(); + } + + public Object attach( ThreadId threadId ) + { + if(DEBUG) System.err.println("##### " + getClass().getName() + ".attach - id:" + threadId); + JobQueue jobQueue = _javaThreadPoolFactory.getJobQueue(threadId); + if(jobQueue == null) + jobQueue = new JobQueue(_javaThreadPoolFactory, threadId, false); + + // acquiring the jobQueue registers it at the ThreadPoolFactory + jobQueue.acquire(); + return jobQueue; + } + + public void attach() { + attach( getThreadId() ); + } + + public void detach( Object handle, ThreadId id ) + { + ((JobQueue)handle).release(); + } + + public void detach() { + ThreadId threadId = getThreadId(); + detach(_javaThreadPoolFactory.getJobQueue(threadId), threadId ); + } + + + public Object enter( ) throws Throwable { + ThreadId threadId = getThreadId(); + return enter( _javaThreadPoolFactory.getJobQueue( threadId ), threadId ); + } + + public Object enter( Object handle, ThreadId threadId ) throws Throwable { + return ((JobQueue)handle).enter(this); + } + + public void putJob(Job job) { + if (!job.isRequest() || job.isSynchronous()) { + JobQueue jobQueue = _javaThreadPoolFactory.getJobQueue(job.getThreadId()); + + // this has not be synchronized, cause + // sync jobs can only come over one bridge + // (cause the thread blocks on other side) + if(jobQueue == null) + jobQueue = new JobQueue(_javaThreadPoolFactory, job.getThreadId(), true); + + // put job acquires the queue and registers it at the ThreadPoolFactory + jobQueue.putJob(job, this); + } + else { + // this has to be synchronized, cause + // async jobs of the same thread can come + // over different bridges + synchronized(_javaThreadPoolFactory) { + JobQueue async_jobQueue = _javaThreadPoolFactory.getAsyncJobQueue(job.getThreadId()); + + // ensure there is jobQueue + if(async_jobQueue == null) // so, there is really no async queue + async_jobQueue = new JobQueue(_javaThreadPoolFactory, job.getThreadId()); + + // put job acquires the queue and registers it at the ThreadPoolFactory + async_jobQueue.putJob(job, this); + } + } + } + + public void dispose(Throwable throwable) { + if(DEBUG) System.err.println("##### " + getClass().getName() + ".dispose:" + throwable); + + _javaThreadPoolFactory.dispose(this, throwable); + } + + public void destroy() { + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ridljar/com/sun/star/lib/uno/environments/remote/JavaThreadPoolFactory.java b/ridljar/com/sun/star/lib/uno/environments/remote/JavaThreadPoolFactory.java new file mode 100644 index 000000000000..181a3e17e40a --- /dev/null +++ b/ridljar/com/sun/star/lib/uno/environments/remote/JavaThreadPoolFactory.java @@ -0,0 +1,87 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ + +package com.sun.star.lib.uno.environments.remote; + +import java.util.Collection; +import java.util.HashMap; +import java.util.WeakHashMap; + +final class JavaThreadPoolFactory { + + public IThreadPool createThreadPool() { + return new JavaThreadPool(this); + } + + public void addJobQueue(JobQueue jobQueue) { + synchronized (jobQueues) { + jobQueues.put(jobQueue.getThreadId(), jobQueue); + } + } + + public void removeJobQueue(JobQueue jobQueue) { + synchronized (jobQueues) { + jobQueues.remove(jobQueue.getThreadId()); + } + } + + public JobQueue getJobQueue(ThreadId threadId) { + synchronized (jobQueues) { + return jobQueues.get(threadId); + } + } + + public JobQueue getAsyncJobQueue(ThreadId threadId) { + JobQueue q = getJobQueue(threadId); + return q == null ? null : q._async_jobQueue; + } + + public void dispose(Object disposeId, Throwable throwable) { + JobQueue[] qs; + synchronized (jobQueues) { + Collection<JobQueue> c = jobQueues.values(); + qs = c.toArray(new JobQueue[c.size()]); + } + for (int i = 0; i < qs.length; ++i) { + qs[i].dispose(disposeId, throwable); + } + } + + public static ThreadId getThreadId() { + Thread t = Thread.currentThread(); + if (t instanceof JobQueue.JobDispatcher) { + return ((JobQueue.JobDispatcher) t).getThreadId(); + } else { + ThreadId id; + synchronized (threadIdMap) { + id = threadIdMap.get(t); + if (id == null) { + id = ThreadId.createFresh(); + threadIdMap.put(t, id); + } + } + return id; + } + } + + private static final WeakHashMap<Thread, ThreadId> threadIdMap = new WeakHashMap<Thread, ThreadId>(); + private final HashMap<ThreadId, JobQueue> jobQueues = new HashMap<ThreadId, JobQueue>(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ridljar/com/sun/star/lib/uno/environments/remote/Job.java b/ridljar/com/sun/star/lib/uno/environments/remote/Job.java new file mode 100644 index 000000000000..8ec4492c4c01 --- /dev/null +++ b/ridljar/com/sun/star/lib/uno/environments/remote/Job.java @@ -0,0 +1,163 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ + +package com.sun.star.lib.uno.environments.remote; + + +import java.io.PrintWriter; +import java.io.StringWriter; + +import java.lang.reflect.InvocationTargetException; + +import com.sun.star.lib.uno.typedesc.MethodDescription; +import com.sun.star.uno.Any; +import com.sun.star.uno.Type; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XCurrentContext; + +/** + * The Job is an abstraction for tasks which have to be done + * remotely because of a method invocation. + * + * @see com.sun.star.lib.uno.environments.remote.ThreadId + * @see com.sun.star.lib.uno.environments.remote.IReceiver + * @since UDK1.0 + */ +public class Job { + protected IReceiver _iReceiver; + protected Message _iMessage; + Object _disposeId; + + protected Object _object; + + public Job(Object object, IReceiver iReceiver, Message iMessage) { + _object = object; + _iReceiver = iReceiver; + _iMessage = iMessage; + } + + /** + * Dispatches a <code>queryInterface</code> call. + * + * @return the result of the call (should be an <code>Any</code>). + */ + protected Object dispatch_queryInterface(Type type) { + Class<?> zInterface = type.getTypeDescription().getZClass(); + + Object result = null; + + Object face = UnoRuntime.queryInterface(zInterface, _object); + // the hell knows why, but empty interfaces a given back as void anys + if(face != null) + result = new Any(type, face); + return result; + } + + /** + * Execute the job. + * + * @return the result of the message. + */ + public Object execute() throws Throwable { + if (_iMessage.isRequest()) { + Object result = null; + Throwable exception = null; + MethodDescription md = _iMessage.getMethod(); + Object[] args = _iMessage.getArguments(); + XCurrentContext oldCC = UnoRuntime.getCurrentContext(); + UnoRuntime.setCurrentContext(_iMessage.getCurrentContext()); + try { + result = md.getIndex() == MethodDescription.ID_QUERY_INTERFACE + ? dispatch_queryInterface((Type) args[0]) + : md.getMethod().invoke(_object, args); + } catch (InvocationTargetException e) { + exception = e.getCause(); + if (exception == null) { + exception = e; + } + } catch (Exception e) { + exception = e; + } finally { + UnoRuntime.setCurrentContext(oldCC); + } + if (_iMessage.isSynchronous()) { + if (exception == null) { + _iReceiver.sendReply( + false, _iMessage.getThreadId(), result); + } else { + // Here we have to be aware of non-UNO exceptions, because + // they may kill a remote side which does not know anything + // about their types: + if (!(exception instanceof com.sun.star.uno.Exception) + && !(exception instanceof + com.sun.star.uno.RuntimeException)) + { + StringWriter writer = new StringWriter(); + exception.printStackTrace(new PrintWriter(writer)); + exception = new com.sun.star.uno.RuntimeException( + "Java exception: <" + writer + ">", null); + } + _iReceiver.sendReply( + true, _iMessage.getThreadId(), exception); + } + } + return null; + } else if (_iMessage.isAbnormalTermination()) { + throw remoteUnoRequestRaisedException(_iMessage.getResult()); + } else { + return _iMessage.getResult(); + } + } + + public ThreadId getThreadId() { + return _iMessage.getThreadId(); + } + + public boolean isRequest() { + return _iMessage.isRequest(); + } + + public boolean isSynchronous() { + return _iMessage.isSynchronous(); + } + + public void dispose() { +// _oId = null; +// _iReceiver = null; +// _threadId = null; +// _object = null; +// _operation = null; +// _param = null; +// _exception = null; +// _zInterface = null; +// _disposeId = null; + } + + /** + * The name of this method is chosen to generate a somewhat self-explanatory + * stack trace. + */ + private Exception remoteUnoRequestRaisedException(Object exception) { + Exception e = (Exception) exception; + e.fillInStackTrace(); + return e; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ridljar/com/sun/star/lib/uno/environments/remote/JobQueue.java b/ridljar/com/sun/star/lib/uno/environments/remote/JobQueue.java new file mode 100644 index 000000000000..b71525ba5dd8 --- /dev/null +++ b/ridljar/com/sun/star/lib/uno/environments/remote/JobQueue.java @@ -0,0 +1,373 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ + +package com.sun.star.lib.uno.environments.remote; + +import java.util.ArrayList; + +import com.sun.star.lang.DisposedException; + +/** + * The <code>JobQueue</code> implements a queue for jobs. + * + * <p>For every jobs thread id exists a job queue which is registered + * at the <code>ThreadPool</code>.</p> + * + * <p>A JobQueue is split into a sync job queue and an async job queue. + * The sync job queue is the registered queue, it delegates async jobs + * (put by <code>putjob</code>) into the async queue, which is only + * known by the sync queue.</p> + * + * @see com.sun.star.lib.uno.environments.remote.IThreadPool + * @see com.sun.star.lib.uno.environments.remote.Job + * @see com.sun.star.lib.uno.environments.remote.ThreadId + * @since UDK1.0 + */ +public class JobQueue { + /** + * When set to true, enables various debugging output. + */ + private static final boolean DEBUG = false; + + final ArrayList<Job> jobList = new ArrayList<Job>(); + + private ThreadId _threadId; // the thread id of the queue + protected int _ref_count = 0; // the stack deepness + private boolean _createThread; // create a worker thread, if needed + private boolean _createThread_now; // create a worker thread, if needed + private Thread _worker_thread; // the thread that does the jobs + + private Object _disposeId; // the active dispose id + private Object _doDispose = null; + private Throwable _throwable; + + JobQueue _async_jobQueue; // chaining job queues for asyncs + protected JobQueue _sync_jobQueue; // chaining job queues for syncs + + private boolean _active = false; + + private JavaThreadPoolFactory _javaThreadPoolFactory; + + /** + * A thread for dispatching jobs. + */ + class JobDispatcher extends Thread { + Object _disposeId; + + JobDispatcher(Object disposeId) { + super("JobDispatcher"); + + if(DEBUG) System.err.println("JobQueue$JobDispatcher.<init>:" + _threadId); + + _disposeId = disposeId; + } + + ThreadId getThreadId() { + return _threadId; + } + + @Override + public void run() { + if(DEBUG) System.err.println("ThreadPool$JobDispatcher.run: " + Thread.currentThread()); + + try { + enter(2000, _disposeId); + } catch(Throwable throwable) { + synchronized (JobQueue.this) { + if(!jobList.isEmpty() || _active) { // there was a job in progress, so give a stack + System.err.println(getClass().getName() + " - exception occurred:" + throwable); + throwable.printStackTrace(System.err); + } + } + } + finally { + release(); + } + + if(DEBUG) System.err.println("##### " + getClass().getName() + ".run - exit:" + _threadId); + } + } + + + /** + * Constructs an async job queue with the given thread id which belongs to + * the given sync job queue. + * + * @param threadId the thread id. + * @see com.sun.star.lib.uno.environments.remote.ThreadId + */ + JobQueue(JavaThreadPoolFactory javaThreadPoolFactory, ThreadId threadId) { + _javaThreadPoolFactory = javaThreadPoolFactory; + _threadId = ThreadId.createFresh(); + + _sync_jobQueue = javaThreadPoolFactory.getJobQueue(threadId); + if(_sync_jobQueue == null) { + _sync_jobQueue = new JobQueue(javaThreadPoolFactory, threadId, true); + _sync_jobQueue.acquire(); + } + + _sync_jobQueue._async_jobQueue = this; + + _createThread = true; + _createThread_now = true; + + acquire(); + + if(DEBUG) System.err.println("##### " + getClass().getName() + " - init:" + _threadId); + } + + /** + * Constructs a sync job queue with the given thread id and the given thread. + * + * @param threadId the thread id. + * @param createThread if true, the queue creates a worker thread if needed. + * @see com.sun.star.lib.uno.environments.remote.ThreadId + */ + JobQueue(JavaThreadPoolFactory javaThreadPoolFactory, ThreadId threadId, boolean createThread){ + _javaThreadPoolFactory = javaThreadPoolFactory; + _threadId = threadId; + _createThread = createThread; + _createThread_now = createThread; + + if(DEBUG) System.err.println("##### " + getClass().getName() + " - init:" + _threadId + " " + createThread); + } + + /** + * Gives the thread id of this queue. + * + * @return the thread id. + * @see com.sun.star.lib.uno.environments.remote.ThreadId + */ + ThreadId getThreadId() { + return _threadId; + } + + synchronized void acquire() { + // add only synchronous queues . + if(_ref_count <= 0 && _sync_jobQueue == null ) + _javaThreadPoolFactory.addJobQueue(this); + + ++ _ref_count; + } + + synchronized void release() { + -- _ref_count; + + if(_ref_count <= 0) { + // only synchronous queues needs to be removed . + if( _sync_jobQueue == null ) + _javaThreadPoolFactory.removeJobQueue(this); + + + if(_sync_jobQueue != null) { + _sync_jobQueue._async_jobQueue = null; + _sync_jobQueue.release(); + } + } + } + + /** + * Removes a job from the queue. + * + * @param waitTime the maximum amount of time to wait for a job. + * @return a job or null if timed out. + */ + private Job removeJob(int waitTime) { + if(DEBUG) System.err.println("##### " + getClass().getName() + ".removeJob:" + jobList + " " + _threadId); + + Job job = null; + synchronized (this) { + // wait max. waitTime time for a job to enter the queue + boolean waited = false; + while(jobList.isEmpty() && (waitTime == 0 || !waited)) { + if(_doDispose == _disposeId) { + _doDispose = null; + throw (DisposedException) + new DisposedException().initCause(_throwable); + } + + // notify sync queues + notifyAll(); + + try { + // wait for new job + wait(waitTime); + } catch(InterruptedException interruptedException) { + throw new com.sun.star.uno.RuntimeException(getClass().getName() + ".removeJob - unexpected:" + interruptedException); + } + + // signal that we have already waited once + waited = true; + } + + + if(!jobList.isEmpty()) { + job = jobList.remove(0); + _active = true; + } + } + + // always wait for asynchron jobqueue to be finished ! + if(job != null && _async_jobQueue != null) { + synchronized(_async_jobQueue) { + // wait for async queue to be empty and last job to be done + while(_async_jobQueue._active || !_async_jobQueue.jobList.isEmpty()) { + if(DEBUG) System.err.println("waiting for async:" + _async_jobQueue.jobList + " " + _async_jobQueue._worker_thread); + + if(_doDispose == _disposeId) { + _doDispose = null; + throw (DisposedException) + new DisposedException().initCause(_throwable); + } + + try { + _async_jobQueue.wait(); + } catch(InterruptedException interruptedException) { + throw new com.sun.star.uno.RuntimeException(getClass().getName() + ".removeJob - unexpected:" + interruptedException); + } + } + } + } + + return job; + } + + /** + * Puts a job into the queue. + * + * @param job the job. + * @param disposeId a dispose id. + */ + synchronized void putJob(Job job, Object disposeId) { + if(DEBUG) System.err.println("##### " + getClass().getName() + ".putJob todoes: " + " job:" + job); + + jobList.add(job); + + if(_worker_thread == null && _createThread && _createThread_now) { // if there is no thread, which dispatches and if shall create one, create one + + acquire(); + + _createThread_now = false; + new JobDispatcher(disposeId).start(); + } + + // always notify possible waiters + notifyAll(); + } + + /** + * Enters the job queue. + * + * @param disposeId a dispose id. + * @return the result of the final job (reply). + */ + Object enter(Object disposeId) throws Throwable { + return enter(0, disposeId); // wait infinitely + } + + /** + * Enters the job queue. + * + * @param waitTime the maximum amount of time to wait for a job (0 means wait infinitely). + * @param disposeId a dispose id. + * @return the result of the final job (reply). + */ + Object enter(int waitTime, Object disposeId) throws Throwable { + if(DEBUG) System.err.println("#####" + getClass().getName() + ".enter: " + _threadId); + + boolean quit = false; + + Object hold_disposeId = _disposeId; + _disposeId = disposeId; + + Object result = null; + + Thread hold_worker_thread = _worker_thread; + _worker_thread = Thread.currentThread(); + + while(!quit) { + Job job = null; + + try { + job = removeJob(waitTime); + + if(job != null) { + try { + result = job.execute(); + } + finally { + _active = false; + } + + if (!job.isRequest()) { + job.dispose(); + + quit = true; + } + + job = null; + } + else + quit = true; + + + } + finally { // ensure that this queue becomes disposed, if necessary + if(DEBUG) System.err.println("##### " + getClass().getName() + ".enter leaving: " + _threadId + " " + _worker_thread + " " + hold_worker_thread + " " + result); + + synchronized(this) { + if(job != null || (quit && jobList.isEmpty())) { + _worker_thread = hold_worker_thread; + + _createThread_now = true; + + _disposeId = hold_disposeId; + + if(_sync_jobQueue != null) + notifyAll(); // notify waiters (e.g. this is an asyncQueue and there is a sync waiting) + } + else + quit = false; + + } + } + } + + return result; + } + + /** + * If the given disposeId is registered, interrupts the worker thread. + * + * @param disposeId the dispose id. + */ + synchronized void dispose(Object disposeId, Throwable throwable) { + if(_sync_jobQueue == null) { // dispose only sync queues + _doDispose = disposeId; + _throwable = throwable; + + // get thread out of wait and let it throw the throwable + if(DEBUG) System.err.println(getClass().getName() + ".dispose - notifying thread"); + + notifyAll(); + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ridljar/com/sun/star/lib/uno/environments/remote/Message.java b/ridljar/com/sun/star/lib/uno/environments/remote/Message.java new file mode 100644 index 000000000000..b34295ae33be --- /dev/null +++ b/ridljar/com/sun/star/lib/uno/environments/remote/Message.java @@ -0,0 +1,189 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ + +package com.sun.star.lib.uno.environments.remote; + +import com.sun.star.lib.uno.typedesc.MethodDescription; +import com.sun.star.lib.uno.typedesc.TypeDescription; +import com.sun.star.uno.XCurrentContext; + +/** + * A remote request or reply message. + */ +public class Message { + public Message( + ThreadId threadId, boolean request, String objectId, + TypeDescription type, MethodDescription method, boolean synchronous, + XCurrentContext currentContext, boolean abnormalTermination, + Object result, Object[] arguments) + { + this.threadId = threadId; + this.request = request; + this.objectId = objectId; + this.type = type; + this.method = method; + this.synchronous = synchronous; + this.currentContext = currentContext; + this.abnormalTermination = abnormalTermination; + this.result = result; + this.arguments = arguments; + } + + /** + * Returns the thread ID of the message. + * + * <p>Valid for all kinds of messages.</p> + * + * @return the (non-<code>null</code>) thread ID. + */ + public final ThreadId getThreadId() { + return threadId; + } + + /** + * Returns whether the message is a request or a reply. + * + * <p>Valid for all kinds of messages.</p> + * + * @return <code>true</code> for a request, <code>false</code> for a reply. + */ + public final boolean isRequest() { + return request; + } + + /** + * Returns the object ID of a request message. + * + * <p>Valid only for request messages.</p> + * + * @return the (non-<code>null</code>) object ID for a request, + * <code>null</code> for a reply. + */ + public final String getObjectId() { + return objectId; + } + + /** + * Returns the type of a request message. + * + * <p>Valid only for request messages.</p> + * + * @return the (non-<code>null</code>) type for a request, <code>null</code> + * for a reply. + */ + public final TypeDescription getType() { + return type; + } + + /** + * Returns the method description of a request message. + * + * <p>Valid only for request messages. The returned + * <code>MethodDescription</code> is consistent with the type of the + * message.</p> + * + * @return the (non-<code>null</code>) method description for a request, + * <code>null</code> for a reply. + */ + public final MethodDescription getMethod() { + return method; + } + + /** + * Returns whether the request message is synchronous. + * + * <p>Valid only for request messages.</p> + * + * @return <code>true</code> for a synchronous request, <code>false</code> + * for an asynchronous request or a reply. + */ + public final boolean isSynchronous() { + return synchronous; + } + + /** + * Returns the current context of a request message. + * + * <p>Valid only for request messages.</p> + * + * @return the current context (which may be <code>null</code>) for a + * request, <code>null</code> for a reply. + */ + public XCurrentContext getCurrentContext() { + return currentContext; + } + + /** + * Returns whether the reply message represents abnormal termination. + * + * <p>Valid only for reply messages.</p> + * + * @return <code>true</code> for a reply that represents abnormal + * termination, <code>false</code> for a reply that represents normal + * termination or a request. + */ + public final boolean isAbnormalTermination() { + return abnormalTermination; + } + + /** + * Returns the result of a reply message. + * + * <p>Valid only for reply messages.</p> + * + * @return any (possibly <code>null</code>) return value for a reply that + * represents normal termination, the (non-<code>null</code>) exception for + * a reply that represents abnormal termination, <code>null</code> for a + * request. + */ + public final Object getResult() { + return result; + } + + /** + * Returns the arguments of a message. + * + * <p>Valid only for request messages and reply messages that represent + * normal termination. Any returned array must not be modified.</p> + * + * @return the in and in– { + * }out arguments for a request (possibly + * <code>null</code> for a parameterless function), the out and in– { + * }out + * arguments for a reply that represents normal termination (possibly + * <code>null</code> for a parameterless function), <code>null</code> for a + * reply that represents abnormal termination. + */ + public final Object[] getArguments() { + return arguments; + } + + private final ThreadId threadId; + private final boolean request; + private final String objectId; + private final TypeDescription type; + private final MethodDescription method; + private final boolean synchronous; + private final XCurrentContext currentContext; + private final boolean abnormalTermination; + private final Object result; + private final Object[] arguments; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ridljar/com/sun/star/lib/uno/environments/remote/NativeThreadPool.java b/ridljar/com/sun/star/lib/uno/environments/remote/NativeThreadPool.java new file mode 100644 index 000000000000..f77bbb466d64 --- /dev/null +++ b/ridljar/com/sun/star/lib/uno/environments/remote/NativeThreadPool.java @@ -0,0 +1,94 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ + +package com.sun.star.lib.uno.environments.remote; + +final class NativeThreadPool implements IThreadPool { + public NativeThreadPool() { + pool = create(); + } + + public ThreadId getThreadId() { + return new ThreadId(threadId()); + } + + public void attach() { + attach(pool); + } + + public Object attach(ThreadId id) { + attach(); + return null; + } + + public void detach() { + detach(pool); + } + + public void detach(Object handle, ThreadId id) { + detach(); + } + + public Object enter() throws Throwable { + Job job = enter(pool); + if (job == null) { + throw dispose; + } + return job.execute(); + } + + public Object enter(Object handle, ThreadId id) throws Throwable { + return enter(); + } + + public void putJob(Job job) { + putJob( + pool, job.getThreadId().getBytes(), job, job.isRequest(), + job.isRequest() && !job.isSynchronous()); + } + + public void dispose(Throwable throwable) { + dispose = throwable; + dispose(pool); + } + + public void destroy() { + destroy(pool); + } + + // The native implementation is in + // bridges/source/jni_uno/nativethreadpool.cxx: + static { + System.loadLibrary("java_uno"); + } + private static native byte[] threadId(); + private static native long create(); + private static native void attach(long pool); + private static native Job enter(long pool); + private static native void detach(long pool); + private static native void putJob( + long pool, byte[] threadId, Job job, boolean request, boolean oneWay); + private static native void dispose(long pool); + private static native void destroy(long pool); + + private final long pool; + private volatile Throwable dispose; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ridljar/com/sun/star/lib/uno/environments/remote/ThreadId.java b/ridljar/com/sun/star/lib/uno/environments/remote/ThreadId.java new file mode 100644 index 000000000000..f8dacc78cc48 --- /dev/null +++ b/ridljar/com/sun/star/lib/uno/environments/remote/ThreadId.java @@ -0,0 +1,109 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ + +package com.sun.star.lib.uno.environments.remote; + +import java.io.UnsupportedEncodingException; +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicLong; + +import com.sun.star.uno.UnoRuntime; + +public final class ThreadId { + public static ThreadId createFresh() { + long c = count.getAndIncrement(); + try { + return new ThreadId((PREFIX + c).getBytes("UTF-8")); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("this cannot happen: " + e); + } + } + + public ThreadId(byte[] id) { + this.id = id; + } + + /** + * Indicates whether some other object is equal to this one. + * + * @param obj the reference object with which to compare. + * @return <code>true</code> if this object is the same as the obj argument; + * <code>false</code> otherwise. + * + * @see java.lang.Object#equals + */ + @Override + public boolean equals(Object obj) { + return obj instanceof ThreadId + && Arrays.equals(id, ((ThreadId) obj).id); + } + + /** + * Returns a hash code value for the object. + * + * @return a hash code value for this object. + * @see java.lang.Object#hashCode + */ + @Override + public int hashCode() { + int h = hash; + if (h == 0) { + // Same algorithm as java.util.List.hashCode (also see Java 1.5 + // java.util.Arrays.hashCode(byte[])): + h = 1; + for (int i = 0; i < id.length; ++i) { + h = 31 * h + id[i]; + } + hash = h; + } + return h; + } + + /** + * Returns a string representation of the object. + * + * @return a string representation of the object. + * @see java.lang.Object#toString + */ + @Override + public String toString() { + StringBuffer b = new StringBuffer("[ThreadId:"); + for (int i = 0; i < id.length; ++i) { + String n = Integer.toHexString(id[i] & 0xFF); + if (n.length() == 1) { + b.append('0'); + } + b.append(n); + } + b.append(']'); + return b.toString(); + } + + public byte[] getBytes() { + return id; + } + + private static final String PREFIX = "java:" + UnoRuntime.getUniqueKey() + ":"; + private static final AtomicLong count = new AtomicLong(0); + + private final byte[] id; + private int hash = 0; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ridljar/com/sun/star/lib/uno/environments/remote/ThreadPoolManager.java b/ridljar/com/sun/star/lib/uno/environments/remote/ThreadPoolManager.java new file mode 100644 index 000000000000..2014f51674d6 --- /dev/null +++ b/ridljar/com/sun/star/lib/uno/environments/remote/ThreadPoolManager.java @@ -0,0 +1,74 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ + +package com.sun.star.lib.uno.environments.remote; + +/** + * Manages the UNO thread pool factory. + * + * <P>The thread pool factory is a process-wide resource. It is important that + * all UNO environments within a process share the same thread pool mechanisms: + * if a synchronous UNO call is bridged out from one local UNO environment over + * one remote bridge, and recursively calls back into another local UNO + * environment over another remote bridge, the code in the second environment + * should be executed in the thread that did the original call from the first + * environment.</P> + * + * <P>There are both a Java and a native thread pool factory. A pure Java + * process will always use the Java thread pool factory. A mixed process uses + * the system property <CODE>org.openoffice.native</CODE> (to be set by the + * native code that starts the JVM) to determine which implementation + * to use.</P> + */ +public final class ThreadPoolManager { + /** + * Creates a thread pool instance. + * + * @return a new thread pool instance; will never be <CODE>null</CODE>. + */ + public static synchronized IThreadPool create() { + if (useNative) { + return new NativeThreadPool(); + } else { + if (javaFactory == null) { + javaFactory = new JavaThreadPoolFactory(); + } + return javaFactory.createThreadPool(); + } + } + + /** + * Leads to using the native thread pool factory, unless a Java thread pool + * has already been created. + * + * @return <CODE>false</CODE> if a Java thread pool has already been created. + */ + public static synchronized boolean useNative() { + useNative = javaFactory == null; + return useNative; + } + + private static boolean useNative + = System.getProperty("org.openoffice.native") != null; + private static JavaThreadPoolFactory javaFactory = null; + + private ThreadPoolManager() {} // do not instantiate +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ridljar/com/sun/star/lib/uno/environments/remote/remote_environment.java b/ridljar/com/sun/star/lib/uno/environments/remote/remote_environment.java new file mode 100644 index 000000000000..09259c8cb0f5 --- /dev/null +++ b/ridljar/com/sun/star/lib/uno/environments/remote/remote_environment.java @@ -0,0 +1,66 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ + +package com.sun.star.lib.uno.environments.remote; + +import com.sun.star.uno.IEnvironment; +import com.sun.star.uno.Type; + +public final class remote_environment implements IEnvironment { + public remote_environment(Object context) { + this.context = context; + } + + public Object getContext() { + return context; + } + + public String getName() { + return "remote"; + } + + public Object registerInterface(Object object, String[] oid, Type type) { + throw new UnsupportedOperationException( + "java_remote environment is not functional"); + } + + public void revokeInterface(String oid, Type type) { + throw new UnsupportedOperationException( + "java_remote environment is not functional"); + } + + public Object getRegisteredInterface(String oid, Type type) { + throw new UnsupportedOperationException( + "java_remote environment is not functional"); + } + + public String getRegisteredObjectIdentifier(Object object) { + throw new UnsupportedOperationException( + "java_remote environment is not functional"); + } + + public void list() { + throw new UnsupportedOperationException( + "java_remote environment is not functional"); + } + + private final Object context; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ridljar/com/sun/star/lib/uno/protocols/urp/Cache.java b/ridljar/com/sun/star/lib/uno/protocols/urp/Cache.java new file mode 100644 index 000000000000..26c2d449d617 --- /dev/null +++ b/ridljar/com/sun/star/lib/uno/protocols/urp/Cache.java @@ -0,0 +1,114 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ + +package com.sun.star.lib.uno.protocols.urp; + +import java.util.HashMap; + +/** + * An LRU cache for arbitrary objects. + * + * <p>This class is not synchronized, as any necessary synchronization will already + * take place in the client.</p> + */ +final class Cache { + /** + * Create a cache. + * + * @param size the maximum cache size, must be between 0, inclusive, and + * NOT_CACHED, exclusive. + */ + public Cache(int size) { + maxSize = size; + } + + public int add(boolean[] found, Object content) { + Entry e = map.get(content); + found[0] = e != null; + if (e == null) { + if (map.size() < maxSize) { + // There is still room for a new entry at the front: + e = new Entry(content, map.size(), null, first); + if (first == null) { + last = e; + } else { + first.prev = e; + } + first = e; + } else if (last != null) { + // Take last entry out and recycle as new front: + map.remove(last.content); + e = last; + e.content = content; + if (first != last) { + // Reached only if maxSize > 1: + last = last.prev; + last.next = null; + e.prev = null; + e.next = first; + first.prev = e; + first = e; + } + } else { + // Reached iff maxSize == 0: + return NOT_CACHED; + } + map.put(content, e); + } else if (e != first) { + // Move to front (reached only if maxSize > 1): + e.prev.next = e.next; + if (e.next == null) { + last = e.prev; + } else { + e.next.prev = e.prev; + } + e.prev = null; + e.next = first; + first.prev = e; + first = e; + } + return e.index; + } + + public static final int NOT_CACHED = 0xFFFF; + + private static final class Entry { + public Entry(Object content, int index, Entry prev, Entry next) { + this.content = content; + this.index = index; + this.prev = prev; + this.next = next; + } + + public Object content; + public int index; + public Entry prev; + public Entry next; + } + + // first/last form a list of 0 to maxSize entries, most recently used first; + // map contains the same entries; each entry has a unique index in the range + // 0 to maxSize - 1 + private final int maxSize; + private final HashMap<Object, Entry> map = new HashMap<Object, Entry>(); // from Object to Entry + private Entry first = null; + private Entry last = null; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ridljar/com/sun/star/lib/uno/protocols/urp/Marshal.java b/ridljar/com/sun/star/lib/uno/protocols/urp/Marshal.java new file mode 100644 index 000000000000..106a8736cb31 --- /dev/null +++ b/ridljar/com/sun/star/lib/uno/protocols/urp/Marshal.java @@ -0,0 +1,355 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ +package com.sun.star.lib.uno.protocols.urp; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutput; +import java.io.DataOutputStream; +import java.io.IOException; +import java.lang.reflect.Array; +import java.lang.reflect.InvocationTargetException; + +import com.sun.star.lib.uno.environments.remote.ThreadId; +import com.sun.star.lib.uno.typedesc.FieldDescription; +import com.sun.star.lib.uno.typedesc.TypeDescription; +import com.sun.star.uno.Any; +import com.sun.star.uno.Enum; +import com.sun.star.uno.IBridge; +import com.sun.star.uno.Type; +import com.sun.star.uno.TypeClass; +import com.sun.star.uno.XInterface; + +final class Marshal { + public Marshal(IBridge bridge, short cacheSize) { + this.bridge = bridge; + objectIdCache = new Cache(cacheSize); + threadIdCache = new Cache(cacheSize); + typeCache = new Cache(cacheSize); + } + + public void write8Bit(int value) { + try { + output.writeByte(value); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void write16Bit(int value) { + try { + output.writeShort(value); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void writeObjectId(String objectId) { + try { + if (objectId == null) { + writeStringValue(null); + write16Bit(0xFFFF); + } else { + boolean[] found = new boolean[1]; + int index = objectIdCache.add(found, objectId); + writeStringValue(found[0] ? null : objectId); + write16Bit(index); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void writeInterface(XInterface object, Type type) { + writeObjectId((String) bridge.mapInterfaceTo(object, type)); + } + + public void writeThreadId(ThreadId threadId) { + try { + byte[] data = threadId.getBytes(); + boolean[] found = new boolean[1]; + int index = threadIdCache.add(found, data); + if (found[0]) { + writeCompressedNumber(0); + } else { + writeCompressedNumber(data.length); + writeBytes(data); + } + write16Bit(index); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void writeType(TypeDescription type) { + try { + TypeClass typeClass = type.getTypeClass(); + if (TypeDescription.isTypeClassSimple(typeClass)) { + write8Bit(typeClass.getValue()); + } else { + boolean[] found = new boolean[1]; + int index = typeCache.add(found, type.getTypeName()); + write8Bit(typeClass.getValue() | (found[0] ? 0 : 0x80)); + write16Bit(index); + if (!found[0]) { + writeStringValue(type.getTypeName()); + } + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void writeValue(TypeDescription type, Object value) { + try { + switch(type.getTypeClass().getValue()) { + case TypeClass.VOID_value: + break; + + case TypeClass.BOOLEAN_value: + writeBooleanValue((Boolean) value); + break; + + case TypeClass.BYTE_value: + writeByteValue((Byte) value); + break; + + case TypeClass.SHORT_value: + case TypeClass.UNSIGNED_SHORT_value: + writeShortValue((Short) value); + break; + + case TypeClass.LONG_value: + case TypeClass.UNSIGNED_LONG_value: + writeLongValue((Integer) value); + break; + + case TypeClass.HYPER_value: + case TypeClass.UNSIGNED_HYPER_value: + writeHyperValue((Long) value); + break; + + case TypeClass.FLOAT_value: + writeFloatValue((Float) value); + break; + + case TypeClass.DOUBLE_value: + writeDoubleValue((Double) value); + break; + + case TypeClass.CHAR_value: + writeCharValue((Character) value); + break; + + case TypeClass.STRING_value: + writeStringValue((String) value); + break; + + case TypeClass.TYPE_value: + writeTypeValue((Type) value); + break; + + case TypeClass.ANY_value: + writeAnyValue(value); + break; + + case TypeClass.SEQUENCE_value: + writeSequenceValue(type, value); + break; + + case TypeClass.ENUM_value: + writeEnumValue(type, (Enum) value); + break; + + case TypeClass.STRUCT_value: + writeStructValue(type, value); + break; + + case TypeClass.EXCEPTION_value: + writeExceptionValue(type, (Exception) value); + break; + + case TypeClass.INTERFACE_value: + writeInterfaceValue(type, (XInterface) value); + break; + + default: + throw new IllegalArgumentException("Bad type descriptor " + type); + } + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + + public byte[] reset() { + byte[] data = buffer.toByteArray(); + buffer.reset(); + return data; + } + + private void writeBooleanValue(Boolean value) throws IOException { + output.writeBoolean(value != null && value.booleanValue()); + } + + private void writeByteValue(Byte value) { + write8Bit(value == null ? 0 : value.byteValue()); + } + + private void writeShortValue(Short value) { + write16Bit(value == null ? 0 : value.shortValue()); + } + + private void writeLongValue(Integer value) throws IOException { + write32Bit(value == null ? 0 : value.intValue()); + } + + private void writeHyperValue(Long value) throws IOException { + output.writeLong(value == null ? 0 : value.longValue()); + } + + private void writeFloatValue(Float value) throws IOException { + output.writeFloat(value == null ? 0 : value.floatValue()); + } + + private void writeDoubleValue(Double value) throws IOException { + output.writeDouble(value == null ? 0 : value.doubleValue()); + } + + private void writeCharValue(Character value) throws IOException { + output.writeChar(value == null ? 0 : value.charValue()); + } + + private void writeStringValue(String value) throws IOException { + if (value == null) { + writeCompressedNumber(0); + } else { + byte[] data = value.getBytes("UTF8"); + writeCompressedNumber(data.length); + writeBytes(data); + } + } + + private void writeTypeValue(Type value) throws ClassNotFoundException { + writeType( + TypeDescription.getTypeDescription( + value == null ? Type.VOID : value)); + } + + private void writeAnyValue(Object value) throws ClassNotFoundException { + TypeDescription type; + if (value == null || value instanceof XInterface) { + type = TypeDescription.getTypeDescription(XInterface.class); + } else if (value instanceof Any) { + Any any = (Any) value; + type = TypeDescription.getTypeDescription(any.getType()); + value = any.getObject(); + } else if (value.getClass() == Object.class) { + // Avoid StackOverflowError: + throw new IllegalArgumentException( + "Object instance does not represent UNO value"); + } else { + type = TypeDescription.getTypeDescription(value.getClass()); + } + writeType(type); + writeValue(type, value); + } + + private void writeSequenceValue(TypeDescription type, Object value) throws IOException { + if (value == null) { + writeCompressedNumber(0); + } else { + TypeDescription ctype = type.getComponentType(); + if (ctype.getTypeClass() == TypeClass.BYTE) { + byte[] data = (byte[]) value; + writeCompressedNumber(data.length); + writeBytes(data); + } else { + int len = Array.getLength(value); + writeCompressedNumber(len); + for (int i = 0; i < len; ++i) { + writeValue(ctype, Array.get(value, i)); + } + } + } + } + + private void writeEnumValue(TypeDescription type, Enum value) throws IllegalAccessException, IOException, InvocationTargetException, NoSuchMethodException { + int n; + if (value == null) { + n = ((Enum) + (type.getZClass().getMethod("getDefault", (Class[]) null). + invoke(null, (Object[]) null))). + getValue(); + } else { + n = value.getValue(); + } + write32Bit(n); + } + + private void writeStructValue(TypeDescription type, Object value) throws IllegalAccessException { + FieldDescription[] fields = type.getFieldDescriptions(); + for (int i = 0; i < fields.length; ++i) { + writeValue( + fields[i].getTypeDescription(), + value == null ? null : fields[i].getField().get(value)); + } + } + + private void writeExceptionValue(TypeDescription type, Exception value) throws IllegalAccessException, IOException { + writeStringValue(value == null ? null : value.getMessage()); + writeStructValue(type, value); + } + + private void writeInterfaceValue(TypeDescription type, XInterface value) { + writeInterface(value, new Type(type)); + } + + private void write32Bit(int value) throws IOException { + output.writeInt(value); + } + + private void writeCompressedNumber(int number) throws IOException { + if (number >= 0 && number < 0xFF) { + write8Bit(number); + } else { + write8Bit(0xFF); + write32Bit(number); + } + } + + private void writeBytes(byte[] data) throws IOException { + output.write(data); + } + + private final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + private final DataOutput output = new DataOutputStream(buffer); + private final IBridge bridge; + private final Cache objectIdCache; + private final Cache threadIdCache; + private final Cache typeCache; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ridljar/com/sun/star/lib/uno/protocols/urp/PendingRequests.java b/ridljar/com/sun/star/lib/uno/protocols/urp/PendingRequests.java new file mode 100644 index 000000000000..928cdcd63176 --- /dev/null +++ b/ridljar/com/sun/star/lib/uno/protocols/urp/PendingRequests.java @@ -0,0 +1,65 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ + +package com.sun.star.lib.uno.protocols.urp; + +import java.util.HashMap; +import java.util.Stack; + +import com.sun.star.lib.uno.environments.remote.ThreadId; +import com.sun.star.lib.uno.typedesc.MethodDescription; + +final class PendingRequests { + + public synchronized void push(ThreadId tid, Item item) { + Stack<Item> s = map.get(tid); + if (s == null) { + s = new Stack<Item>(); + map.put(tid, s); + } + s.push(item); + } + + public synchronized Item pop(ThreadId tid) { + Stack<Item> s = map.get(tid); + Item i = s.pop(); + if (s.empty()) { + map.remove(tid); + } + return i; + } + + public static final class Item { + public Item( + boolean internal, MethodDescription function, Object[] arguments) + { + this.internal = internal; + this.function = function; + this.arguments = arguments; + } + + public final boolean internal; + public final MethodDescription function; + public final Object[] arguments; + } + + private final HashMap<ThreadId, Stack<Item>> map = new HashMap<ThreadId, Stack<Item>>(); // from ThreadId to Stack of Item +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ridljar/com/sun/star/lib/uno/protocols/urp/Unmarshal.java b/ridljar/com/sun/star/lib/uno/protocols/urp/Unmarshal.java new file mode 100644 index 000000000000..c16d19291356 --- /dev/null +++ b/ridljar/com/sun/star/lib/uno/protocols/urp/Unmarshal.java @@ -0,0 +1,476 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ +package com.sun.star.lib.uno.protocols.urp; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Array; +import java.lang.reflect.InvocationTargetException; + +import com.sun.star.lib.uno.environments.remote.ThreadId; +import com.sun.star.lib.uno.typedesc.TypeDescription; +import com.sun.star.uno.Any; +import com.sun.star.uno.Enum; +import com.sun.star.uno.IBridge; +import com.sun.star.uno.Type; +import com.sun.star.uno.TypeClass; +import com.sun.star.uno.XInterface; +import com.sun.star.lib.uno.typedesc.FieldDescription; + +final class Unmarshal { + public Unmarshal(IBridge bridge, int cacheSize) { + this.bridge = bridge; + objectIdCache = new String[cacheSize]; + threadIdCache = new ThreadId[cacheSize]; + typeCache = new TypeDescription[cacheSize]; + reset(new byte[0]); + } + + public int read8Bit() { + try { + return input.readUnsignedByte(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public int read16Bit() { + try { + return input.readUnsignedShort(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public String readObjectId() { + try { + String id = readStringValue(); + int index = read16Bit(); + if (index == 0xFFFF) { + if (id.length() == 0) { + id = null; + } + } else { + if (id.length() == 0) { + id = objectIdCache[index]; + } else { + objectIdCache[index] = id; + } + } + return id; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public Object readInterface(Type type) { + String id = readObjectId(); + return id == null ? null : bridge.mapInterfaceFrom(id, type); + } + + public ThreadId readThreadId() { + try { + int len = readCompressedNumber(); + byte[] data ; + ThreadId id = null; + if (len != 0) { + data = new byte[len]; + readBytes(data); + id = new ThreadId(data); + } + int index = read16Bit(); + if (index != 0xFFFF) { + if (len == 0) { + id = threadIdCache[index]; + } else { + threadIdCache[index] = id; + } + } + return id; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public TypeDescription readType() { + int b = read8Bit(); + TypeClass typeClass = TypeClass.fromInt(b & 0x7F); + if (typeClass == null) { + throw new RuntimeException( + "Reading TYPE with bad type class " + (b & 0x7F)); + } + if (TypeDescription.isTypeClassSimple(typeClass)) { + if ((b & 0x80) != 0) { + throw new RuntimeException( + "Reading TYPE with bad type class/cache flag " + b); + } + return TypeDescription.getTypeDescription(typeClass); + } else { + int index = read16Bit(); + TypeDescription type; + if ((b & 0x80) == 0) { + if (index >= typeCache.length) { + throw new RuntimeException( + "Reading TYPE with bad cache index " + index); + } + type = typeCache[index]; + if (type == null) { + throw new RuntimeException( + "Reading TYPE with empty cache index " + index); + } + } else { + try { + type = TypeDescription.getTypeDescription( + readStringValue()); + } catch (IOException e) { + throw new RuntimeException(e); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + if (index != 0xFFFF) { + if (index >= typeCache.length) { + throw new RuntimeException( + "Reading TYPE with bad cache index " + index); + } + typeCache[index] = type; + } + } + return type; + } + } + + public Object readValue(TypeDescription type) { + try { + switch (type.getTypeClass().getValue()) { + case TypeClass.VOID_value: + return null; + + case TypeClass.BOOLEAN_value: + return readBooleanValue(); + + case TypeClass.BYTE_value: + return readByteValue(); + + case TypeClass.SHORT_value: + case TypeClass.UNSIGNED_SHORT_value: + return readShortValue(); + + case TypeClass.LONG_value: + case TypeClass.UNSIGNED_LONG_value: + return readLongValue(); + + case TypeClass.HYPER_value: + case TypeClass.UNSIGNED_HYPER_value: + return readHyperValue(); + + case TypeClass.FLOAT_value: + return readFloatValue(); + + case TypeClass.DOUBLE_value: + return readDoubleValue(); + + case TypeClass.CHAR_value: + return readCharValue(); + + case TypeClass.STRING_value: + return readStringValue(); + + case TypeClass.TYPE_value: + return readTypeValue(); + + case TypeClass.ANY_value: + return readAnyValue(); + + case TypeClass.SEQUENCE_value: + return readSequenceValue(type); + + case TypeClass.ENUM_value: + return readEnumValue(type); + + case TypeClass.STRUCT_value: + return readStructValue(type); + + case TypeClass.EXCEPTION_value: + return readExceptionValue(type); + + case TypeClass.INTERFACE_value: + return readInterfaceValue(type); + + default: + throw new IllegalArgumentException("Bad type descriptor " + type); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public boolean hasMore() { + try { + return input.available() > 0; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void reset(byte[] data) { + input = new DataInputStream(new ByteArrayInputStream(data)); + } + + private Boolean readBooleanValue() throws IOException { + return input.readBoolean() ? Boolean.TRUE : Boolean.FALSE; + } + + private Byte readByteValue() throws IOException { + return Byte.valueOf(input.readByte()); + } + + private Short readShortValue() throws IOException { + return Short.valueOf(input.readShort()); + } + + private Integer readLongValue() throws IOException { + return Integer.valueOf(input.readInt()); + } + + private Long readHyperValue() throws IOException { + return Long.valueOf(input.readLong()); + } + + private Float readFloatValue() throws IOException { + return new Float(input.readFloat()); + } + + private Double readDoubleValue() throws IOException { + return new Double(input.readDouble()); + } + + private Character readCharValue() throws IOException { + return new Character(input.readChar()); + } + + private String readStringValue() throws IOException { + int len = readCompressedNumber(); + byte[] data = new byte[len]; + readBytes(data); + try { + return new String(data, "UTF8"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + private Type readTypeValue() { + return new Type(readType()); + } + + private Object readAnyValue() throws IOException { + TypeDescription type = readType(); + switch (type.getTypeClass().getValue()) { + case TypeClass.VOID_value: + return Any.VOID; + + case TypeClass.BOOLEAN_value: + return readBooleanValue(); + + case TypeClass.BYTE_value: + return readByteValue(); + + case TypeClass.SHORT_value: + return readShortValue(); + + case TypeClass.UNSIGNED_SHORT_value: + return new Any(Type.UNSIGNED_SHORT, readShortValue()); + + case TypeClass.LONG_value: + return readLongValue(); + + case TypeClass.UNSIGNED_LONG_value: + return new Any(Type.UNSIGNED_LONG, readLongValue()); + + case TypeClass.HYPER_value: + return readHyperValue(); + + case TypeClass.UNSIGNED_HYPER_value: + return new Any(Type.UNSIGNED_HYPER, readHyperValue()); + + case TypeClass.FLOAT_value: + return readFloatValue(); + + case TypeClass.DOUBLE_value: + return readDoubleValue(); + + case TypeClass.CHAR_value: + return readCharValue(); + + case TypeClass.STRING_value: + return readStringValue(); + + case TypeClass.TYPE_value: + return readTypeValue(); + + case TypeClass.SEQUENCE_value: + { + Object value = readSequenceValue(type); + TypeDescription ctype = type.getComponentType(); + while (ctype.getTypeClass() == TypeClass.SEQUENCE) { + ctype = ctype.getComponentType(); + } + switch (ctype.getTypeClass().getValue()) { + case TypeClass.UNSIGNED_SHORT_value: + case TypeClass.UNSIGNED_LONG_value: + case TypeClass.UNSIGNED_HYPER_value: + return new Any(new Type(type), value); + + case TypeClass.STRUCT_value: + if (ctype.hasTypeArguments()) { + return new Any(new Type(type), value); + } + default: + return value; + } + } + + case TypeClass.ENUM_value: + return readEnumValue(type); + + case TypeClass.STRUCT_value: + { + Object value = readStructValue(type); + return type.hasTypeArguments() + ? new Any(new Type(type), value) : value; + } + + case TypeClass.EXCEPTION_value: + return readExceptionValue(type); + + case TypeClass.INTERFACE_value: + { + Object value = readInterfaceValue(type); + return type.getZClass() == XInterface.class + ? value : new Any(new Type(type), value); + } + + default: + throw new RuntimeException( + "Reading ANY with bad type " + type.getTypeClass()); + } + } + + private Object readSequenceValue(TypeDescription type) throws IOException { + int len = readCompressedNumber(); + TypeDescription ctype = type.getComponentType(); + if (ctype.getTypeClass() == TypeClass.BYTE) { + byte[] data = new byte[len]; + readBytes(data); + return data; + } else { + Object value = Array.newInstance( + ctype.getTypeClass() == TypeClass.ANY + ? Object.class : ctype.getZClass(), len); + for (int i = 0; i < len; ++i) { + Array.set(value, i, readValue(ctype)); + } + return value; + } + } + + private Enum readEnumValue(TypeDescription type) throws IOException { + try { + return (Enum) + type.getZClass().getMethod( + "fromInt", new Class[] { int.class }). + invoke(null, new Object[] { readLongValue() }); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + + private Object readStructValue(TypeDescription type) { + Object value; + try { + value = type.getZClass().newInstance(); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InstantiationException e) { + throw new RuntimeException(e); + } + readFields(type, value); + return value; + } + + private Exception readExceptionValue(TypeDescription type) throws IOException { + Exception value; + try { + value = (Exception) + type.getZClass().getConstructor(new Class[] { String.class }). + newInstance(new Object[] { readStringValue() }); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InstantiationException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + readFields(type, value); + return value; + } + + private Object readInterfaceValue(TypeDescription type) { + return readInterface(new Type(type)); + } + + private int readCompressedNumber() throws IOException { + int number = read8Bit(); + return number < 0xFF ? number : input.readInt(); + } + + private void readBytes(byte[] data) throws IOException { + input.readFully(data); + } + + private void readFields(TypeDescription type, Object value) { + FieldDescription[] fields = type.getFieldDescriptions(); + for (int i = 0; i < fields.length; ++i) { + try { + fields[i].getField().set( + value, + readValue( + fields[i].getTypeDescription())); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + } + + private final IBridge bridge; + private final String[] objectIdCache; + private final ThreadId[] threadIdCache; + private final TypeDescription[] typeCache; + private DataInputStream input; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ridljar/com/sun/star/lib/uno/protocols/urp/UrpMessage.java b/ridljar/com/sun/star/lib/uno/protocols/urp/UrpMessage.java new file mode 100644 index 000000000000..34cdf7073902 --- /dev/null +++ b/ridljar/com/sun/star/lib/uno/protocols/urp/UrpMessage.java @@ -0,0 +1,48 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ + +package com.sun.star.lib.uno.protocols.urp; + +import com.sun.star.lib.uno.environments.remote.Message; +import com.sun.star.lib.uno.environments.remote.ThreadId; +import com.sun.star.lib.uno.typedesc.MethodDescription; +import com.sun.star.lib.uno.typedesc.TypeDescription; +import com.sun.star.uno.XCurrentContext; + +final class UrpMessage extends Message { + public UrpMessage( + ThreadId threadId, boolean request, String objectId, + TypeDescription type, MethodDescription method, boolean synchronous, + XCurrentContext currentContext, boolean abnormalTermination, + Object result, Object[] arguments, boolean internal) + { + super( + threadId, request, objectId, type, method, synchronous, + currentContext, abnormalTermination, result, arguments); + this.internal = internal; + } + + public boolean isInternal() { + return internal; + } + + private final boolean internal; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ridljar/com/sun/star/lib/uno/protocols/urp/urp.java b/ridljar/com/sun/star/lib/uno/protocols/urp/urp.java new file mode 100644 index 000000000000..0ce9d35be75f --- /dev/null +++ b/ridljar/com/sun/star/lib/uno/protocols/urp/urp.java @@ -0,0 +1,760 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * 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 . + */ + +package com.sun.star.lib.uno.protocols.urp; + +import com.sun.star.bridge.InvalidProtocolChangeException; +import com.sun.star.bridge.ProtocolProperty; +import com.sun.star.bridge.XProtocolProperties; +import com.sun.star.lang.DisposedException; +import com.sun.star.lib.uno.environments.remote.IProtocol; +import com.sun.star.lib.uno.environments.remote.Message; +import com.sun.star.lib.uno.environments.remote.ThreadId; +import com.sun.star.lib.uno.typedesc.MethodDescription; +import com.sun.star.lib.uno.typedesc.TypeDescription; +import com.sun.star.uno.Any; +import com.sun.star.uno.IBridge; +import com.sun.star.uno.Type; +import com.sun.star.uno.TypeClass; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XCurrentContext; +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Random; +import java.util.StringTokenizer; + +/** + * This class internally relies on the availability of Java UNO type information + * for the interface type <code>com.sun.star.bridge.XProtocolProperties</code>, + * even though URP itself does not rely on that type. + */ +public final class urp implements IProtocol { + public urp( + IBridge bridge, String attributes, InputStream input, + OutputStream output) + { + this.input = new DataInputStream(input); + this.output = new DataOutputStream(output); + marshal = new Marshal(bridge, CACHE_SIZE); + unmarshal = new Unmarshal(bridge, CACHE_SIZE); + forceSynchronous = parseAttributes(attributes); + } + + /** + * + * @see IProtocol#init + */ + public void init() throws IOException { + synchronized (monitor) { + if (state == STATE_INITIAL0) { + sendRequestChange(); + } + } + } + + /** + * + * @see IProtocol#terminate + */ + public void terminate() { + synchronized (monitor) { + state = STATE_TERMINATED; + initialized = true; + monitor.notifyAll(); + } + } + + /** + * + * @see IProtocol#readMessage + */ + public Message readMessage() throws IOException { + for (;;) { + if (!unmarshal.hasMore()) { + unmarshal.reset(readBlock()); + if (!unmarshal.hasMore()) { + throw new IOException("closeConnection message received"); + } + } + UrpMessage msg; + int header = unmarshal.read8Bit(); + if ((header & HEADER_LONGHEADER) != 0) { + if ((header & HEADER_REQUEST) != 0) { + msg = readLongRequest(header); + } else { + msg = readReply(header); + } + } else { + msg = readShortRequest(header); + } + if (msg.isInternal()) { + handleInternalMessage(msg); + } else { + return msg; + } + } + } + + /** + * + * @see IProtocol#writeRequest + */ + public boolean writeRequest( + String oid, TypeDescription type, String function, ThreadId tid, + Object[] arguments) + throws IOException + { + if (oid.equals(PROPERTIES_OID)) { + throw new IllegalArgumentException("illegal OID " + oid); + } + synchronized (monitor) { + while (!initialized) { + try { + monitor.wait(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException(e); + } + } + if (state == STATE_TERMINATED) { + throw new DisposedException(); + } + return writeRequest(false, oid, type, function, tid, arguments); + } + } + + /** + * + * @see IProtocol#writeReply + */ + public void writeReply(boolean exception, ThreadId tid, Object result) + throws IOException + { + synchronized (output) { + writeQueuedReleases(); + int header = HEADER_LONGHEADER; + PendingRequests.Item pending = pendingIn.pop(tid); + TypeDescription resultType; + TypeDescription[] argTypes; + Object[] args; + if (exception) { + header |= HEADER_EXCEPTION; + resultType = TypeDescription.getTypeDescription(TypeClass.ANY); + argTypes = null; + args = null; + } else { + resultType = pending.function.getReturnSignature(); + argTypes = pending.function.getOutSignature(); + args = pending.arguments; + } + if (!tid.equals(outL1Tid)) { + header |= HEADER_NEWTID; + outL1Tid = tid; + } else { + tid = null; + } + marshal.write8Bit(header); + if (tid != null) { + marshal.writeThreadId(tid); + } + marshal.writeValue(resultType, result); + if (argTypes != null) { + for (int i = 0; i < argTypes.length; ++i) { + if (argTypes[i] != null) { + marshal.writeValue( + argTypes[i].getComponentType(), + Array.get(args[i], 0)); + } + } + } + writeBlock(true); + } + } + + private void sendRequestChange() throws IOException { + if (propertiesTid == null) { + propertiesTid = ThreadId.createFresh(); + } + random = randomGenerator.nextInt(); + writeRequest( + true, PROPERTIES_OID, + TypeDescription.getTypeDescription(XProtocolProperties.class), + PROPERTIES_FUN_REQUEST_CHANGE, propertiesTid, + new Object[] { Integer.valueOf(random) }); + state = STATE_REQUESTED; + } + + private void handleInternalMessage(Message message) throws IOException { + if (message.isRequest()) { + String t = message.getType().getTypeName(); + if (!t.equals("com.sun.star.bridge.XProtocolProperties")) { + throw new IOException( + "read URP protocol properties request with unsupported" + + " type " + t); + } + int fid = message.getMethod().getIndex(); + switch (fid) { + case PROPERTIES_FID_REQUEST_CHANGE: + checkSynchronousPropertyRequest(message); + synchronized (monitor) { + switch (state) { + case STATE_INITIAL0: + case STATE_INITIAL: + writeReply( + false, message.getThreadId(), Integer.valueOf(1)); + state = STATE_WAIT; + break; + case STATE_REQUESTED: + int n + = ((Integer) message.getArguments()[0]).intValue(); + if (random < n) { + writeReply( + false, message.getThreadId(), Integer.valueOf(1)); + state = STATE_WAIT; + } else if (random == n) { + writeReply( + false, message.getThreadId(), Integer.valueOf(-1)); + state = STATE_INITIAL; + sendRequestChange(); + } else { + writeReply( + false, message.getThreadId(), Integer.valueOf(0)); + } + break; + default: + writeReply( + true, message.getThreadId(), + new com.sun.star.uno.RuntimeException( + "read URP protocol properties requestChange" + + " request in illegal state")); + break; + } + } + break; + case PROPERTIES_FID_COMMIT_CHANGE: + checkSynchronousPropertyRequest(message); + synchronized (monitor) { + if (state == STATE_WAIT) { + ProtocolProperty[] p = (ProtocolProperty[]) + message.getArguments()[0]; + boolean ok = true; + boolean cc = false; + int i = 0; + for (; i < p.length; ++i) { + if (p[i].Name.equals(PROPERTY_CURRENT_CONTEXT)) { + cc = true; + } else { + ok = false; + break; + } + } + if (ok) { + writeReply(false, message.getThreadId(), null); + } else { + writeReply( + true, message.getThreadId(), + new InvalidProtocolChangeException( + "", null, p[i], 1)); + } + state = STATE_INITIAL; + if (!initialized) { + if (cc) { + currentContext = true; + initialized = true; + monitor.notifyAll(); + } else { + sendRequestChange(); + } + } + } else { + writeReply( + true, message.getThreadId(), + new com.sun.star.uno.RuntimeException( + "read URP protocol properties commitChange" + + " request in illegal state")); + } + } + break; + default: + throw new IOException( + "read URP protocol properties request with unsupported" + + " function ID " + fid); + } + } else { + synchronized (monitor) { + if (state == STATE_COMMITTED) { + // commitChange reply: + if (!message.isAbnormalTermination()) { + currentContext = true; + } + state = STATE_INITIAL; + initialized = true; + monitor.notifyAll(); + } else { + // requestChange reply: + if (message.isAbnormalTermination()) { + // remote side probably does not support negotiation: + state = STATE_INITIAL; + initialized = true; + monitor.notifyAll(); + } else { + int n = ((Integer) message.getResult()).intValue(); + switch (n) { + case -1: + case 0: + break; + case 1: + writeRequest( + true, PROPERTIES_OID, + TypeDescription.getTypeDescription( + XProtocolProperties.class), + PROPERTIES_FUN_COMMIT_CHANGE, propertiesTid, + new Object[] { + new ProtocolProperty[] { + new ProtocolProperty( + PROPERTY_CURRENT_CONTEXT, + Any.VOID) } }); + state = STATE_COMMITTED; + break; + default: + throw new IOException( + "read URP protocol properties " + + PROPERTIES_FUN_REQUEST_CHANGE + + " reply with illegal return value " + n); + } + } + } + } + } + } + + private void checkSynchronousPropertyRequest(Message message) + throws IOException + { + if (!message.isSynchronous()) { + throw new IOException( + "read URP protocol properties request for synchronous function" + + " marked as not SYNCHRONOUS"); + } + } + + private byte[] readBlock() throws IOException { + int size = input.readInt(); + input.readInt(); // ignore count + byte[] bytes = new byte[size]; + input.readFully(bytes); + return bytes; + } + + private UrpMessage readLongRequest(int header) throws IOException { + boolean sync = false; + if ((header & HEADER_MOREFLAGS) != 0) { + if (unmarshal.read8Bit() != (HEADER_MUSTREPLY | HEADER_SYNCHRONOUS)) + { + throw new IOException( + "read URP request with bad MUSTREPLY/SYNCHRONOUS byte"); + } + sync = true; + } + int funId = (header & HEADER_FUNCTIONID16) != 0 + ? unmarshal.read16Bit() : unmarshal.read8Bit(); + if ((header & HEADER_NEWTYPE) != 0) { + inL1Type = unmarshal.readType(); + if (inL1Type.getTypeClass() != TypeClass.INTERFACE) { + throw new IOException( + "read URP request with non-interface type " + inL1Type); + } + } + if ((header & HEADER_NEWOID) != 0) { + inL1Oid = unmarshal.readObjectId(); + } + if ((header & HEADER_NEWTID) != 0) { + inL1Tid = unmarshal.readThreadId(); + } + return readRequest(funId, sync); + } + + private UrpMessage readShortRequest(int header) throws IOException { + int funId = (header & HEADER_FUNCTIONID14) != 0 + ? ((header & HEADER_FUNCTIONID) << 8) | unmarshal.read8Bit() + : header & HEADER_FUNCTIONID; + return readRequest(funId, false); + } + + private UrpMessage readRequest(int functionId, boolean forcedSynchronous) + throws IOException + { + boolean internal = PROPERTIES_OID.equals(inL1Oid); + // inL1Oid may be null in XInstanceProvider.getInstance("") + XCurrentContext cc = + (currentContext && !internal + && functionId != MethodDescription.ID_RELEASE) + ? (XCurrentContext) unmarshal.readInterface( + new Type(XCurrentContext.class)) + : null; + MethodDescription desc = inL1Type.getMethodDescription(functionId); + if (desc == null) { + throw new IOException( + "read URP request with unsupported function ID " + functionId); + } + TypeDescription[] inSig = desc.getInSignature(); + TypeDescription[] outSig = desc.getOutSignature(); + Object[] args = new Object[inSig.length]; + for (int i = 0; i < args.length; ++i) { + if (inSig[i] != null) { + if (outSig[i] != null) { + Object inout = Array.newInstance( + outSig[i].getComponentType().getZClass(), 1); + Array.set( + inout, 0, + unmarshal.readValue( + outSig[i].getComponentType())); + args[i] = inout; + } else { + args[i] = unmarshal.readValue(inSig[i]); + } + } else { + args[i] = Array.newInstance( + outSig[i].getComponentType().getZClass(), 1); + } + } + boolean sync = forcedSynchronous || !desc.isOneway(); + if (sync) { + pendingIn.push( + inL1Tid, new PendingRequests.Item(internal, desc, args)); + } + return new UrpMessage( + inL1Tid, true, inL1Oid, inL1Type, desc, sync, cc, false, null, args, + internal); + } + + private UrpMessage readReply(int header) { + if ((header & HEADER_NEWTID) != 0) { + inL1Tid = unmarshal.readThreadId(); + } + PendingRequests.Item pending = pendingOut.pop(inL1Tid); + TypeDescription resultType; + TypeDescription[] argTypes; + Object[] args; + boolean exception = (header & HEADER_EXCEPTION) != 0; + if (exception) { + resultType = TypeDescription.getTypeDescription(TypeClass.ANY); + argTypes = null; + args = null; + } else { + resultType = pending.function.getReturnSignature(); + argTypes = pending.function.getOutSignature(); + args = pending.arguments; + } + Object result = resultType == null + ? null : unmarshal.readValue(resultType); + if (argTypes != null) { + for (int i = 0; i < argTypes.length; ++i) { + if (argTypes[i] != null) { + Array.set( + args[i], 0, + unmarshal.readValue( + argTypes[i].getComponentType())); + } + } + } + return new UrpMessage( + inL1Tid, false, null, null, null, false, null, exception, result, + args, pending.internal); + } + + private boolean writeRequest( + boolean internal, String oid, TypeDescription type, String function, + ThreadId tid, Object[] arguments) + throws IOException + { + MethodDescription desc = type.getMethodDescription(function); + synchronized (output) { + if (desc.getIndex() == MethodDescription.ID_RELEASE + && releaseQueue.size() < MAX_RELEASE_QUEUE_SIZE) + { + releaseQueue.add( + new QueuedRelease(internal, oid, type, desc, tid)); + return false; + } else { + writeQueuedReleases(); + return writeRequest( + internal, oid, type, desc, tid, arguments, true); + } + } + } + + private boolean writeRequest( + boolean internal, String oid, TypeDescription type, + MethodDescription desc, ThreadId tid, Object[] arguments, + boolean flush) + throws IOException + { + int funId = desc.getIndex(); + if (funId < 0 || funId > MAX_FUNCTIONID16) { + throw new IllegalArgumentException( + "function ID " + funId + " out of range"); + } + boolean forceSync = forceSynchronous + && funId != MethodDescription.ID_RELEASE; + boolean moreFlags = forceSync && desc.isOneway(); + boolean longHeader = moreFlags; + int header = 0; + if (!type.equals(outL1Type)) { + longHeader = true; + header |= HEADER_NEWTYPE; + outL1Type = type; + } else { + type = null; + } + if (!oid.equals(outL1Oid)) { + longHeader = true; + header |= HEADER_NEWOID; + outL1Oid = oid; + } else { + oid = null; + } + if (!tid.equals(outL1Tid)) { + longHeader = true; + header |= HEADER_NEWTID; + outL1Tid = tid; + } else { + tid = null; + } + if (funId > MAX_FUNCTIONID14) { + longHeader = true; + } + if (longHeader) { + header |= HEADER_LONGHEADER | HEADER_REQUEST; + if (funId > MAX_FUNCTIONID8) { + header |= HEADER_FUNCTIONID16; + } + if (moreFlags) { + header |= HEADER_MOREFLAGS; + } + marshal.write8Bit(header); + if (moreFlags) { + marshal.write8Bit(HEADER_MUSTREPLY | HEADER_SYNCHRONOUS); + } + if (funId > MAX_FUNCTIONID8) { + marshal.write16Bit(funId); + } else { + marshal.write8Bit(funId); + } + if (type != null) { + marshal.writeType(type); + } + if (oid != null) { + marshal.writeObjectId(oid); + } + if (tid != null) { + marshal.writeThreadId(tid); + } + } else { + if (funId > HEADER_FUNCTIONID) { + marshal.write8Bit(HEADER_FUNCTIONID14 | (funId >> 8)); + } + marshal.write8Bit(funId); + } + if (currentContext && !internal + && funId != MethodDescription.ID_RELEASE) + { + marshal.writeInterface( + UnoRuntime.getCurrentContext(), + new Type(XCurrentContext.class)); + } + TypeDescription[] inSig = desc.getInSignature(); + TypeDescription[] outSig = desc.getOutSignature(); + for (int i = 0; i < inSig.length; ++i) { + if (inSig[i] != null) { + if (outSig[i] != null) { + marshal.writeValue( + outSig[i].getComponentType(), + ((Object[]) arguments[i])[0]); + } else { + marshal.writeValue( + inSig[i], arguments[i]); + } + } + } + boolean sync = forceSync || !desc.isOneway(); + if (sync) { + pendingOut.push( + outL1Tid, new PendingRequests.Item(internal, desc, arguments)); + } + writeBlock(flush); + return sync; + } + + private void writeBlock(boolean flush) throws IOException { + byte[] data = marshal.reset(); + output.writeInt(data.length); + output.writeInt(1); + output.write(data); + if (flush) { + output.flush(); + } + } + + private void writeQueuedReleases() throws IOException { + for (int i = releaseQueue.size(); i > 0;) { + --i; + QueuedRelease r = releaseQueue.get(i); + writeRequest( + r.internal, r.objectId, r.type, r.method, r.threadId, null, + false); + releaseQueue.remove(i); + } + } + + private static boolean parseAttributes(String attributes) { + boolean forceSynchronous = true; + if (attributes != null) { + StringTokenizer t = new StringTokenizer(attributes, ","); + while (t.hasMoreTokens()) { + String a = t.nextToken(); + String v = null; + int i = a.indexOf('='); + if (i >= 0) { + v = a.substring(i + 1); + a = a.substring(0, i); + } + if (a.equalsIgnoreCase("ForceSynchronous")) { + forceSynchronous = parseBooleanAttributeValue(a, v); + } else if (a.equalsIgnoreCase("negotiate")) { + // Ignored: + parseBooleanAttributeValue(a, v); + } else { + throw new IllegalArgumentException( + "unknown protocol attribute " + a); + } + } + } + return forceSynchronous; + } + + private static boolean parseBooleanAttributeValue( + String attribute, String value) + { + if (value == null) { + throw new IllegalArgumentException( + "missing value for protocol attribute " + attribute); + } + if (value.equals("0")) { + return false; + } else if (value.equals("1")) { + return true; + } else { + throw new IllegalArgumentException( + "bad value " + value + " for protocol attribute " + attribute); + } + } + + private static final class QueuedRelease { + public QueuedRelease( + boolean internal, String objectId, TypeDescription type, + MethodDescription method, ThreadId threadId) + { + this.internal = internal; + this.objectId = objectId; + this.type = type; + this.method = method; + this.threadId = threadId; + } + + public final boolean internal; + public final String objectId; + public final TypeDescription type; + public final MethodDescription method; + public final ThreadId threadId; + } + + private static final String PROPERTIES_OID = "UrpProtocolProperties"; + private static final int PROPERTIES_FID_REQUEST_CHANGE = 4; + private static final String PROPERTIES_FUN_REQUEST_CHANGE = "requestChange"; + private static final int PROPERTIES_FID_COMMIT_CHANGE = 5; + private static final String PROPERTIES_FUN_COMMIT_CHANGE = "commitChange"; + private static final String PROPERTY_CURRENT_CONTEXT = "CurrentContext"; + + private static final short CACHE_SIZE = 256; + + private static final int HEADER_LONGHEADER = 0x80; + private static final int HEADER_REQUEST = 0x40; + private static final int HEADER_NEWTYPE = 0x20; + private static final int HEADER_NEWOID = 0x10; + private static final int HEADER_NEWTID = 0x08; + private static final int HEADER_FUNCTIONID16 = 0x04; + private static final int HEADER_MOREFLAGS = 0x01; + private static final int HEADER_MUSTREPLY = 0x80; + private static final int HEADER_SYNCHRONOUS = 0x40; + private static final int HEADER_FUNCTIONID14 = 0x40; + private static final int HEADER_FUNCTIONID = 0x3F; + private static final int HEADER_EXCEPTION = 0x20; + + private static final int MAX_FUNCTIONID16 = 0xFFFF; + private static final int MAX_FUNCTIONID14 = 0x3FFF; + private static final int MAX_FUNCTIONID8 = 0xFF; + + private static final int STATE_INITIAL0 = 0; + private static final int STATE_INITIAL = 1; + private static final int STATE_REQUESTED = 2; + private static final int STATE_COMMITTED = 3; + private static final int STATE_WAIT = 4; + private static final int STATE_TERMINATED = 5; + + private static final int MAX_RELEASE_QUEUE_SIZE = 100; + + private static final Random randomGenerator = new Random(); + + private final DataInput input; + private final DataOutputStream output; + + private final Marshal marshal; + private final Unmarshal unmarshal; + + private final boolean forceSynchronous; + + private final PendingRequests pendingIn = new PendingRequests(); + private final PendingRequests pendingOut = new PendingRequests(); + + private final Object monitor = new Object(); + private int state = STATE_INITIAL0; + private boolean initialized = false; + private ThreadId propertiesTid = null; + private int random; + private boolean currentContext = false; + + private ThreadId inL1Tid = null; + private String inL1Oid = null; + private TypeDescription inL1Type = null; + + private ThreadId outL1Tid = null; + private String outL1Oid = null; + private TypeDescription outL1Type = null; + + private final ArrayList<QueuedRelease> releaseQueue = new ArrayList<QueuedRelease>(); // of QueuedRelease +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |