From e82f28bf499c7544b839e3de2842a72a61e3c4c1 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Tue, 2 Apr 2024 10:00:42 -0700 Subject: [PATCH 1/3] refactor python-java bridge into seperate project --- java/pom.xml | 9 +- .../internal/zmq/SerializableObject.java | 17 - .../micromanager/internal/zmq/ZMQClient.java | 38 - .../internal/zmq/ZMQPullSocket.java | 40 - .../internal/zmq/ZMQPushSocket.java | 58 - .../micromanager/internal/zmq/ZMQServer.java | 549 --------- .../internal/zmq/ZMQSocketWrapper.java | 219 ---- .../micromanager/internal/zmq/ZMQUtil.java | 670 ----------- .../micromanager/remote/HeadlessLauncher.java | 2 +- .../micromanager/remote/RemoteAcqHook.java | 4 +- .../remote/RemoteCoreCallback.java | 2 +- .../remote/RemoteEventSource.java | 2 +- .../remote/RemoteImageProcessor.java | 6 +- .../remote/RemoteNotificationHandler.java | 2 +- .../remote/RemoteStorageMonitor.java | 3 +- pycromanager/__init__.py | 2 +- .../acquisition/java_backend_acquisitions.py | 6 +- pycromanager/headless.py | 4 +- pycromanager/mm_java_classes.py | 2 +- pycromanager/zmq_bridge/__init__.py | 0 pycromanager/zmq_bridge/bridge.py | 1047 ----------------- pycromanager/zmq_bridge/wrappers.py | 108 -- 22 files changed, 22 insertions(+), 2768 deletions(-) delete mode 100644 java/src/main/java/org/micromanager/internal/zmq/SerializableObject.java delete mode 100644 java/src/main/java/org/micromanager/internal/zmq/ZMQClient.java delete mode 100644 java/src/main/java/org/micromanager/internal/zmq/ZMQPullSocket.java delete mode 100644 java/src/main/java/org/micromanager/internal/zmq/ZMQPushSocket.java delete mode 100644 java/src/main/java/org/micromanager/internal/zmq/ZMQServer.java delete mode 100644 java/src/main/java/org/micromanager/internal/zmq/ZMQSocketWrapper.java delete mode 100644 java/src/main/java/org/micromanager/internal/zmq/ZMQUtil.java delete mode 100644 pycromanager/zmq_bridge/__init__.py delete mode 100644 pycromanager/zmq_bridge/bridge.py delete mode 100644 pycromanager/zmq_bridge/wrappers.py diff --git a/java/pom.xml b/java/pom.xml index 84bdf7fb..546988c6 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -41,11 +41,12 @@ - - org.zeromq - jeromq - 0.5.1 + + org.micro-manager.pyjavaz + PyJavaZ + 1.0.0 + org.micro-manager.mmcorej MMCoreJ diff --git a/java/src/main/java/org/micromanager/internal/zmq/SerializableObject.java b/java/src/main/java/org/micromanager/internal/zmq/SerializableObject.java deleted file mode 100644 index fbec651a..00000000 --- a/java/src/main/java/org/micromanager/internal/zmq/SerializableObject.java +++ /dev/null @@ -1,17 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package org.micromanager.internal.zmq; - -/** - * - * @author henrypinkard - */ -public interface SerializableObject { - - public byte[] serialize(); - - -} diff --git a/java/src/main/java/org/micromanager/internal/zmq/ZMQClient.java b/java/src/main/java/org/micromanager/internal/zmq/ZMQClient.java deleted file mode 100644 index 36f161bf..00000000 --- a/java/src/main/java/org/micromanager/internal/zmq/ZMQClient.java +++ /dev/null @@ -1,38 +0,0 @@ -///* -// * To change this license header, choose License Headers in Project Properties. -// * To change this template file, choose Tools | Templates -// * and open the template in the editor. -// */ -//package org.micromanager.internal.zmq; -// -//import org.micromanager.Studio; -//import org.zeromq.SocketType; -// -///** -// * -// * @author henrypinkard -// */ -//public class ZMQClient extends ZMQSocketWrapper{ -// -// public ZMQClient(Studio studio, SocketType type) { -// super(studio, type); -// } -// -// /** -// * send a command from a Java client to a python server and wait for response -// * -// * @param request Command to be send through the port -// * @return response from the Python side -// */ -// protected Object sendRequest(String request) { -// socket_.send(request); -// byte[] reply = socket_.recv(); -// return deserialize(reply); -// } -// -// @Override -// public void initialize(int port) { -// -// } -// -//} diff --git a/java/src/main/java/org/micromanager/internal/zmq/ZMQPullSocket.java b/java/src/main/java/org/micromanager/internal/zmq/ZMQPullSocket.java deleted file mode 100644 index b6879faf..00000000 --- a/java/src/main/java/org/micromanager/internal/zmq/ZMQPullSocket.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package org.micromanager.internal.zmq; - -import java.util.function.Function; -import java.util.logging.Level; -import java.util.logging.Logger; -import mmcorej.org.json.JSONException; -import mmcorej.org.json.JSONObject; -import org.zeromq.SocketType; - -/** - * Does not run on its own thread - * @author henrypinkard - */ -public class ZMQPullSocket extends ZMQSocketWrapper { - - Function deserializationFunction_; - - public ZMQPullSocket(Function deserializationFunction) { - super(SocketType.PULL); - deserializationFunction_ = deserializationFunction; - } - - @Override - public void initialize(int port) { - socket_ = context_.createSocket(type_); - port_ = port; - socket_.connect("tcp://127.0.0.1:" + port); - } - - public T next() { - JSONObject json = receiveMessage(); - return (T) deserializationFunction_.apply(json); - } - -} diff --git a/java/src/main/java/org/micromanager/internal/zmq/ZMQPushSocket.java b/java/src/main/java/org/micromanager/internal/zmq/ZMQPushSocket.java deleted file mode 100644 index bded2bb5..00000000 --- a/java/src/main/java/org/micromanager/internal/zmq/ZMQPushSocket.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package org.micromanager.internal.zmq; - -import java.util.concurrent.ExecutorService; -import java.util.function.Function; -import mmcorej.org.json.JSONObject; -import static org.micromanager.internal.zmq.ZMQSocketWrapper.context_; -import org.zeromq.SocketType; - -/** - * - * @author henrypinkard - */ -public class ZMQPushSocket extends ZMQSocketWrapper { - - private Function serializationFn_; - - //Constructor for server the base class that runs on its own thread - public ZMQPushSocket(Function serializationFn) { - super(SocketType.PUSH); - serializationFn_ = serializationFn; - } - - @Override - public void initialize(int port) { - socket_ = context_.createSocket(type_); - port_ = port; - socket_.bind("tcp://127.0.0.1:" + port); -// executor_ = Executors.newSingleThreadExecutor( -// (Runnable r) -> new Thread(r, "ZMQ Pusher " )); -// executor_.submit(() -> { -// socket_ = context_.createSocket(type_); -// port_ = port; -// socket_.bind("tcp://127.0.0.1:" + port); -// }); - } - - /** - * Serialize the object and send it out to any pulling sockets - * - * @param o - */ - public void push(T o) { - JSONObject json = serializationFn_.apply(o); - sendMessage(json); - -// return executor_.submit(() -> { -// socket_.send(serializationFn_.apply(o).toString()); -// }); - } - - - -} diff --git a/java/src/main/java/org/micromanager/internal/zmq/ZMQServer.java b/java/src/main/java/org/micromanager/internal/zmq/ZMQServer.java deleted file mode 100644 index bb766e17..00000000 --- a/java/src/main/java/org/micromanager/internal/zmq/ZMQServer.java +++ /dev/null @@ -1,549 +0,0 @@ -package org.micromanager.internal.zmq; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.io.UnsupportedEncodingException; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.URISyntaxException; -import java.net.URLClassLoader; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import mmcorej.org.json.JSONException; -import mmcorej.org.json.JSONObject; - -import org.zeromq.SocketType; - -/** - * implements request reply server (ie the reply part) - * - * ecompasses both the master server and the - */ -public class ZMQServer extends ZMQSocketWrapper { - - private ExecutorService executor_; -// protected static Set apiClasses_; - private static Set packages_; - private static ZMQUtil util_; - - //map of objects that exist in some client of the server - protected final ConcurrentHashMap externalObjects_ = new ConcurrentHashMap(); - - public static final String VERSION = "5.1.0"; - - private static Function classMapper_; - private static ZMQServer mainServer_; - static boolean debug_ = false; - private static Consumer debugLogger_; - - //for testing -// public static void main(String[] args) { -// ZMQServer server = new ZMQServer(DEFAULT_MASTER_PORT_NUMBER, "master", new Function() { -// @Override -// public Object apply(Class t) { -// return null; -// } -// }); -// while (true) { -// if (portSocketMap_.containsKey(DEFAULT_MASTER_PORT_NUMBER + 1)) { -// ZMQPullSocket socket = (ZMQPullSocket) portSocketMap_.get(DEFAULT_MASTER_PORT_NUMBER + 1); -// Object n = socket.next(); -// System.out.println(); -// } -// } -// } - - /** - * This constructor used if making a new server on a different port and all the classloader info already parsed - */ - public ZMQServer() { - super(SocketType.REP); - } - - public ZMQServer(Collection cls, Function classMapper, - String[] excludePaths, Consumer debugLogger) throws URISyntaxException, UnsupportedEncodingException { - this(cls, classMapper, excludePaths, debugLogger, ZMQSocketWrapper.STARTING_PORT_NUMBER); - } - - public ZMQServer(Collection cls, Function classMapper, - String[] excludePaths, Consumer debugLogger, int port) throws URISyntaxException, UnsupportedEncodingException { - super(SocketType.REP, port); - mainServer_ = this; - debugLogger_ = debugLogger; - - classMapper_ = classMapper; - util_ = new ZMQUtil(cls, excludePaths); - - //get packages for current classloader (redundant?) - packages_ = ZMQUtil.getPackages(); - for (ClassLoader cl : cls) { - // Dont understand the launching conditions that make each neccessary, but both needed at times - if (cl instanceof URLClassLoader) { - packages_.addAll(ZMQUtil.getPackagesFromJars((URLClassLoader) cl)); - } else { - packages_.addAll(Stream.of(Package.getPackages()).map(p -> p.getName()).collect(Collectors.toList())); - } - } - } - - public static ZMQServer getMasterServer() { - return mainServer_; - } - - @Override - public void initialize(int port) { - // Can we be initialized multiple times? If so, we should cleanup - // the multiple instances of executors and sockets cleanly - executor_ = Executors.newSingleThreadExecutor( - (Runnable r) -> new Thread(r, "ZMQ Server ")); - executor_.submit(() -> { - socket_ = context_.createSocket(type_); - port_ = port; - socket_.bind("tcp://127.0.0.1:" + port); - - //Master request-reply loop - while (true) { - JSONObject message = receiveMessage(); - if (debug_) { - System.out.println("Recieved message: \t" + message); - debugLogger_.accept("Recieved message: \t" + message); - } - JSONObject reply = null; - try { - reply = parseAndExecuteCommand(message); - } catch (Exception e) { - try { - reply = new JSONObject(); - reply.put("type", "exception"); - - StringWriter sw = new StringWriter(); - e.printStackTrace(new PrintWriter(sw)); - String exceptionAsString = sw.toString(); - reply.put("value", exceptionAsString); - - e.printStackTrace(); - - } catch (JSONException ex) { - throw new RuntimeException(ex); - // This wont happen - } - } - - if (debug_) { - System.out.println("Sending message: \t" + reply.toString()); - debugLogger_.accept("Sending message: \t" + reply.toString()); - } - sendMessage(reply); - if (debug_) { - System.out.println("Message sent"); - debugLogger_.accept("Message sent"); - } - // Check if any more objects in clients know about this server. If its not the main one, - // shut it down - if (this != mainServer_ && externalObjects_.keySet().size() == 0) { - close(); - break; - } - } - }); - } - - public void close() { - super.close(); - if (executor_ != null) { - executor_.shutdownNow(); - socket_.close(); - } - } - - protected JSONObject getField(Object obj, JSONObject json) throws JSONException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException { - String fieldName = json.getString("name"); - Object field = obj.getClass().getField(fieldName).get(obj); - JSONObject serialized = new JSONObject(); - util_.serialize(externalObjects_, field, serialized, port_); - return serialized; - } - - protected void setField(Object obj, JSONObject json) throws JSONException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException { - String fieldName = json.getString("name"); - Object val = json.get("value"); - if (val instanceof JSONObject) { - val = externalObjects_.get(((JSONObject) val).getString("hash-code")); - } - obj.getClass().getField(fieldName).set(obj, val); - } - - /** - * Traverse the class/interface inheritance hierarchy - * @param potentialPackages - */ - private void traverseInheritedPackages(Set potentialPackages, Class classOrInterface) { - try { - if (classOrInterface == null) { - return; - } - potentialPackages.add(classOrInterface.getPackage().getName()); - // superclasses - if (classOrInterface.getSuperclass() != null) { - traverseInheritedPackages(potentialPackages, classOrInterface.getSuperclass()); - } - // interfaces - for (Class c : classOrInterface.getInterfaces()) { - traverseInheritedPackages(potentialPackages, c); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - /** - * Get reference to object that may be stored on any of the ZMQServers on different ports - * @param hashCode - * @return - */ - private Object getObjectKnownToServer (String hashCode) { - for (ZMQSocketWrapper z : portSocketMap_.values()) { - if (z instanceof ZMQServer) { - if (((ZMQServer) z).externalObjects_.containsKey(hashCode)) { - return ((ZMQServer) z).externalObjects_.get(hashCode); - } - } - } - throw new RuntimeException("Object with Hash code " + hashCode + " unknown to all ZMQ servers"); - } - - /** - * Generate every possible combination of parameters given multiple interfaces for classes so that - * the correct method can be located. Also fill in argVals with the correct objects or primitives - * - * @param message - * @param argVals - * @return - * @throws JSONException - * @throws UnsupportedEncodingException - */ - private LinkedList> getArgumentSignatures(JSONObject message, Object[] argVals) throws JSONException, - UnsupportedEncodingException { - - //get argument values - for (int i = 0; i < argVals.length; i++) { -// Class c = message.getJSONArray("arguments").get(i).getClass(); - if (message.getJSONArray("arguments").get(i) instanceof JSONObject - && message.getJSONArray("arguments").getJSONObject(i).has("hash-code")) { - //Passed in a javashadow object as an argument - argVals[i] = getObjectKnownToServer(message.getJSONArray("arguments").getJSONObject(i).getString("hash-code")); - } else if (ZMQUtil.PRIMITIVE_NAME_CLASS_MAP.containsKey(message.getJSONArray("argument-deserialization-types").get(i) )) { - Object primitive = message.getJSONArray("arguments").get(i); //Double, Integer, Long, Boolean - Class c = ZMQUtil.PRIMITIVE_NAME_CLASS_MAP.get(message.getJSONArray("argument-deserialization-types").get(i)); - argVals[i] = ZMQUtil.convertToPrimitiveClass(primitive, c); - } else if (ZMQUtil.PRIMITIVE_ARRAY_NAME_CLASS_MAP.containsKey(message.getJSONArray("argument-deserialization-types").get(i))) { - byte[] byteArray = (byte[]) message.getJSONArray("arguments").get(i); - Class c = ZMQUtil.PRIMITIVE_ARRAY_NAME_CLASS_MAP.get(message.getJSONArray("argument-deserialization-types").get(i)); - argVals[i] = ZMQUtil.convertToPrimitiveArray(byteArray, c); - } else if (message.getJSONArray("argument-deserialization-types").get(i).equals("java.lang.String")) { - //Strings are a special case because they're like a primitive but not quite - if (message.getJSONArray("arguments").get(i) == JSONObject.NULL) { - argVals[i] = null; - } else { - argVals[i] = message.getJSONArray("arguments").getString(i); - } - } else if (message.getJSONArray("argument-deserialization-types").get(i).equals("java.lang.Object")) { - argVals[i] = message.getJSONArray("arguments").get(i); - } - } - - - //get classes - Object[] argClasses = new Object[message.getJSONArray("arguments").length()]; - for (int i = 0; i < argVals.length; i++) { -// Class c = message.getJSONArray("arguments").get(i).getClass(); - if (message.getJSONArray("arguments").get(i) instanceof JSONObject - && message.getJSONArray("arguments").getJSONObject(i).has("hash-code")) { - //abstract to superclasses/interfaces in the API - Set potentialPackages = new TreeSet(); - traverseInheritedPackages(potentialPackages, argVals[i].getClass()); - - //build up a list of valid packages - Set apiClasses = new HashSet(); - for (String packageName : potentialPackages) { - apiClasses.addAll(util_.getPackageClasses(packageName)); - } - - ParamSet potentialClasses = new ParamSet(); - for (Class apiClass : apiClasses) { - if (apiClass.isAssignableFrom(argVals[i].getClass())) { - potentialClasses.add(apiClass); - } - } - //add the class itself. This is needed for java internal classes - potentialClasses.add(argVals[i].getClass()); - argClasses[i] = potentialClasses; - } else if (ZMQUtil.PRIMITIVE_NAME_CLASS_MAP.containsKey(message.getJSONArray("argument-types").get(i))) { - argClasses[i] = ZMQUtil.PRIMITIVE_NAME_CLASS_MAP.get(message.getJSONArray("argument-types").get(i)); - } else if (ZMQUtil.PRIMITIVE_ARRAY_NAME_CLASS_MAP.containsKey(message.getJSONArray("argument-types").get(i))) { - argClasses[i] = ZMQUtil.PRIMITIVE_ARRAY_NAME_CLASS_MAP.get( message.getJSONArray("argument-types").get(i)); - } else if (message.getJSONArray("argument-types").get(i).equals("java.lang.String")) { - //Strings are a special case because they're like a primitive but not quite - argClasses[i] = java.lang.String.class; - } else if (message.getJSONArray("argument-types").get(i).equals("java.lang.Object")) { - argClasses[i] = java.lang.Object.class; - } - } - - //Generate every possible combination of parameters given multiple interfaces for classes - //so that the correct method can be located - LinkedList> paramCombos = new LinkedList>(); - for (Object argument : argClasses) { - if (argument instanceof ParamSet) { - if (paramCombos.isEmpty()) { - //Add an entry for each possible type of the argument - for (Class c : (ParamSet) argument) { - paramCombos.add(new LinkedList()); - paramCombos.getLast().add(c); - } - } else { - //multiply each existing combo by each possible value of the arg - LinkedList> newComboList = new LinkedList>(); - for (Class c : (ParamSet) argument) { - for (LinkedList argList : paramCombos) { - LinkedList newArgList = new LinkedList(argList); - newArgList.add(c); - newComboList.add(newArgList); - } - } - paramCombos = newComboList; - } - } else { - //only one type, simply add it to every combo - if (paramCombos.isEmpty()) { - //Add an entry for each possible type of the argument - paramCombos.add(new LinkedList()); - } - for (LinkedList argList : paramCombos) { - argList.add((Class) argument); - } - } - } - return paramCombos; - } - - private Object runConstructor(JSONObject message, Class baseClass) throws - JSONException, InstantiationException, IllegalAccessException, - IllegalArgumentException, InvocationTargetException, UnsupportedEncodingException { - - Object[] argVals = new Object[message.getJSONArray("arguments").length()]; - - LinkedList> paramCombos = getArgumentSignatures(message, argVals); - - Constructor mathcingConstructor = null; - if (paramCombos.isEmpty()) { //Constructor with no arguments - try { - mathcingConstructor = baseClass.getConstructor(new Class[]{}); - } catch (Exception ex) { - throw new RuntimeException(ex); - } - } else { //Figure out which constructor matches given arguments - for (LinkedList argList : paramCombos) { - Class[] classArray = argList.stream().toArray(Class[]::new); - try { - mathcingConstructor = baseClass.getConstructor(classArray); - break; - } catch (NoSuchMethodException e) { - //ignore - } - } - } - if (mathcingConstructor == null) { - throw new RuntimeException("No Matching method found with argumetn types"); - } - - return mathcingConstructor.newInstance(argVals); - } - - private JSONObject runMethod(Object obj, JSONObject message, boolean staticMethod) throws NoSuchMethodException, IllegalAccessException, - JSONException, UnsupportedEncodingException { - /** - * For static methods the class is the object - */ - Class clazz; - if (staticMethod) { - clazz = (Class) obj; - } else { - clazz = obj.getClass(); - } - - String methodName = message.getString("name"); - Object[] argVals = new Object[message.getJSONArray("arguments").length()]; - LinkedList> paramCombos = getArgumentSignatures(message, argVals); - - Method matchingMethod = null; - if (paramCombos.isEmpty()) { - //0 argument funtion - matchingMethod = clazz.getMethod(methodName); - } else { - for (LinkedList argList : paramCombos) { - Class[] parameterTypes = argList.stream().toArray(Class[]::new); - - Method[] nameMatches = Stream.of(clazz.getMethods()).filter( - method -> method.getName().equals(methodName)).toArray(Method[]::new); - - for (Method m : nameMatches) { - // if they dont have same number of parameters, its not a match - if (m.getParameters().length != parameterTypes.length) { - continue; - } - // Check for equality or superclass compatibility of each parameter - // this is needed e.g. for the case of a method declared with a parameter List - // but an argument supplied ArrayList - // Right now it just takes the first match it finds. It might be better in the future to - // explicitly find the closest match (for example if it has multiple methods with - // LinkedList and List as arguments). See: - // https://stackoverflow.com/questions/2580665/java-getmethod-with-superclass-parameters-in-method - boolean matches = true; - for (int i = 0; i < parameterTypes.length; i++) { - if (!m.getParameterTypes()[i].isAssignableFrom(parameterTypes[i])) { - matches = false; - break; - } - } - if (matches) { - matchingMethod = m; - } - } - - } - } - if (matchingMethod == null) { - throw new RuntimeException("No Matching method found with argument types"); - } - - Object result; - try { - matchingMethod.setAccessible(true); //this is needed to call public methods on private classes - result = matchingMethod.invoke(obj, argVals); - } catch (InvocationTargetException ex) { - ex.printStackTrace(); - result = ex.getCause(); - } - - JSONObject serialized = new JSONObject(); - util_.serialize(externalObjects_, result, serialized, port_); - return serialized; - } - - protected JSONObject parseAndExecuteCommand(JSONObject request) throws Exception { - JSONObject reply; - switch (request.getString("command")) { - case "connect": { - // Connect to the server - debug_ = request.getBoolean("debug"); - //Called by master process - reply = new JSONObject(); - reply.put("type", "none"); - reply.put("version", VERSION); - return reply; - } - case "get-constructors": { - String classpath = request.getString("classpath"); - reply = new JSONObject(); - reply.put("type", "none"); - reply.put("api", ZMQUtil.parseConstructors(classpath, classMapper_)); - return reply; - } -// case "get-class": -// // Get Java class for calling static methods -// Class baseStaticClass = util_.loadClass(request.getString("classpath")); -// if (baseStaticClass == null) { -// throw new RuntimeException("Couldnt find class with name" + request.getString("classpath")); -// } -// -// ZMQServer newServer = null; -// if (request.has("new-port") && request.getBoolean("new-port")) { -// //start the server for this class and store it -// newServer = new ZMQServer(); -// } -// reply = new JSONObject(); -// util_.serialize(baseStaticClass, reply, newServer == null ? port_ :newServer.port_); -// return reply; - case "constructor": - case "get-class": { - //construct a new object (or grab an exisitng instance) - // or get a static java class - Class baseClass = util_.loadClass(request.getString("classpath")); - if (baseClass == null) { - throw new RuntimeException("Couldnt find class with name" + request.getString("classpath")); - } - - Object instance; - if (request.getString("command").equals("constructor")) { - instance = classMapper_.apply(baseClass); - //if this is not one of the classes that is supposed to grab an existing - //object, construct a new one - if (instance == null) { - instance = runConstructor(request, baseClass); - } - } else { //just interested in the class itself - instance = baseClass; - } - - ConcurrentHashMap extObjectTracker = externalObjects_; - ZMQServer newServer = null; - if (request.has("new-port") && request.getBoolean("new-port")) { - //start the server for this class and store it - newServer = new ZMQServer(); - extObjectTracker = newServer.externalObjects_; - } - reply = new JSONObject(); - util_.serialize(extObjectTracker, instance, reply, newServer == null ? port_ : newServer.port_); - return reply; - } - case "run-method": { - String hashCode = request.getString("hash-code"); - Object target = externalObjects_.get(hashCode); - return runMethod(target, request, request.getBoolean("static")); - } - case "get-field": { - String hashCode = request.getString("hash-code"); - Object target = externalObjects_.get(hashCode); - return getField(target, request); - } - case "set-field": { - String hashCode = request.getString("hash-code"); - Object target = externalObjects_.get(hashCode); - setField(target, request); - reply = new JSONObject(); - reply.put("type", "none"); - return reply; - } - case "destructor": { - String hashCode = request.getString("hash-code"); - //TODO this is defined in superclass, maybe it would be good to merge these? -// System.out.println("remove object: " + hashCode); - Object removed = externalObjects_.remove(hashCode); - if (debug_) { - System.out.println("Object ready for garbage collection: " + removed); - } - reply = new JSONObject(); - - reply.put("type", "none"); - return reply; - } - default: - break; - } - throw new RuntimeException("Unknown Command"); - } - -} - -class ParamSet extends HashSet { - -} diff --git a/java/src/main/java/org/micromanager/internal/zmq/ZMQSocketWrapper.java b/java/src/main/java/org/micromanager/internal/zmq/ZMQSocketWrapper.java deleted file mode 100644 index 2e78fa18..00000000 --- a/java/src/main/java/org/micromanager/internal/zmq/ZMQSocketWrapper.java +++ /dev/null @@ -1,219 +0,0 @@ -package org.micromanager.internal.zmq; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.concurrent.ConcurrentHashMap; - -import mmcorej.org.json.JSONArray; -import mmcorej.org.json.JSONException; -import mmcorej.org.json.JSONObject; -import org.zeromq.SocketType; -import org.zeromq.ZContext; -import org.zeromq.ZMQ; - -// Base class that wraps a ZMQ socket and implements type conversions as well -// as the impicit JSON message syntax -public abstract class ZMQSocketWrapper { - - protected static ZContext context_; - - //map of port numbers to servers, each of which has its own thread and base class - protected static ConcurrentHashMap portSocketMap_ - = new ConcurrentHashMap(); - public static int STARTING_PORT_NUMBER = 4827; -// public static int nextPort_ = DEFAULT_MASTER_PORT_NUMBER; - - protected SocketType type_; - protected volatile ZMQ.Socket socket_; - protected int port_; - - private boolean closed_ = false; - - public ZMQSocketWrapper(SocketType type, int port) { - STARTING_PORT_NUMBER = port; - type_ = type; - if (context_ == null) { - context_ = new ZContext(); - } - port_ = nextPortNumber(this); - initialize(port_); - } - - public ZMQSocketWrapper(SocketType type) { - this(type, STARTING_PORT_NUMBER); - } - - private static synchronized int nextPortNumber(ZMQSocketWrapper t) { - int port = STARTING_PORT_NUMBER; - while (portSocketMap_.containsKey(port)) { - port++; - } - portSocketMap_.put(port, t); - return port; - } - - public int getPort() { - return port_; - } - - public abstract void initialize(int port); - - public void close() { - synchronized (socket_) { - if (closed_) { - return; - } - socket_.close(); - portSocketMap_.remove(this.getPort()); - closed_ = true; - } - } - - /** - * Extract all byte arrays so their values can be sent seperately - * @param binaryData - * @param json - * @throws JSONException - */ - private void recurseBinaryData(ArrayList binaryData, Object json) throws JSONException { - if (json instanceof JSONObject) { - Iterator keys = ((JSONObject) json).keys(); - while (keys.hasNext()) { - String key = keys.next(); - Object value = ((JSONObject) json).get(key); - if (value instanceof byte[]) { - binaryData.add((byte[]) value); - } else if (value instanceof JSONObject || value instanceof JSONArray) { - recurseBinaryData(binaryData, value); - } - } - } else if (json instanceof JSONArray) { - for (int i = 0; i < ((JSONArray) json).length(); i++) { - Object value = ((JSONArray) json).get(i); - if (value instanceof byte[]) { - binaryData.add((byte[]) value); - } else if (value instanceof JSONObject || value instanceof JSONArray) { - recurseBinaryData(binaryData, value); - } - } - } - } - - /** - * Send a json object as a message, removing binary data as needed and sending in multiple parts - * @param json - */ - public void sendMessage(JSONObject json) { - ArrayList byteData = new ArrayList(); - try { - recurseBinaryData(byteData, json); - } catch (JSONException e) { - throw new RuntimeException(e); - } - - if (byteData.size() == 0) { - socket_.send(json.toString().getBytes( StandardCharsets.ISO_8859_1)); - } else { - socket_.sendMore(json.toString().getBytes( StandardCharsets.ISO_8859_1)); - for (int i = 0; i < byteData.size() - 1; i ++) { - socket_.sendMore(ByteBuffer.allocate(4).order(ByteOrder.nativeOrder()).putInt( - System.identityHashCode(byteData.get(i))).array()); - socket_.sendMore(byteData.get(i)); - } - socket_.sendMore(ByteBuffer.allocate(4).order(ByteOrder.nativeOrder()).putInt( - System.identityHashCode(byteData.get(byteData.size() - 1))).array()); - socket_.send(byteData.get(byteData.size() - 1)); - } - } - - /** - * Recursively search through recieved message and replace byte buffer hashes with their values - */ - private void insertByteBuffer(Object json, long hash, byte[] value) throws JSONException { - if (json instanceof JSONObject) { - Iterator keys = ((JSONObject) json).keys(); - while (keys.hasNext()) { - String key = keys.next(); - Object o = ((JSONObject) json).get(key); - if (o instanceof String && ((String) o).contains("@")) { - int intID = Integer.parseInt(((String) o).substring(1).split("_")[0]); - int bytesPerEntry = Integer.parseInt(((String) o).substring(1).split("_")[1]); - if (intID == hash) { - ((JSONObject) json).put(key, decodeByteArray(value, bytesPerEntry)); - return; - } - } else if (o instanceof JSONObject) { - insertByteBuffer(o, hash, value); - } else if (o instanceof JSONArray) { - insertByteBuffer(o, hash, value); - } - } - } else if (json instanceof JSONArray) { - for (int i = 0; i < ((JSONArray) json).length(); i++) { - Object o = ((JSONArray) json).get(i); - if (o instanceof String && ((String) o).contains("@")) { - int intID = Integer.parseInt(((String) o).substring(1).split("_")[0]); - int bytesPerEntry = Integer.parseInt(((String) o).substring(1).split("_")[1]); - if (intID == hash) { - ((JSONArray) json).put(i, decodeByteArray(value, bytesPerEntry)); - return; - } - } else if (o instanceof JSONObject) { - insertByteBuffer(o, hash, value); - } else if (o instanceof JSONArray) { - insertByteBuffer(o, hash, value); - } - } - } - } - - //TODO: this is redundant to a function in ZMQUtil. - // There are multiple mechanisms for byte data to be decoded. these should be consolidated - // and consistent with the outgoing messages - private Object decodeByteArray(byte[] value, int bytesPerEntry) { - if (bytesPerEntry == 0) { - return value; // it was sent over as raw binary. Might get converted at a high level - } else if (bytesPerEntry == 1) { - return value; - } else if (bytesPerEntry == 2) { - short[] shorts = new short[value.length / 2]; - ByteBuffer.wrap(value).order(ByteOrder.nativeOrder()).asShortBuffer().get(shorts); - return shorts; - } else if (bytesPerEntry == 4) { - int[] ints = new int[value.length / 4]; - ByteBuffer.wrap(value).order(ByteOrder.nativeOrder()).asIntBuffer().get(ints); - return ints; - } - throw new RuntimeException("unknown bytes per pixel"); - } - - public JSONObject receiveMessage() { - ArrayList byteData = new ArrayList(); - String message = new String(socket_.recv(), StandardCharsets.ISO_8859_1); - JSONObject json; - try { - json = new JSONObject(message); - } catch (JSONException e) { - throw new RuntimeException(e); - } - while (socket_.hasReceiveMore()) { - byteData.add(socket_.recv()); - } - //Unpack byte data - for (int i = 0; i < byteData.size(); i+=2) { - ByteBuffer byteBuffer = ByteBuffer.wrap(byteData.get(i)); - int hash = byteBuffer.order(ByteOrder.nativeOrder()).asIntBuffer().get(); - byte[] value = byteData.get(i + 1); - try { - insertByteBuffer(json, hash, value); - } catch (JSONException e) { - throw new RuntimeException(e); - } - } - return json; - } - -} diff --git a/java/src/main/java/org/micromanager/internal/zmq/ZMQUtil.java b/java/src/main/java/org/micromanager/internal/zmq/ZMQUtil.java deleted file mode 100644 index 18490fc6..00000000 --- a/java/src/main/java/org/micromanager/internal/zmq/ZMQUtil.java +++ /dev/null @@ -1,670 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package org.micromanager.internal.zmq; - -import java.io.*; -import java.lang.reflect.*; -import java.net.*; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.ShortBuffer; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; - -import mmcorej.org.json.JSONArray; -import mmcorej.org.json.JSONException; -import mmcorej.org.json.JSONObject; - - -/** - * - * @author henrypinkard - */ -public class ZMQUtil { - - private static Collection classLoaders_; - private String[] excludedPaths_; - private HashMap> packageAPIClasses_ = new HashMap>(); - - - public final static Set PRIMITIVES = new HashSet(); - public final static Map> PRIMITIVE_NAME_CLASS_MAP = new HashMap>(); - public final static Map> PRIMITIVE_ARRAY_NAME_CLASS_MAP = new HashMap>(); - - static { - PRIMITIVES.add(Boolean.class); - PRIMITIVES.add(Byte.class); - PRIMITIVES.add(Short.class); - PRIMITIVES.add(Character.class); - PRIMITIVES.add(Integer.class); - PRIMITIVES.add(Long.class); - PRIMITIVES.add(Float.class); - PRIMITIVES.add(Double.class); - - PRIMITIVE_NAME_CLASS_MAP.put("boolean", boolean.class); - PRIMITIVE_NAME_CLASS_MAP.put("java.lang.Boolean", Boolean.class); - PRIMITIVE_NAME_CLASS_MAP.put("byte", byte.class); - PRIMITIVE_NAME_CLASS_MAP.put("java.lang.Byte", Byte.class); - PRIMITIVE_NAME_CLASS_MAP.put("short", short.class); - PRIMITIVE_NAME_CLASS_MAP.put("java.lang.Short", Short.class); - PRIMITIVE_NAME_CLASS_MAP.put("char", char.class); - PRIMITIVE_NAME_CLASS_MAP.put("java.lang.Character", Character.class); - PRIMITIVE_NAME_CLASS_MAP.put("int", int.class); - PRIMITIVE_NAME_CLASS_MAP.put("java.lang.Integer", Integer.class); - PRIMITIVE_NAME_CLASS_MAP.put("long", long.class); - PRIMITIVE_NAME_CLASS_MAP.put("java.lang.Long", Long.class); - PRIMITIVE_NAME_CLASS_MAP.put("float", float.class); - PRIMITIVE_NAME_CLASS_MAP.put("java.lang.Float", Float.class); - PRIMITIVE_NAME_CLASS_MAP.put("double", double.class); - PRIMITIVE_NAME_CLASS_MAP.put("java.lang.Double", Double.class); - - PRIMITIVE_ARRAY_NAME_CLASS_MAP.put("boolean[]", boolean[].class); - PRIMITIVE_ARRAY_NAME_CLASS_MAP.put("byte[]", byte[].class); - PRIMITIVE_ARRAY_NAME_CLASS_MAP.put("short[]", short[].class); - PRIMITIVE_ARRAY_NAME_CLASS_MAP.put("char[]", char[].class); - PRIMITIVE_ARRAY_NAME_CLASS_MAP.put("int[]", int[].class); - PRIMITIVE_ARRAY_NAME_CLASS_MAP.put("long[]", long[].class); - PRIMITIVE_ARRAY_NAME_CLASS_MAP.put("float[]", float[].class); - PRIMITIVE_ARRAY_NAME_CLASS_MAP.put("double[]", double[].class); - } - - public ZMQUtil(Collection cl, String[] excludePaths) { - classLoaders_ = cl; - excludedPaths_ = excludePaths; - } - - /** - * Recursively seek through the directory structure under the specified - * root and generate a list of files that match the given extension. - * Just a passthrough to the actual recursive method. - */ - static ArrayList findPaths(String root, String extension) { - ArrayList result = new ArrayList<>(); - // Short-circuit if we're called with a non-directory. - if (!(new File(root).isDirectory())) { - if (root.endsWith(extension)) { - result.add(root); - } - return result; - } - recursiveFindPaths(new File(root), extension, result); - return result; - } - - private static void recursiveFindPaths(File root, String extension, - ArrayList result) { - File[] items = root.listFiles(); - for (File item : items) { - if (item.getAbsolutePath().endsWith(extension)) { - result.add(item.getAbsolutePath()); - } - else if (item.isDirectory()) { - recursiveFindPaths(item, extension, result); - } - } - } - - private static final ByteOrder BYTE_ORDER = ByteOrder.nativeOrder(); - - protected static Object deserialize(byte[] message, Function deserializationFn) { - try { - String s = new String(message); - JSONObject json = new JSONObject(s); - String type = json.getString("type"); - if (type.equals("object")) { - Object result = deserializationFn.apply(json.getJSONObject("value")); - return result; - } - throw new RuntimeException("Problem decoding message"); - } catch (JSONException ex) { - throw new RuntimeException("Problem turning message into JSON. "); - } - } - - /** - * Convert objects that will be serialized into JSON. Used for objects that - * will pass out and never need to be returned - * - */ - public static JSONObject toJSON(Object o) { - JSONObject json = new JSONObject(); - try { - if (o instanceof Exception) { - json.put("type", "exception"); - Throwable root = ((Exception) o).getCause() == null - ? ((Exception) o) : ((Exception) o).getCause(); - String s = root.toString() + "\n"; - for (StackTraceElement el : root.getStackTrace()) { - s += el.toString() + "\n"; - } - json.put("value", s); - } else if (o instanceof String) { - json.put("type", "string"); - json.put("value", o); - } else if (o == null) { - json.put("type", "none"); - } else if (PRIMITIVES.contains(o.getClass())) { - json.put("type", "primitive"); - json.put("value", o); - } else if (o.getClass().equals(JSONObject.class)) { - json.put("type", "object"); - json.put("class", "JSONObject"); - json.put("value", o.toString()); - } else if (o.getClass().equals(byte[].class)) { - json.put("type", "byte-array"); - json.put("value", encodeArray(o)); - } else if (o.getClass().equals(short[].class)) { - json.put("type", "short-array"); - json.put("value", encodeArray(o)); - } else if (o.getClass().equals(double[].class)) { - json.put("type", "double-array"); - json.put("value", encodeArray(o)); - } else if (o.getClass().equals(int[].class)) { - json.put("type", "int-array"); - json.put("value", encodeArray(o)); - } else if (o.getClass().equals(float[].class)) { - json.put("type", "float-array"); - json.put("value", encodeArray(o)); - } else { - return null; - } - } catch (JSONException e) { - throw new RuntimeException(e); - } - return json; - } - - /** - * This version serializes primitves, converts lists to JSONArrays, and sends - * out pointers to Objects - * - * @param externalObjects the servers map of its external objects, needed for memory manamgement - * @param o Object to be serialized - * @param json JSONObject that will contain the serialized Object can not be - * @param port Port that the object is being sent out on - */ - public void serialize(ConcurrentHashMap externalObjects, Object o, JSONObject json, int port) { - try { - JSONObject converted = toJSON(o); - if (converted != null) { - //Can be driectly converted into a serialized object (i.e. primitive)--copy into - converted.keys().forEachRemaining(new Consumer() { - @Override - public void accept(String t) { - try { - json.put(t, converted.get(t)); - } catch (JSONException ex) { - throw new RuntimeException(ex); //Wont happen - } - } - }); - } else { - //Don't serialize the object, but rather send out its name so that python side - //can construct a shadow version of it - //Keep track of which objects have been sent out, so that garbage collection can be synchronized between - //the two languages - String hash = Integer.toHexString(System.identityHashCode(o)); - //Add a random UUID to account for the fact that there may be multiple - //pythons shadows of the same object - hash += UUID.randomUUID(); - externalObjects.put(hash, o); - json.put("type", "unserialized-object"); - json.put("class", o.getClass().getName()); - json.put("hash-code", hash); - json.put("port", port); - - ArrayList apiInterfaces = new ArrayList<>(); - if (o.getClass().equals(Class.class)) { - //return the class itself, e.g. to call static methods - for (Class c : ((Class) o).getInterfaces()) { - apiInterfaces.add(c); - } - apiInterfaces.add((Class) o); - } else { - //Return all interfaces and superclasses interfaces - Class clazz = o.getClass(); - do { - apiInterfaces.add(clazz); - for (Class inter : clazz.getInterfaces()) { - apiInterfaces.add(inter); - recursiveAddInterfaces(apiInterfaces, inter); - } - clazz = clazz.getSuperclass(); - } while (clazz != null); - } - - if (apiInterfaces.isEmpty()) { - throw new RuntimeException("Couldn't find " + o.getClass().getName() - + " on classpath, or this is an internal class that was accidentally exposed"); - } - //List all API interfaces this class implments in case its passed - //back as an argument to another function - JSONArray e = new JSONArray(); - json.put("interfaces", e); - for (Class c : apiInterfaces) { - e.put(c.getName()); - } - - //copy in all public fields of the object - JSONArray f = new JSONArray(); - json.put("fields", f); - for (Field field : o.getClass().getFields()) { - int modifiers = field.getModifiers(); - if (Modifier.isPublic(modifiers)) { - f.put(field.getName()); - } - } - - json.put("api", parseAPI(apiInterfaces)); - } - } catch (JSONException e) { - throw new RuntimeException(e); - } - } - - private void recursiveAddInterfaces(ArrayList apiInterfaces, Class inter) { - for (Class extendedInterface : inter.getInterfaces()) { - apiInterfaces.add(extendedInterface); - recursiveAddInterfaces(apiInterfaces, extendedInterface); - } - } - - /** - * Convert array of primitives to a String - * - * @param array - * @return - */ - public static byte[] encodeArray(Object array) { - byte[] byteArray = null; - if (array instanceof byte[]) { - byteArray = (byte[]) array; - } else if (array instanceof short[]) { - ByteBuffer buffer = ByteBuffer.allocate((((short[]) array)).length * Short.BYTES); - buffer.order(BYTE_ORDER).asShortBuffer().put((short[]) array); - byteArray = buffer.array(); - } else if (array instanceof int[]) { - ByteBuffer buffer = ByteBuffer.allocate((((int[]) array)).length * Integer.BYTES); - buffer.order(BYTE_ORDER).asIntBuffer().put((int[]) array); - byteArray = buffer.array(); - } else if (array instanceof double[]) { - ByteBuffer buffer = ByteBuffer.allocate((((double[]) array)).length * Double.BYTES); - buffer.order(BYTE_ORDER).asDoubleBuffer().put((double[]) array); - byteArray = buffer.array(); - } else if (array instanceof float[]) { - ByteBuffer buffer = ByteBuffer.allocate((((float[]) array)).length * Float.BYTES); - buffer.order(BYTE_ORDER).asFloatBuffer().put((float[]) array); - byteArray = buffer.array(); - } - return byteArray; - } - - public static Object decodeArray(byte[] byteArray, Class arrayClass) { - if (arrayClass.equals(byte[].class)) { - return byteArray; - } else if (arrayClass.equals(short[].class)) { - short[] shorts = new short[byteArray.length / 2]; - ByteBuffer.wrap(byteArray).order(ByteOrder.nativeOrder()).asShortBuffer().get(shorts); - return shorts; - } else if (arrayClass.equals(int[].class)) { - int[] ints = new int[byteArray.length / 4]; - ByteBuffer.wrap(byteArray).order(ByteOrder.nativeOrder()).asIntBuffer().get(ints); - return ints; - } else if (arrayClass.equals(double[].class)) { - double[] doubles = new double[byteArray.length / 8]; - ByteBuffer.wrap(byteArray).order(ByteOrder.nativeOrder()).asDoubleBuffer().get(doubles); - return doubles; - } else if (arrayClass.equals(float[].class)) { - float[] floats = new float[byteArray.length / 4]; - ByteBuffer.wrap(byteArray).order(ByteOrder.nativeOrder()).asFloatBuffer().get(floats); - return floats; - } - throw new RuntimeException("unknown array type"); - } - - public static JSONArray parseConstructors(String classpath, Function classMapper) - throws JSONException, ClassNotFoundException { - JSONArray methodArray = new JSONArray(); - - Class clazz = loadClass(classpath); - - Constructor[] m = clazz.getConstructors(); - for (Constructor c : m) { - JSONObject methJSON = new JSONObject(); - methJSON.put("name", c.getName()); - JSONArray args = new JSONArray(); - for (Class arg : c.getParameterTypes()) { - args.put(arg.getCanonicalName()); - } - methJSON.put("arguments", args); - methodArray.put(methJSON); - } - // add in 0 argmunet "constructors" for interfaces that get mapped to an existing instance of a class - if (clazz.isInterface()) { - if (classMapper.apply(clazz) != null) { - JSONObject methJSON = new JSONObject(); - methJSON.put("name", clazz.getName()); - JSONArray args = new JSONArray(); - methJSON.put("arguments", args); - methodArray.put(methJSON); - } - } - - return methodArray; - } - - /** - * Go through all methods of the given class and put them into a big JSON - * array that describes the API - * - * @param apiClasses Classes to be translated into JSON - * @return Classes translated to JSON - * @throws JSONException - */ - private static JSONArray parseAPI(ArrayList apiClasses) throws JSONException { - JSONArray methodArray = new JSONArray(); - for (Class clazz : apiClasses) { - for (Method method : clazz.getDeclaredMethods()) { - JSONObject methJSON = new JSONObject(); - methJSON.put("name", method.getName()); - methJSON.put("return-type", method.getReturnType().getTypeName()); - JSONArray args = new JSONArray(); - for (Class arg : method.getParameterTypes()) { - args.put(arg.getTypeName()); - } - methJSON.put("arguments", args); -// JSONArray argNames = new JSONArray(); -// for (Parameter p : method.getParameters()) { -// argNames.put(p.getName()); -// } -// methJSON.put("argument-names", argNames); - methodArray.put(methJSON); - } - } - return methodArray; - } - - public static Collection getPackagesFromJars(URLClassLoader cl) { - HashSet packages = new HashSet(); - for (URL u : cl.getURLs()) { - try { - ZipInputStream zip = new ZipInputStream(new FileInputStream(URLDecoder.decode(u.getFile(), "UTF-8"))); - for (ZipEntry entry = zip.getNextEntry(); entry != null; entry = zip.getNextEntry()) { - if (!entry.isDirectory() && entry.getName().endsWith(".class") && !entry.getName().contains("$")) { - // This ZipEntry represents a class. Now, what class does it represent? - String className = entry.getName().replace('/', '.'); - className = className.substring(0, className.length() - 6); // including ".class" - try { - Class clazz = loadClass(className); - try { - if (clazz.getPackage() != null) { - packages.add(clazz.getPackage().getName()); - } - } catch (Exception sdf) { - } - } catch (IllegalAccessError e) { - //Don't know why this happens but it doesnt seem to matter - } - } - } - } catch (Exception e) { -// e.printStackTrace(); - continue; - } - } - return packages; - } - - public static Set getPackages() { - - Set packages = new HashSet(); - Package[] p = Package.getPackages(); - - for (Package pa : p) { - packages.add(pa.getName()); - } - return packages; - } - - protected static Class loadClass(String path) { - for (ClassLoader cl : classLoaders_) { - try { - return cl.loadClass(path); - } catch (ClassNotFoundException e) { - //On to the next one - } catch (NoClassDefFoundError e) { - //On to the next one - } catch (UnsupportedClassVersionError e) { - System.err.println(path + e.getMessage()); - } - - } - throw new RuntimeException("Class not found on any classloaders"); - } - - public Set getPackageClasses(String packageName) throws UnsupportedEncodingException { - if (packageAPIClasses_.containsKey(packageName)) { - return packageAPIClasses_.get(packageName); - } - - Set packageClasses = new HashSet(); - if (packageName.contains("java.")) { - //java classes are different for some reason - //Aparently you can't find java classes in a package without a third party library - } else { - for (ClassLoader classLoader : classLoaders_) { - String path = packageName.replace('.', '/'); - Enumeration resources; - try { - resources = classLoader.getResources(path); - } catch (IOException ex) { - throw new RuntimeException("Invalid package name in ZMQ server: " + path); - } - List dirs = new ArrayList<>(); - while (resources.hasMoreElements()) { - URL resource = resources.nextElement(); - String file = resource.getFile().replaceAll("^file:", ""); - file = (String) URLDecoder.decode(file, "UTF-8"); - - dirs.add(new File(file)); - } - - for (File directory : dirs) { - if (directory.getAbsolutePath().contains(".jar")) { - packageClasses.addAll(getClassesFromJarFile(directory)); - } else { - packageClasses.addAll(getClassesFromDirectory(packageName, directory)); - } - } - } - } - - //filter out internal classes - Stream clazzStream = packageClasses.stream(); - Set classSet = clazzStream.filter(new Predicate() { - @Override - public boolean test(Class t) { - Package p = t.getPackage(); - if (p == null) { - return true; - } - for (String exclude : excludedPaths_) { - if (t.getPackage().getName().contains(exclude)) { - return false; - } - } - return true; - } - }).collect(Collectors.toSet()); - - packageAPIClasses_.put(packageName, classSet); - return classSet; - } - - private static Collection getClassesFromJarFile(File directory) { - List classes = new ArrayList(); - - try { - String jarPath = Stream.of(directory.getAbsolutePath().split(File.pathSeparator)) - .flatMap((String t) -> Stream.of(t.split("!"))) - .filter((String t) -> t.contains(".jar")).findFirst().get(); - JarFile jarFile = new JarFile(jarPath); - Enumeration entries = jarFile.entries(); - while (entries.hasMoreElements()) { - JarEntry entry = entries.nextElement(); - String name = entry.getName(); - //include classes but not inner classes - if (name.endsWith(".class") && !name.contains("$")) { - try { - classes.add(Class.forName(name.replace("/", "."). - substring(0, name.length() - 6))); - } catch (ClassNotFoundException ex) { - ex.printStackTrace(); - } - } - } - } catch (IOException ex) { - throw new RuntimeException(ex); - } - - return classes; - } - - private static Collection getClassesFromDirectory(String packageName, File directory) { - List classes = new ArrayList(); - - // get jar files from top-level directory - List jarFiles = listFiles(directory, new FilenameFilter() { - @Override - public boolean accept(File dir, String name) { - return name.endsWith(".jar"); - } - }, false); - - for (File file : jarFiles) { - classes.addAll(getClassesFromJarFile(file)); - } - - // get all class-files - List classFiles = listFiles(directory, new FilenameFilter() { - @Override - public boolean accept(File dir, String name) { - return name.endsWith(".class"); - } - }, true); - - for (File file : classFiles) { - if (!file.isDirectory()) { - try { - classes.add(Class.forName(packageName + '.' + file.getName(). - substring(0, file.getName().length() - 6))); - } catch (ClassNotFoundException ex) { - ex.printStackTrace(); -// studio_.logs().logError("Failed to load class: " + file.getName()); - } - } - } - return classes; - } - - private static List listFiles(File directory, FilenameFilter filter, boolean recurse) { - List files = new ArrayList(); - File[] entries = directory.listFiles(); - - // Go over entries - for (File entry : entries) { - // If there is no filter or the filter accepts the - // file / directory, add it to the list - if (filter == null || filter.accept(directory, entry.getName())) { - files.add(entry); - } - - // If the file is a directory and the recurse flag - // is set, recurse into the directory - if (recurse && entry.isDirectory()) { - files.addAll(listFiles(entry, filter, recurse)); - } - } - - // Return collection of files - return files; - } - - static Object convertToPrimitiveClass(Object primitive, Class argClass) { - if (argClass.equals(boolean.class) || argClass.equals(Boolean.class)) { - return primitive; - } else if (argClass.equals(char.class) || argClass.equals(Character.class)) { - return (char) ((Number) primitive).intValue(); - } else if (argClass.equals(byte.class) || argClass.equals(Byte.class)) { - return ((Number) primitive).byteValue(); - } else if (argClass.equals(short.class) || argClass.equals(Short.class)) { - return ((Number) primitive).shortValue(); - } else if (argClass.equals(int.class) || argClass.equals(Integer.class)) { - return ((Number) primitive).intValue(); - } else if (argClass.equals(long.class) || argClass.equals(Long.class)) { - return ((Number) primitive).longValue(); - } else if (argClass.equals(float.class) || argClass.equals(Float.class)) { - return ((Number) primitive).floatValue(); - } else if (argClass.equals(double.class) || argClass.equals(Double.class)) { - return ((Number) primitive).doubleValue(); - } else { - throw new RuntimeException("Unknown class"); - } - } - - public static Object convertToPrimitiveArray(byte[] bytes, Object clazz) { - if (clazz.equals(byte[].class)) { - return bytes; - } else if (clazz.equals(short[].class)) { - short[] shorts = new short[bytes.length / 2]; - ByteBuffer.wrap(bytes).asShortBuffer().get(shorts); - return shorts; - } else if (clazz.equals(float[].class)) { - float[] floats = new float[bytes.length / 4]; - ByteBuffer.wrap(bytes).asFloatBuffer().get(floats); - return floats; - } else if (clazz.equals(double[].class)) { - double[] doubles = new double[bytes.length / 8]; - ByteBuffer.wrap(bytes).asDoubleBuffer().get(doubles); - return doubles; - } else if (clazz.equals(int[].class)) { - int[] ints = new int[bytes.length / 4]; - ByteBuffer.wrap(bytes).asIntBuffer().get(ints); - return ints; - } else if (clazz.equals(boolean[].class)) { - // TODO: boolean array deserialzation - throw new RuntimeException("Not sure how to handle booleans yet"); - } else if (clazz.equals(char[].class)) { - char[] chars = new char[bytes.length / 2]; - ByteBuffer.wrap(bytes).asCharBuffer().get(chars); - return chars; - } else if (clazz.equals(long[].class)) { - long[] longs = new long[bytes.length / 8]; - ByteBuffer.wrap(bytes).asLongBuffer().get(longs); - return longs; - } - throw new RuntimeException("unknown type " + clazz.toString()); - } - -} diff --git a/java/src/main/java/org/micromanager/remote/HeadlessLauncher.java b/java/src/main/java/org/micromanager/remote/HeadlessLauncher.java index 46b908bd..a549a1a8 100644 --- a/java/src/main/java/org/micromanager/remote/HeadlessLauncher.java +++ b/java/src/main/java/org/micromanager/remote/HeadlessLauncher.java @@ -2,7 +2,7 @@ import mmcorej.CMMCore; import org.micromanager.acqj.internal.Engine; -import org.micromanager.internal.zmq.ZMQServer; +import org.micromanager.pyjavaz.ZMQServer; import javax.swing.*; import javax.swing.plaf.ColorUIResource; diff --git a/java/src/main/java/org/micromanager/remote/RemoteAcqHook.java b/java/src/main/java/org/micromanager/remote/RemoteAcqHook.java index a9f502e3..5e629900 100644 --- a/java/src/main/java/org/micromanager/remote/RemoteAcqHook.java +++ b/java/src/main/java/org/micromanager/remote/RemoteAcqHook.java @@ -15,8 +15,8 @@ import org.micromanager.acqj.api.AcquisitionAPI; import org.micromanager.acqj.main.AcquisitionEvent; import org.micromanager.acqj.api.AcquisitionHook; -import org.micromanager.internal.zmq.ZMQPullSocket; -import org.micromanager.internal.zmq.ZMQPushSocket; +import org.micromanager.pyjavaz.ZMQPullSocket; +import org.micromanager.pyjavaz.ZMQPushSocket; /** * diff --git a/java/src/main/java/org/micromanager/remote/RemoteCoreCallback.java b/java/src/main/java/org/micromanager/remote/RemoteCoreCallback.java index 945011ef..3c62ee6c 100644 --- a/java/src/main/java/org/micromanager/remote/RemoteCoreCallback.java +++ b/java/src/main/java/org/micromanager/remote/RemoteCoreCallback.java @@ -28,7 +28,7 @@ import mmcorej.org.json.JSONArray; import mmcorej.org.json.JSONException; import mmcorej.org.json.JSONObject; -import org.micromanager.internal.zmq.ZMQPushSocket; +import org.micromanager.pyjavaz.ZMQPushSocket; /** * Class that connects core callbacks to a ZMQPush socket so they can be dispatched to external clients diff --git a/java/src/main/java/org/micromanager/remote/RemoteEventSource.java b/java/src/main/java/org/micromanager/remote/RemoteEventSource.java index 461ce681..d584932c 100644 --- a/java/src/main/java/org/micromanager/remote/RemoteEventSource.java +++ b/java/src/main/java/org/micromanager/remote/RemoteEventSource.java @@ -17,7 +17,7 @@ import mmcorej.org.json.JSONObject; import org.micromanager.acqj.main.Acquisition; import org.micromanager.acqj.main.AcquisitionEvent; -import org.micromanager.internal.zmq.ZMQPullSocket; +import org.micromanager.pyjavaz.ZMQPullSocket; import javax.swing.*; diff --git a/java/src/main/java/org/micromanager/remote/RemoteImageProcessor.java b/java/src/main/java/org/micromanager/remote/RemoteImageProcessor.java index c825c59f..2f3ad653 100644 --- a/java/src/main/java/org/micromanager/remote/RemoteImageProcessor.java +++ b/java/src/main/java/org/micromanager/remote/RemoteImageProcessor.java @@ -17,9 +17,9 @@ import org.micromanager.acqj.main.AcqEngMetadata; import org.micromanager.acqj.api.TaggedImageProcessor; import org.micromanager.acqj.main.Acquisition; -import org.micromanager.internal.zmq.ZMQPullSocket; -import org.micromanager.internal.zmq.ZMQPushSocket; -import org.micromanager.internal.zmq.ZMQUtil; +import org.micromanager.pyjavaz.ZMQPullSocket; +import org.micromanager.pyjavaz.ZMQPushSocket; +import org.micromanager.pyjavaz.ZMQUtil; /** * Implements an ImageProcessor that sends/recieves images from a remote source diff --git a/java/src/main/java/org/micromanager/remote/RemoteNotificationHandler.java b/java/src/main/java/org/micromanager/remote/RemoteNotificationHandler.java index 8b1e4ad9..e9db6757 100644 --- a/java/src/main/java/org/micromanager/remote/RemoteNotificationHandler.java +++ b/java/src/main/java/org/micromanager/remote/RemoteNotificationHandler.java @@ -9,7 +9,7 @@ import org.micromanager.acqj.api.AcqNotificationListener; import org.micromanager.acqj.api.AcquisitionAPI; import org.micromanager.acqj.main.AcqNotification; -import org.micromanager.internal.zmq.ZMQPushSocket; +import org.micromanager.pyjavaz.ZMQPushSocket; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; diff --git a/java/src/main/java/org/micromanager/remote/RemoteStorageMonitor.java b/java/src/main/java/org/micromanager/remote/RemoteStorageMonitor.java index 2dbcc2f1..8d78fee5 100644 --- a/java/src/main/java/org/micromanager/remote/RemoteStorageMonitor.java +++ b/java/src/main/java/org/micromanager/remote/RemoteStorageMonitor.java @@ -10,10 +10,9 @@ import mmcorej.org.json.JSONException; import mmcorej.org.json.JSONObject; -import org.micromanager.internal.zmq.ZMQPushSocket; +import org.micromanager.pyjavaz.ZMQPushSocket; import org.micromanager.ndtiffstorage.ImageWrittenListener; import org.micromanager.ndtiffstorage.IndexEntryData; -import org.micromanager.ndtiffstorage.NDTiffStorage; /** * A class that broadcasts information about images that have finsihed saving to disk diff --git a/pycromanager/__init__.py b/pycromanager/__init__.py index 3d715064..3026a3bc 100644 --- a/pycromanager/__init__.py +++ b/pycromanager/__init__.py @@ -6,7 +6,7 @@ from pycromanager.headless import start_headless, stop_headless from pycromanager.mm_java_classes import Studio, Magellan from pycromanager.core import Core -from pycromanager.zmq_bridge.wrappers import JavaObject, JavaClass, PullSocket, PushSocket +from pyjavaz import JavaObject, JavaClass, PullSocket, PushSocket from pycromanager.acquisition.acq_eng_py.main.acq_notification import AcqNotification from pycromanager.logging import set_logger_instance, reset_logger_instance from ndtiff import Dataset diff --git a/pycromanager/acquisition/java_backend_acquisitions.py b/pycromanager/acquisition/java_backend_acquisitions.py index aef7c9c8..89b81e78 100644 --- a/pycromanager/acquisition/java_backend_acquisitions.py +++ b/pycromanager/acquisition/java_backend_acquisitions.py @@ -9,9 +9,9 @@ import threading from inspect import signature import time -from pycromanager.zmq_bridge.bridge import deserialize_array -from pycromanager.zmq_bridge.wrappers import PullSocket, PushSocket, JavaObject, JavaClass -from pycromanager.zmq_bridge.wrappers import DEFAULT_BRIDGE_PORT as DEFAULT_PORT +from pyjavaz import deserialize_array +from pyjavaz import PullSocket, PushSocket, JavaObject, JavaClass +from pyjavaz import DEFAULT_BRIDGE_PORT as DEFAULT_PORT from pycromanager.mm_java_classes import ZMQRemoteMMCoreJ, Magellan import pycromanager.logging as logging from ndtiff import Dataset diff --git a/pycromanager/headless.py b/pycromanager/headless.py index 0d0ad2c1..6bdb79f8 100644 --- a/pycromanager/headless.py +++ b/pycromanager/headless.py @@ -5,10 +5,10 @@ import types from pycromanager.acquisition.acq_eng_py.internal.engine import Engine -from pycromanager.zmq_bridge.bridge import _Bridge, server_terminated from pymmcore import CMMCore import pycromanager.logging as logging import pymmcore +from pyjavaz import DEFAULT_BRIDGE_PORT, server_terminated import re @@ -124,7 +124,7 @@ def start_headless( mm_app_path: str, config_file: str=None, java_loc: str=None, python_backend=False, core_log_path: str='', buffer_size_mb: int=1024, max_memory_mb: int=2000, - port: int=_Bridge.DEFAULT_PORT, debug=False): + port: int=DEFAULT_BRIDGE_PORT, debug=False): """ Start a Java process that contains the neccessary libraries for pycro-manager to run, so that it can be run independently of the Micro-Manager GUI/application. This calls diff --git a/pycromanager/mm_java_classes.py b/pycromanager/mm_java_classes.py index 5f5fbb58..ac5bf87a 100644 --- a/pycromanager/mm_java_classes.py +++ b/pycromanager/mm_java_classes.py @@ -1,7 +1,7 @@ """ Classes that wrap instance of known java objects for ease of use """ -from pycromanager.zmq_bridge.wrappers import JavaObject, PullSocket, DEFAULT_BRIDGE_PORT, DEFAULT_BRIDGE_TIMEOUT +from pyjavaz import JavaObject, PullSocket, DEFAULT_BRIDGE_PORT, DEFAULT_BRIDGE_TIMEOUT import threading class _CoreCallback: diff --git a/pycromanager/zmq_bridge/__init__.py b/pycromanager/zmq_bridge/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/pycromanager/zmq_bridge/bridge.py b/pycromanager/zmq_bridge/bridge.py deleted file mode 100644 index a9122c1e..00000000 --- a/pycromanager/zmq_bridge/bridge.py +++ /dev/null @@ -1,1047 +0,0 @@ -import json -import re -import time -import typing -import warnings -import inspect -import numpy as np -import zmq -import copy -import sys -from threading import Lock -import threading -import weakref -import atexit -import traceback -import pycromanager.logging as logging - - -class _DataSocket: - """ - Wrapper for ZMQ socket that sends and recieves dictionaries - Includes ZMQ client, push, and pull sockets - """ - - def __init__(self, context, port, type, debug=False, ip_address="127.0.0.1"): - # request reply socket - self._socket = context.socket(type) - # if 1000 messages are queued up, queue indefinitely until they can be sent - self._socket.setsockopt(zmq.SNDHWM, 1000) - # Set the send timeout to -1, making it block indefinitely - self._socket.setsockopt(zmq.SNDTIMEO, -1) - self._debug = debug - # store these as wekrefs so that circular refs dont prevent garbage collection - self._java_objects = weakref.WeakSet() - self._port = port - self._close_lock = Lock() - self._closed = False - if type == zmq.PUSH: - if debug: - logging.main_logger.debug("binding {}".format(port)) - self._socket.bind("tcp://{}:{}".format(ip_address, port)) - else: - if debug: - logging.main_logger.debug("connecting {}".format(port)) - self._socket.connect("tcp://{}:{}".format(ip_address, port)) - - def _register_java_object(self, object): - self._java_objects.add(object) - - def _convert_np_to_python(self, d): - """ - recursively search dictionary and convert any values from numpy floats/ints to - python floats/ints so they can be json serialized - :return: - """ - if type(d) != dict: - return - for k, v in d.items(): - if isinstance(v, dict): - self._convert_np_to_python(v) - elif type(v) == list: - for e in v: - self._convert_np_to_python(e) - elif np.issubdtype(type(v), np.floating): - d[k] = float(v) - elif np.issubdtype(type(v), np.integer): - d[k] = int(v) - - def _make_array_identifier(self, entry): - """ - make a string to replace bytes data or numpy array in message, which encode data type if numpy - """ - # make up a random 32 bit int as the identifier - # TODO: change to simple counting - identifier = np.random.randint(-(2 ** 31), 2 ** 31 - 1, 1, dtype=np.int32)[0] - # '@{some_number}_{bytes_per_pixel}' - # if its a numpy array, include bytes per pixel, otherwise just interpret it as raw byts - # TODO : I thinkg its always raw binary and the argument deserialization types handles conversion to java arrays - # This definitely could use some cleanup and simplification. Probably best to encode the data type here and remove - # argument deserialization types - return identifier, "@" + str(int(identifier)) + "_" + str( - 0 if isinstance(entry, bytes) else entry.dtype.itemsize - ) - - def _remove_bytes(self, bytes_data, structure): - if isinstance(structure, list): - for i, entry in enumerate(structure): - if isinstance(entry, bytes) or isinstance(entry, np.ndarray): - int_id, str_id = self._make_array_identifier(entry) - structure[i] = str_id - bytes_data.append((int_id, entry)) - elif isinstance(entry, list) or isinstance(entry, dict): - self._remove_bytes(bytes_data, entry) - elif isinstance(structure, dict): - for key in structure.keys(): - entry = structure[key] - if isinstance(entry, bytes) or isinstance(entry, np.ndarray): - int_id, str_id = self._make_array_identifier(entry) - structure[key] = str_id - bytes_data.append((int_id, entry)) - elif isinstance(entry, list) or isinstance(entry, dict): - self._remove_bytes(bytes_data, structure[key]) - - def send(self, message, timeout=None, suppress_debug_message=False): - if message is None: - message = {} - # make sure any np types convert to python types so they can be json serialized - self._convert_np_to_python(message) - # Send binary data in seperate messages so it doesnt need to be json serialized - bytes_data = [] - self._remove_bytes(bytes_data, message) - message_string = json.dumps(message) - if self._debug and not suppress_debug_message: - logging.main_logger.debug("sending: {}".format(message)) - # convert keys to byte array - key_vals = [(identifier.tobytes(), value) for identifier, value in bytes_data] - message_parts = [bytes(message_string, "iso-8859-1")] + [ - item for keyval in key_vals for item in keyval - ] - if self._socket is None: - raise Exception("Tried to send message through socket {}, which is already closed".format(self._port)) - if timeout == 0 or timeout is None: - self._socket.send_multipart(message_parts) - else: - start = time.time() - while 1000 * (time.time() - start) < timeout: - try: - self._socket.send_multipart(message_parts, flags=zmq.NOBLOCK) - return True - except zmq.ZMQError: - pass # ignore, keep trying - return False - - def _replace_bytes(self, dict_or_list, hash, value): - """ - Replace placeholders for byte arrays in JSON message with their actual values - """ - if isinstance(dict_or_list, dict): - for key in dict_or_list: - if isinstance(dict_or_list[key], str) and "@" in dict_or_list[key]: - hash_in_message = int( - dict_or_list[key].split("@")[1], 16 - ) # interpret hex hash string - if hash == hash_in_message: - dict_or_list[key] = value - return - elif isinstance(dict_or_list[key], list) or isinstance(dict_or_list[key], dict): - self._replace_bytes(dict_or_list[key], hash, value) - elif isinstance(dict_or_list, list): - for i, entry in enumerate(dict_or_list): - if isinstance(entry, str) and "@" in dict_or_list[entry]: - hash_in_message = int(entry.split("@")[1], 16) # interpret hex hash string - if hash == hash_in_message: - dict_or_list[i] = value - return - elif isinstance(entry, list) or isinstance(entry, dict): - self._replace_bytes(entry, hash, value) - - def receive(self, timeout=None, suppress_debug_message=False): - if timeout == 0 or timeout is None: - reply = self._socket.recv_multipart() - else: - start = time.time() - reply = None - while 1000 * (time.time() - start) < timeout: - try: - reply = self._socket.recv_multipart(flags=zmq.NOBLOCK) - if reply is not None: - break - except zmq.ZMQError: - pass # ignore, keep trying - if reply is None: - return reply - message = json.loads(reply[0].decode("iso-8859-1")) - # replace any byte data placeholders with the byte data itself - for i in np.arange(1, len(reply), 2): - # messages come in pairs: first is hash, second it byte data - identity_hash = int.from_bytes(reply[i], byteorder=sys.byteorder) - value = reply[i + 1] - self._replace_bytes(message, identity_hash, value) - - if self._debug and not suppress_debug_message: - logging.main_logger.debug("received: {}".format(message)) - self._check_exception(message) - return message - - def _check_exception(self, response): - if "type" in response and response["type"] == "exception": - raise Exception(response["value"]) - - def __del__(self): - self.close() # make sure it closes properly - - def close(self): - with self._close_lock: - if not self._closed: - for java_object in self._java_objects: - java_object._close() - del java_object # potentially redundant, trying to fix closing race condition - self._java_objects = None - self._socket.close() - while not self._socket.closed: - time.sleep(0.01) - self._socket = None - if self._debug: - logging.main_logger.debug('closed socket {}'.format(self._port)) - self._closed = True - -def server_terminated(port): - """ - Call when the server on the Java side has been terminated. There is - no way to detect this directly due to the server-client architecture. - So this function will tell all JavaObjectShadow instances to not wait - around for a response from the server. - """ - _Bridge._ports_with_terminated_servers.add(port) - -class _Bridge: - """ - Create an object which acts as a client to a corresponding server (running in a Java process). - This enables construction and interaction with arbitrary java objects. Bridge.close() should be explicitly - called when finished - """ - - DEFAULT_PORT = 4827 - DEFAULT_TIMEOUT = 500 - _EXPECTED_ZMQ_SERVER_VERSION = "5.1.0" - - _bridge_creation_lock = threading.Lock() - _cached_bridges_by_port_and_thread = {} - _ports_with_terminated_servers = set() - - - @staticmethod - def create_or_get_existing_bridge(port: int=DEFAULT_PORT, convert_camel_case: bool=True, - debug: bool=False, ip_address: str="127.0.0.1", timeout: int=DEFAULT_TIMEOUT, iterate: bool = False): - """ - Get a bridge for a given port and thread. If a bridge for that port/thread combo already exists, - return it - """ - with _Bridge._bridge_creation_lock: - thread_id = threading.current_thread().ident - port_thread_id = (port, thread_id) - - # return the existing cached bridge if it exists, otherwise make a new one - if port_thread_id in _Bridge._cached_bridges_by_port_and_thread.keys(): - bridge = _Bridge._cached_bridges_by_port_and_thread[port_thread_id]() - if bridge is None: - raise Exception("Bridge for port {} and thread {} has been " - "closed but not removed".format(port, threading.current_thread().name)) - if debug: - logging.main_logger.debug("returning cached bridge for port {} thread {}".format( - port, threading.current_thread().name)) - return bridge - else: - if debug: - logging.main_logger.debug("creating new bridge for port {} thread {}".format( - port, threading.current_thread().name)) - b = _Bridge(port, convert_camel_case, debug, ip_address, timeout, iterate) - # store weak refs so that the existence of thread/port bridge caching doesn't prevent - # the garbage collection of unused bridge objects - _Bridge._cached_bridges_by_port_and_thread[port_thread_id] = weakref.ref(b) - return b - - def __init__( - self, port: int=DEFAULT_PORT, convert_camel_case: bool=True, - debug: bool=False, ip_address: str="127.0.0.1", timeout: int=DEFAULT_TIMEOUT, iterate: bool = False - ): - """ - This constructor should not be called directly. Instead, use the static method create_or_get_existing_bridge - Parameters - ---------- - port : int - The port on which the bridge operates - convert_camel_case : bool - If True, methods for Java objects that are passed across the bridge - will have their names converted from camel case to underscores. i.e. class.methodName() - becomes class.method_name() - debug : bool - If True print helpful stuff for debugging - iterate : bool - If True, ListArray will be iterated and give lists - """ - thread_id = threading.current_thread().ident - port_thread_id = (port, thread_id) - self._port_thread_id = port_thread_id - self._ip_address = ip_address - self.port = port - self._convert_camel_case = convert_camel_case - self._debug = debug - self._timeout = timeout - self._iterate = iterate - self._main_socket = _DataSocket( - zmq.Context.instance(), port, zmq.REQ, debug=debug, ip_address=self._ip_address - ) - self._main_socket.send({"command": "connect", "debug": debug}) - self._class_factory = _JavaClassFactory() - reply_json = self._main_socket.receive(timeout=timeout) - if reply_json is None: - raise Exception( - f"Socket timed out after {timeout} milliseconds" - ) - if reply_json["type"] == "exception": - raise Exception(reply_json["message"]) - if "version" not in reply_json: - reply_json["version"] = "2.0.0" # before version was added - if reply_json["version"] != self._EXPECTED_ZMQ_SERVER_VERSION: - warnings.warn( - "Version mistmatch between Java ZMQ server and Python client. " - "\nJava ZMQ server version: {}\nPython client expected version: {}" - "\n To fix, update to BOTH latest pycromanager and latest micro-manager nightly build".format( - reply_json["version"], self._EXPECTED_ZMQ_SERVER_VERSION - ) - ) - - - def __del__(self): - with _Bridge._bridge_creation_lock: - # Have to cache the port thread id in the bridge instance and then - # use it to figure out which one to delete rather than checking the current thread - # because for some reason at exit time this method can get called by a different thread - # than the one that created the bridge - del _Bridge._cached_bridges_by_port_and_thread[self._port_thread_id] - if self._debug: - logging.main_logger.debug("BRIDGE DESCTRUCTOR for {} on port {} thread {}".format( - str(self), self.port, threading.current_thread().name)) - logging.main_logger.debug("Running on thread {}".format(threading.current_thread().name)) - - - - def _send(self, message, timeout=None): - """ - Send a message over the main socket - """ - return self._main_socket.send(message, timeout=timeout) - - def _receive(self, timeout=None): - """ - Send a message over the main socket - """ - return self._main_socket.receive(timeout=timeout) - - def _deserialize_object(self, serialized_object) -> typing.Type["_JavaObjectShadow"]: - """ - Deserialize the serialized description of a Java object and return a constructor for the shadow - """ - return self._class_factory.create( - serialized_object, convert_camel_case=self._convert_camel_case - ) - - def _construct_java_object(self, classpath: str, new_socket: bool=False, args: list=None, - debug: bool=False): - """ - Create a new instance of a an object on the Java side. Returns a Python "Shadow" of the object, which behaves - just like the object on the Java side (i.e. same methods, fields). Methods of the object can be inferred at - runtime using iPython autocomplete - - Parameters - ---------- - classpath : str - Full classpath of the java object - new_socket : bool - If True, will create new java object on a new port so that blocking calls will not interfere - with the bridges master port - args : list - list of arguments to the constructor, if applicable - debug : bool - If True, print debugging messages - Returns - ------- - - Python "Shadow" to the Java object - """ - if args is None: - args = [] - # classpath_minus_class = '.'.join(classpath.split('.')[:-1]) - # query the server for constructors matching this classpath - message = {"command": "get-constructors", "classpath": classpath} - self._main_socket.send(message) - constructors = self._main_socket.receive()["api"] - - methods_with_name = [m for m in constructors if m["name"] == classpath] - if len(methods_with_name) == 0: - raise Exception("No valid java constructor found with classpath {}".format(classpath)) - valid_method_spec, deserialize_types = _check_method_args(methods_with_name, args) - - # Calling a constructor, rather than getting return from method - message = { - "command": "constructor", - "classpath": classpath, - "argument-types": valid_method_spec["arguments"], - "argument-deserialization-types": deserialize_types, - "arguments": _package_arguments(valid_method_spec, args), - } - if new_socket: - message["new-port"] = True - self._main_socket.send(message) - serialized_object = self._main_socket.receive() - if new_socket: - # create a new bridge over a different port - bridge = _Bridge.create_or_get_existing_bridge( - port=serialized_object["port"], ip_address=self._ip_address, timeout=self._timeout, debug=debug) - # bridge = _Bridge(port=serialized_object["port"], ip_address=self._ip_address, - # timeout=self._timeout, debug=debug) - else: - bridge = self - - java_shadow_constructor = self._deserialize_object(serialized_object) - return java_shadow_constructor(serialized_object=serialized_object, bridge=bridge) - - def _get_java_class(self, classpath: str, new_socket: bool=False, debug: bool=False): - """ - Get an an object corresponding to a java class, for example to be used - when calling static methods on the class directly - - Parameters - ---------- - classpath : str - Full classpath of the java object - new_socket : bool - If True, will create new java object on a new port so that blocking calls will not interfere - with the bridges master port - debug : bool - If True, print debugging messages - Returns - ------- - - Python "Shadow" to the Java class - """ - message = {"command": "get-class", "classpath": classpath} - if new_socket: - message["new-port"] = True - self._main_socket.send(message) - serialized_object = self._main_socket.receive() - - if new_socket: - # create a new bridge over a different port - bridge = _Bridge.create_or_get_existing_bridge(port=serialized_object["port"], ip_address=self._ip_address, - timeout=self._timeout, debug=debug) - else: - bridge = self - return self._class_factory.create( - serialized_object, convert_camel_case=self._convert_camel_case - )(serialized_object=serialized_object, bridge=bridge) - - -class _JavaClassFactory: - """ - This class is responsible for generating subclasses of JavaObjectShadow. Each generated class is kept in a `dict`. - If a given class has already been generate once it will be returns from the cache rather than re-generating it. - """ - - def __init__(self): - self.classes = {} - - def create( - self, serialized_obj: dict, convert_camel_case: bool = True - ) -> typing.Type["_JavaObjectShadow"]: - """Create a class (or return a class from the cache) based on the contents of `serialized_object` message.""" - if serialized_obj["class"] in self.classes.keys(): # Return a cached class - return self.classes[serialized_obj["class"]] - else: # Generate a new class since it wasn't found in the cache. - _java_class: str = serialized_obj["class"] - python_class_name_translation = _java_class.replace( - ".", "_" - ) # Having periods in the name would be problematic. - _interfaces = serialized_obj["interfaces"] - static_attributes = {"_java_class": _java_class, "_interfaces": _interfaces} - - fields = {} # Create a dict of field names with getter and setter funcs. - for field in serialized_obj["fields"]: - fields[field] = property( - fget=lambda instance, Field=field: instance._access_field(Field), - fset=lambda instance, val, Field=field: instance._set_field(Field, val), - ) - - methods = {} # Create a dict of methods for the class by name. - methodSpecs = serialized_obj["api"] - method_names = set([m["name"] for m in methodSpecs]) - # parse method descriptions to make python stand ins - for method_name in method_names: - params, methods_with_name, method_name_modified = _parse_arg_names( - methodSpecs, method_name, convert_camel_case - ) - return_type = methods_with_name[0]["return-type"] - fn = lambda instance, *args, signatures_list=tuple( - methods_with_name - ): instance._translate_call(signatures_list, args, static=_java_class == 'java.lang.Class') - fn.__name__ = method_name_modified - fn.__doc__ = "{}.{}: A dynamically generated Java method.".format( - _java_class, method_name_modified - ) - sig = inspect.signature(fn) - params = [ - inspect.Parameter("self", inspect.Parameter.POSITIONAL_ONLY) - ] + params # Add `self` as the first argument. - return_type = ( - _JAVA_TYPE_NAME_TO_PYTHON_TYPE[return_type] - if return_type in _JAVA_TYPE_NAME_TO_PYTHON_TYPE - else return_type - ) - fn.__signature__ = sig.replace(parameters=params, return_annotation=return_type) - methods[method_name_modified] = fn - - newclass = type( # Dynamically create a class to shadow a java class. - python_class_name_translation, # Name, based on the original java name - (_JavaObjectShadow,), # Inheritance - { - "__init__": lambda instance, serialized_object, bridge: _JavaObjectShadow.__init__( - instance, serialized_object, bridge - ), - **static_attributes, - **fields, - **methods, - }, - ) - - self.classes[_java_class] = newclass - return newclass - -class _JavaObjectShadow: - """ - Generic class for serving as a python interface for a java class using a zmq server backend - - Every instance of this class is assciated with one particular port on which the corresponding - Java server that is keeping track of it exists. But it can be used with multiple Bridge objects, - depending which thread the object is used from - """ - - _interfaces = ( - None # Subclasses should fill these out. This class should never be directly instantiated. - ) - _java_class = None - - def __init__(self, serialized_object, bridge: _Bridge): - self._hash_code = serialized_object["hash-code"] - self._bridges_by_port_thread = {bridge.port: bridge} - # Keep a strong ref to the original bridge that created this object, - # because is must persist for the life of the object and it is - # is required fo the destructor to run properly if its the last - # object to be garbage collected - self._creation_bridge = bridge - # Cache arguments for the bridge that created this object - self._debug = bridge._debug - self._convert_camel_case = bridge._convert_camel_case - self._creation_port = bridge.port - self._creation_thread = threading.current_thread().ident - self._timeout = bridge._timeout - self._ip_address = bridge._ip_address - self._iterate = bridge._iterate - - self._closed = False - # # In case there's an exception rather than normal garbage collection, - # # this makes sure cleanup occurs properly - # # Need to use a wr to ensure that reference to close doesnt cause memeory leak - wr = weakref.ref(self) - def cleanup(): - if wr() is not None: - # It hasn't already been garbage collected - wr()._close() - atexit.register(cleanup) - self._close_lock = Lock() - - def _close(self): - with self._close_lock: - if self._closed: - return - if not hasattr(self, "_hash_code"): - return # constructor didnt properly finish, nothing to clean up on java side - message = { - "command": "destructor", - "hash-code": self._hash_code, - "java_class_name": self._java_class # for debugging - } - self._send(message) - reply_json = None - while reply_json is None: - reply_json = self._get_bridge()._receive(timeout=self._timeout) - if self._creation_port in _Bridge._ports_with_terminated_servers: - break # the server has been terminated, so we can't expect a reply - if reply_json is not None and reply_json["type"] == "exception": - raise Exception(reply_json["value"]) - self._closed = True - # release references to bridges so they can be garbage collected - # if unused by other objects - self._bridges_by_port_thread = None - self._creation_bridge = None - - def _send(self, message): - """ - Send message over the appropriate Bridge - """ - return self._get_bridge()._send(message) - - def _receive(self): - """ - Receive message over appropriate bridge - """ - return self._get_bridge()._receive() - - def _get_bridge(self): - """ - Because ZMQ sockets are not thread safe, every thread must have its own. Bridges are also - also associated with a single port. The JavaObjectShadow instance should hold references to - all bridges it has used, so that dont get garbage collected for the duration of the object's - existence. Otherwise, bridges might keep getting created and then destroyed. - """ - # This should grab an existing bridge if it exists for this thread/port combo - # or create a new one if it doesn't - port = self._creation_port - # In the special case that this is the last object to be garbage collected - # on this bridge, the normal bridging thread/port caching messure will not work - # because the reference count to the bridge will be 0, and all the weakrefs will - # have gone to None. This object holds a reference to the bridge that created it, - # so we can use that to get the bridge - if threading.current_thread().ident == self._creation_thread: - bridge_to_use = self._creation_bridge - else: - bridge_to_use = _Bridge.create_or_get_existing_bridge( - port=port, - convert_camel_case=self._convert_camel_case, - ip_address=self._ip_address, - timeout=self._timeout, - debug=self._debug, - iterate=self._iterate - ) - if bridge_to_use is None: - raise Exception("{} Failed to create bridge on port {}".format(self, port)) - # Get current thread id - thread_id = threading.get_ident() - # Want to hold references to all bridges (specific to each thread port combo) - # so that they don't get garbage collected - combo_id = (thread_id, port) - if combo_id in self._bridges_by_port_thread.keys() and \ - bridge_to_use is not self._bridges_by_port_thread[combo_id]: - # print('bridge just created', bridge_to_use, - # 'cached', self._bridges_by_port_thread[combo_id]) - if self._debug: - logging.main_logger.debug(self) - # print current call stack - traceback.print_stack() - warnings.warn("Duplicate bridges on port {} thread {}".format(port, thread_id)) - self._bridges_by_port_thread[combo_id] = bridge_to_use - - return bridge_to_use - - def __del__(self): - """ - Tell java side this object is garbage collected so it can do the same if needed - """ - if self._debug: - logging.main_logger.debug('destructor for {} on thread {}'.format( - str(self), threading.current_thread().name)) - logging.main_logger.debug('Thread name: {}'.format(threading.current_thread().name)) - try: - self._close() - except Exception as e: - traceback.print_exc() - logging.main_logger.error('Exception in destructor for {} on thread {}'.format( - str(self), threading.current_thread().name)) - - def _access_field(self, name): - """ - Return a python version of the field with a given name - :return: - """ - message = {"command": "get-field", "hash-code": self._hash_code, "name": name} - self._send(message) - return self._deserialize(self._receive()) - - def _set_field(self, name, value): - """ - Return a python version of the field with a given name - :return: - """ - message = { - "command": "set-field", - "hash-code": self._hash_code, - "name": name, - "value": _serialize_arg(value), - } - self._send(message) - reply = self._deserialize(self._receive()) - - def _translate_call(self, method_specs, fn_args: tuple, static: bool): - """ - Translate to appropriate Java method, call it, and return converted python version of its result - Parameters - ---------- - args : - args[0] is list of dictionaries of possible method specifications - kwargs : - hold possible polymorphic args, or none - """ - - # args that are none are placeholders to allow for polymorphism and not considered part of the spec - # fn_args = [a for a in fn_args if a is not None] - valid_method_spec, deserialize_types = _check_method_args(method_specs, fn_args) - # args are good, make call through socket, casting the correct type if needed (e.g. int to float) - message = { - "command": "run-method", - "static": static, - "hash-code": self._hash_code, - "java_class_name": self._java_class, # for debugging - "name": valid_method_spec["name"], - "argument-types": valid_method_spec["arguments"], - "argument-deserialization-types": deserialize_types, - } - message["arguments"] = _package_arguments(valid_method_spec, fn_args) - self._send(message) - recieved = self._receive() - return self._deserialize(recieved) - - def _deserialize(self, json_return): - """ - method_spec : - info about the method that called it - reply : - bytes that represents return - Returns - ------- - An appropriate python type of the converted value - """ - if json_return["type"] == "exception": - raise Exception(json_return["value"]) - elif json_return["type"] == "null": - return None - elif json_return["type"] == "primitive" or json_return["type"] == "string": - return json_return["value"] - elif json_return["type"] == "list": - return [self._deserialize(obj) for obj in json_return["value"]] - elif json_return["type"] == "object": - if json_return["class"] == "JSONObject": - return json.loads(json_return["value"]) - else: - raise Exception("Unrecognized return class") - elif json_return["type"] == "unserialized-object": - - # inherit socket from parent object - java_shadow_constructor = self._get_bridge()._deserialize_object(json_return) - obj = java_shadow_constructor(serialized_object=json_return, bridge=self._get_bridge()) - - # if object is iterable, go through the elements - if self._get_bridge()._iterate and hasattr(obj, 'iterator'): - it = obj.iterator() - elts = [] - has_next = it.hasNext if hasattr(it, 'hasNext') else it.has_next - while(has_next()): - elts.append(it.next()) - return elts - else: - return obj - else: - return deserialize_array(json_return) - - -def deserialize_array(json_return): - """ - Convert a serialized java array to the appropriate numpy type - Parameters - ---------- - json_return - """ - if json_return["type"] in ["byte-array", "int-array", "short-array", "float-array"]: - decoded = json_return["value"] - if json_return["type"] == "byte-array": - return np.frombuffer(decoded, dtype="=u1").copy() - elif json_return["type"] == "double-array": - return np.frombuffer(decoded, dtype="=f8").copy() - elif json_return["type"] == "int-array": - return np.frombuffer(decoded, dtype="=u4").copy() - elif json_return["type"] == "short-array": - return np.frombuffer(decoded, dtype="=u2").copy() - elif json_return["type"] == "float-array": - return np.frombuffer(decoded, dtype="=f4").copy() - - -def _package_arguments(valid_method_spec, fn_args): - """ - Serialize function arguments and also include description of their Java types - - Parameters - ---------- - valid_method_spec: - fn_args : - """ - arguments = [] - for arg_type, arg_val in zip(valid_method_spec["arguments"], fn_args): - if isinstance(arg_val, _JavaObjectShadow): - arguments.append(_serialize_arg(arg_val)) - elif _JAVA_TYPE_NAME_TO_PYTHON_TYPE[arg_type] is object: - arguments.append(_serialize_arg(arg_val)) - elif arg_val is None: - arguments.append(_serialize_arg(arg_val)) - elif isinstance(arg_val, np.ndarray): - arguments.append(_serialize_arg(arg_val)) - else: - arguments.append(_serialize_arg(_JAVA_TYPE_NAME_TO_PYTHON_TYPE[arg_type](arg_val))) - return arguments - - -def _serialize_arg(arg): - if arg is None: - return None - if type(arg) in [bool, str, int, float]: - return arg # json handles serialization - elif type(arg) == np.ndarray: - return arg.tobytes() - elif isinstance(arg, _JavaObjectShadow): - return {"hash-code": arg._hash_code} - else: - raise Exception("Unknown argumetn type") - - -def _check_single_method_spec(method_spec, fn_args): - """ - Check if a single method specification is compatible with the arguments the function received - - Parameters - ---------- - method_spec : - fn_args : - """ - if len(method_spec["arguments"]) != len(fn_args): - return False - for arg_java_type, arg_val in zip(method_spec["arguments"], fn_args): - if isinstance(arg_val, _JavaObjectShadow): - if arg_java_type not in arg_val._interfaces: - # check that it shadows object of the correct type - return False - elif type(arg_val) == np.ndarray: - # For ND Arrays, need to make sure data types match - if ( - arg_java_type != "java.lang.Object" - and arg_val.dtype.type != _JAVA_ARRAY_TYPE_NUMPY_DTYPE[arg_java_type] - ): - return False - elif arg_java_type not in _JAVA_TYPE_NAME_TO_CASTABLE_PYTHON_TYPE or not any( - [ - isinstance(arg_val, acceptable_type) - for acceptable_type in _JAVA_TYPE_NAME_TO_CASTABLE_PYTHON_TYPE[arg_java_type] - ] - ) and not ( - arg_val is None and arg_java_type in _JAVA_NON_PRIMITIVES): # could be null if its an object - # if a type that gets converted - return False - return True - - -def _check_method_args(method_specs, fn_args): - """ - Compare python arguments to java arguments to find correct function to call - - Parameters - ---------- - method_specs : - fn_args : - - Returns - ------- - one of the method_specs that is valid - """ - valid_method_spec = None - for method_spec in method_specs: - if _check_single_method_spec(method_spec, fn_args): - valid_method_spec = method_spec - break - - if valid_method_spec is None: - raise Exception( - "Incorrect arguments. \nExpected {} \nGot {}".format( - " or ".join([", ".join(method_spec["arguments"]) for method_spec in method_specs]), - ", ".join([str(type(a)) for a in fn_args]), - ) - ) - - # subclass NDArrays to the appropriate data type so they dont get incorrectly reconstructed as objects - valid_method_spec = copy.deepcopy(valid_method_spec) - deserialize_types = [] - for java_arg_class, python_arg_val in zip(valid_method_spec["arguments"], fn_args): - if isinstance(python_arg_val, np.ndarray): - deserialize_types.append( - [ - ja - for ja, npdt in zip( - _JAVA_ARRAY_TYPE_NUMPY_DTYPE.keys(), _JAVA_ARRAY_TYPE_NUMPY_DTYPE.values() - ) - if python_arg_val.dtype.type == npdt - ][0] - ) - else: - deserialize_types.append(java_arg_class) - - return valid_method_spec, deserialize_types - - -def _parse_arg_names(methods, method_name, convert_camel_case): - method_name_modified = ( - _camel_case_2_snake_case(method_name) if convert_camel_case else method_name - ) - # all methods with this name and different argument lists - methods_with_name = [m for m in methods if m["name"] == method_name] - min_required_args = ( - 0 - if len(methods_with_name) == 1 and len(methods_with_name[0]["arguments"]) == 0 - else min([len(m["arguments"]) for m in methods_with_name]) - ) - # sort with largest number of args last so lambda at end gets max num args - methods_with_name.sort(key=lambda val: len(val["arguments"])) - method = methods_with_name[-1] # We only need to evaluate the overload with the most arguments. - params = [] - unique_argument_names = [] - for arg_index, typ in enumerate(method["arguments"]): - hint = _CLASS_NAME_MAPPING[typ] if typ in _CLASS_NAME_MAPPING else "object" - python_type = ( - _JAVA_TYPE_NAME_TO_PYTHON_TYPE[typ] if typ in _JAVA_TYPE_NAME_TO_PYTHON_TYPE else typ - ) - if hint in unique_argument_names: # append numbers to end so arg hints have unique names - i = 1 - while hint + str(i) in unique_argument_names: - i += 1 - arg_name = hint + str(i) - else: - arg_name = hint - unique_argument_names.append(arg_name) - # this is how overloading is handled for now, by making default arguments as none, but - # it might be better to explicitly compare argument types - if arg_index >= min_required_args: - default_arg_value = None - else: - default_arg_value = inspect.Parameter.empty - params.append( - inspect.Parameter( - name=arg_name, - kind=inspect.Parameter.POSITIONAL_OR_KEYWORD, - default=default_arg_value, - annotation=python_type, - ) - ) - return params, methods_with_name, method_name_modified - - -def _camel_case_2_snake_case(name): - s1 = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name) - return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower() - -# Used for generating type hints in arguments -_CLASS_NAME_MAPPING = { - "byte[]": "uint8array", - "double[]": "float64_array", - "int[]": "uint32_array", - "short[]": "int16_array", - "char[]": "int16_array", - "float[]": "int16_array", - "long[]": "int16_array", - "java.lang.String": "string", - "boolean": "boolean", - "double": "float", - "float": "float", - "int": "int", - "long": "int", - "short": "int", - "void": "void", -} -#Used for deserializing java arrarys into numpy arrays -_JAVA_ARRAY_TYPE_NUMPY_DTYPE = { - "boolean[]": np.bool_, - "byte[]": np.uint8, - "short[]": np.int16, - "char[]": np.uint16, - "float[]": np.float32, - "double[]": np.float64, - "int[]": np.int32, - "long[]": np.int64, -} -#used for figuring our which java methods to call and if python args match -_JAVA_TYPE_NAME_TO_PYTHON_TYPE = { - "boolean": bool, - "java.lang.Boolean": bool, - "double": float, - "java.lang.Double": float, - "float": float, - "java.lang.Float": float, - "int": int, - "java.lang.Integer": int, - "long": int, - "java.lang.Long": int, - "short": int, - "java.lang.Short": int, - "char": int, - "java.lang.Character": int, - "byte": int, - "java.lang.Byte": int, - "java.lang.String": str, - "void": None, - "java.lang.Object": object, - # maybe could make these more specific to array dtype? - "byte[]": np.ndarray, - "short[]": np.ndarray, - "double[]": np.ndarray, - "int[]": np.ndarray, - "char[]": np.ndarray, - "float[]": np.ndarray, - "long[]": np.ndarray, -} -# type conversions that allow for autocasting -_JAVA_TYPE_NAME_TO_CASTABLE_PYTHON_TYPE = { - "boolean": {bool}, - "java.lang.Boolean": {bool}, - "double": {float, int}, - "java.lang.Double": {float, int}, - "long": {int}, - "java.lang.Long": {int}, - "short": {int}, - "java.lang.Short": {int}, - "char": {int}, - "java.lang.Character": {int}, - "byte": {int}, - "java.lang.Byte": {int}, - "float": {float, int}, - "java.lang.Float": {float, int}, - "int": {int}, - "java.lang.Integer": {int}, - "int[]": {np.ndarray}, - "byte[]": {np.ndarray}, - "double[]": {np.ndarray}, - "java.lang.String": {str}, - "void": {None}, - "java.lang.Object": {object}, -} -_JAVA_NON_PRIMITIVES = {"byte[]", "double[]", "int[]", "short[]", "char[]", "long[]", "boolean[]", - "java.lang.String", "java.lang.Object"} - -if __name__ == "__main__": - # Test basic bridge operations - import traceback - - b = _Bridge.create_or_get_existing_bridge() - try: - s = b.get_studio() - except: - traceback.print_exc() - try: - c = b.get_core() - except: - traceback.print_exc() - a = 1 diff --git a/pycromanager/zmq_bridge/wrappers.py b/pycromanager/zmq_bridge/wrappers.py deleted file mode 100644 index 4f92e668..00000000 --- a/pycromanager/zmq_bridge/wrappers.py +++ /dev/null @@ -1,108 +0,0 @@ -""" -These classes wrap the ZMQ backend for ease of access -""" -from pycromanager.zmq_bridge.bridge import _JavaObjectShadow, _Bridge, _DataSocket -import zmq - -DEFAULT_BRIDGE_PORT = _Bridge.DEFAULT_PORT -DEFAULT_BRIDGE_TIMEOUT = _Bridge.DEFAULT_TIMEOUT - -class PullSocket(_DataSocket): - """ - Create and connect to a pull socket on the given port - """ - def __init__( - self, - port=_Bridge.DEFAULT_PORT, - debug=False, - ip_address="127.0.0.1" - ): - _DataSocket.__init__(self, - context=zmq.Context.instance(), port=port, type=zmq.PULL, debug=debug, ip_address=ip_address) - - -class PushSocket(_DataSocket): - """ - Create and connect to a pull socket on the given port - """ - def __init__( - self, - port=_Bridge.DEFAULT_PORT, - debug=False, - ip_address="127.0.0.1" - ): - _DataSocket.__init__(self, - context=zmq.Context.instance(), port=port, type=zmq.PUSH, debug=debug, ip_address=ip_address) - - - -class JavaObject(_JavaObjectShadow): - """ - Instance of a an object on the Java side. Returns a Python "Shadow" of the object, which behaves - just like the object on the Java side (i.e. same methods, fields). Methods of the object can be inferred at - runtime using iPython autocomplete - """ - - def __new__( - cls, - classpath, - args: list = None, - port=_Bridge.DEFAULT_PORT, - timeout=_Bridge.DEFAULT_TIMEOUT, - new_socket=False, - convert_camel_case=True, - debug=False, - ): - """ - classpath: str - Full classpath of the java object - args: list - list of constructor arguments - port: int - The port of the Bridge used to create the object - new_socket: bool - If True, will create new java object on a new port so that blocking calls will not interfere - with the bridges main port - convert_camel_case : bool - If True, methods for Java objects that are passed across the bridge - will have their names converted from camel case to underscores. i.e. class.methodName() - becomes class.method_name() - debug: - print debug messages - """ - bridge = _Bridge.create_or_get_existing_bridge(port=port, timeout=timeout, convert_camel_case=convert_camel_case, debug=debug) - return bridge._construct_java_object(classpath, new_socket=new_socket, args=args) - - -class JavaClass(_JavaObjectShadow): - """ - Get an an object corresponding to a java class, for example to be used - when calling static methods on the class directly - """ - - def __new__( - cls, - classpath, - port=_Bridge.DEFAULT_PORT, - timeout=_Bridge.DEFAULT_TIMEOUT, - new_socket=False, - convert_camel_case=True, - debug=False, - ): - """ - classpath: str - Full classpath of the java calss - port: int - The port of the Bridge used to create the object - new_socket: bool - If True, will create new java object on a new port so that blocking calls will not interfere - with the bridges main port - convert_camel_case : bool - If True, methods for Java objects that are passed across the bridge - will have their names converted from camel case to underscores. i.e. class.methodName() - becomes class.method_name() - debug: - print debug messages - """ - bridge = _Bridge.create_or_get_existing_bridge(port=port, timeout=timeout, convert_camel_case=convert_camel_case, debug=debug) - return bridge._get_java_class(classpath, new_socket=new_socket) From a501860989128c992342bd3cb2f6bd4eccd31f4b Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Tue, 2 Apr 2024 10:08:14 -0700 Subject: [PATCH 2/3] add dependency --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index b26a7946..3623968f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,3 +5,4 @@ ndtiff>=2.2.0 docstring-inheritance pymmcore sortedcontainers +pyjavaz==1.1.0 From 53d4c40e822fde3d82cd0ec3e5f1169e2163727d Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Tue, 2 Apr 2024 10:09:48 -0700 Subject: [PATCH 3/3] bump version of pycromanager java --- java/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/pom.xml b/java/pom.xml index 546988c6..c7f6b5ca 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -2,7 +2,7 @@ 4.0.0 org.micro-manager.pycro-manager PycroManagerJava - 0.44.9 + 0.45.0 jar Pycro-Manager Java The Java components of Pycro-Manager