diff --git a/java/pom.xml b/java/pom.xml
index 84bdf7fb..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
@@ -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)
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