PageRenderTime 129ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/matlabcontrol/src/matlabcontrol/MatlabConnector.java

http://matlabcontrol.googlecode.com/
Java | 340 lines | 196 code | 40 blank | 104 comment | 7 complexity | f694748ef99acd6a49def735966f4379 MD5 | raw file
  1. package matlabcontrol;
  2. /*
  3. * Copyright (c) 2011, Joshua Kaplan
  4. * All rights reserved.
  5. *
  6. * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
  7. * following conditions are met:
  8. * - Redistributions of source code must retain the above copyright notice, this list of conditions and the following
  9. * disclaimer.
  10. * - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
  11. * following disclaimer in the documentation and/or other materials provided with the distribution.
  12. * - Neither the name of matlabcontrol nor the names of its contributors may be used to endorse or promote products
  13. * derived from this software without specific prior written permission.
  14. *
  15. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  16. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  17. * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  18. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  19. * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  20. * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  21. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  22. */
  23. import java.rmi.NotBoundException;
  24. import java.rmi.RemoteException;
  25. import java.rmi.registry.Registry;
  26. import java.util.ArrayList;
  27. import java.util.Arrays;
  28. import java.util.List;
  29. import java.util.concurrent.ExecutorService;
  30. import java.util.concurrent.Executors;
  31. import java.util.concurrent.ThreadFactory;
  32. import java.util.concurrent.atomic.AtomicBoolean;
  33. import java.util.concurrent.atomic.AtomicInteger;
  34. import java.util.concurrent.atomic.AtomicReference;
  35. import matlabcontrol.MatlabProxy.MatlabThreadCallable;
  36. import matlabcontrol.MatlabProxy.MatlabThreadProxy;
  37. import matlabcontrol.internal.MatlabRMIClassLoaderSpi;
  38. /**
  39. * This class is used only from inside of the MATLAB JVM. It is responsible for creating instances of
  40. * {@link JMIWrapperRemote} and sending them to a waiting receiver over RMI.
  41. * <br><br>
  42. * While this class is package private, it can be seen by MATLAB, which does not respect the package privateness of the
  43. * class. The public methods in this class can be accessed from inside the MATLAB environment.
  44. *
  45. * @since 3.0.0
  46. *
  47. * @author <a href="mailto:nonother@gmail.com">Joshua Kaplan</a>
  48. */
  49. class MatlabConnector
  50. {
  51. /**
  52. * Used to establish connections on a separate thread.
  53. */
  54. private static final ExecutorService _connectionExecutor =
  55. Executors.newSingleThreadExecutor(new NamedThreadFactory("MLC Connection Establisher"));
  56. /**
  57. * The most recently connected receiver retrieved from a Java program running outside of MATLAB.
  58. */
  59. private static final AtomicReference<RequestReceiver> _receiverRef = new AtomicReference<RequestReceiver>();
  60. /**
  61. * If a connection is currently in progress.
  62. */
  63. private static final AtomicBoolean _connectionInProgress = new AtomicBoolean(false);
  64. /**
  65. * Private constructor so this class cannot be constructed.
  66. */
  67. private MatlabConnector() { }
  68. private static class NamedThreadFactory implements ThreadFactory
  69. {
  70. private static final AtomicInteger COUNTER = new AtomicInteger();
  71. private final String _name;
  72. NamedThreadFactory(String name)
  73. {
  74. _name = name;
  75. }
  76. @Override
  77. public Thread newThread(Runnable r)
  78. {
  79. return new Thread(r, _name + "-" + COUNTER.getAndIncrement());
  80. }
  81. }
  82. /**
  83. * If this session of MATLAB is available to be connected to from an external Java program. It will be available if
  84. * it is not currently connected to and there is no connection in progress.
  85. *
  86. * @return
  87. */
  88. static boolean isAvailableForConnection()
  89. {
  90. boolean available;
  91. if(_connectionInProgress.get())
  92. {
  93. available = false;
  94. }
  95. else
  96. {
  97. RequestReceiver receiver = _receiverRef.get();
  98. boolean connected = false;
  99. if(receiver != null)
  100. {
  101. try
  102. {
  103. receiver.getReceiverID();
  104. connected = true;
  105. }
  106. catch(RemoteException e) { }
  107. }
  108. available = !connected;
  109. }
  110. return available;
  111. }
  112. /**
  113. * Called from MATLAB at launch. Creates the JMI wrapper and then sends it over RMI to the Java program running in a
  114. * separate JVM.
  115. *
  116. * @param receiverID the key that binds the receiver in the registry
  117. * @param port the port the registry is running on
  118. * @param initializationTime
  119. */
  120. public static void connectFromMatlab(String receiverID, int port)
  121. {
  122. connect(receiverID, port, false);
  123. }
  124. /**
  125. * Retrieves the receiver and sends over the {@link JMIWrapperRemote} on a separate thread so that MATLAB can
  126. * continue to initialize.
  127. *
  128. * @param receiverID
  129. * @param port
  130. * @param existingSession
  131. */
  132. static void connect(String receiverID, int port, boolean existingSession)
  133. {
  134. _connectionInProgress.set(true);
  135. //Establish the connection on a separate thread to allow MATLAB to continue to initialize
  136. //(If this request is coming over RMI then MATLAB has already initialized, but this will not cause an issue.)
  137. _connectionExecutor.submit(new EstablishConnectionRunnable(receiverID, port, existingSession));
  138. }
  139. /**
  140. * A runnable which sets up matlabcontrol inside MATLAB and sends over the remote JMI wrapper.
  141. */
  142. private static class EstablishConnectionRunnable implements Runnable
  143. {
  144. private final String _receiverID;
  145. private final int _port;
  146. private final boolean _existingSession;
  147. /**
  148. * The classpath (with each classpath entry as an individual canonical path) of the most recently connected
  149. * receiver's JVM.
  150. * <br><br>
  151. * This variable can safely be volatile because the needed behavior is volatile read/write of the array, not
  152. * its entries. It is also unlikely the volatile behavior is actually necessary, but it could be if the thread
  153. * used by {@link MatlabConnector#_connectionExecutor} died and created a new one - this ensures visibility.
  154. */
  155. private static volatile String[] _previousRemoteClassPath = new String[0];
  156. private EstablishConnectionRunnable(String receiverID, int port, boolean existingSession)
  157. {
  158. _receiverID = receiverID;
  159. _port = port;
  160. _existingSession = existingSession;
  161. }
  162. @Override
  163. public void run()
  164. {
  165. //Validate matlabcontrol can be used
  166. try
  167. {
  168. JMIValidator.validateJMIMethods();
  169. }
  170. catch(MatlabConnectionException ex)
  171. {
  172. System.err.println("matlabcontrol is not compatible with this version of MATLAB");
  173. ex.printStackTrace();
  174. return;
  175. }
  176. //If MATLAB was just launched
  177. if(!_existingSession)
  178. {
  179. //Set the RMI class loader service provider
  180. System.setProperty("java.rmi.server.RMIClassLoaderSpi", MatlabRMIClassLoaderSpi.class.getName());
  181. //Make this session of MATLAB of visible over RMI so that reconnections can occur, proceed if it fails
  182. try
  183. {
  184. MatlabBroadcaster.broadcast(_port);
  185. }
  186. catch(MatlabConnectionException ex)
  187. {
  188. System.err.println("Reconnecting to this session of MATLAB will not be possible");
  189. ex.printStackTrace();
  190. }
  191. }
  192. //Send the remote JMI wrapper
  193. try
  194. {
  195. //Get registry
  196. Registry registry = LocalHostRMIHelper.getRegistry(_port);
  197. //Get the receiver from the registry, if it cannot be retrieved, retry once after waiting.
  198. //The retry is attempted because the factory checks periodically to see if the receiver is bound and
  199. //will attempt a rebind if it is not.
  200. RequestReceiver receiver;
  201. try
  202. {
  203. receiver = (RequestReceiver) registry.lookup(_receiverID);
  204. }
  205. catch(NotBoundException nbe1)
  206. {
  207. try
  208. {
  209. Thread.sleep(RemoteMatlabProxyFactory.RECEIVER_CHECK_PERIOD);
  210. try
  211. {
  212. receiver = (RequestReceiver) registry.lookup(_receiverID);
  213. }
  214. catch(NotBoundException nbe2)
  215. {
  216. System.err.println("First attempt to connect to Java application failed");
  217. nbe1.printStackTrace();
  218. System.err.println("Second attempt to connect to Java application failed");
  219. nbe2.printStackTrace();
  220. return;
  221. }
  222. }
  223. catch(InterruptedException ie)
  224. {
  225. System.err.println("First attempt to connect to Java application failed");
  226. nbe1.printStackTrace();
  227. System.err.println("Interrupted while waiting to retry connection to Java application");
  228. ie.printStackTrace();
  229. return;
  230. }
  231. }
  232. //Hold on the to receiver
  233. _receiverRef.set(receiver);
  234. //Load a security manager so that remote class loading can occur
  235. if(System.getSecurityManager() == null)
  236. {
  237. System.setSecurityManager(new PermissiveSecurityManager());
  238. }
  239. //Tell the RMI class loader of the codebase where the receiver is from, this will allow MATLAB to load
  240. //classes defined in the remote JVM, but not in this one
  241. MatlabRMIClassLoaderSpi.setCodebase(receiver.getClassPathAsRMICodebase());
  242. //Tell MATLAB's class loader about the codebase where the receiver is from, if not then MATLAB's
  243. //environment will freak out when interacting with classes it cannot find the definition of and throw
  244. //exceptions with rather confusing messages
  245. String[] newClassPath = receiver.getClassPathAsCanonicalPaths();
  246. try
  247. {
  248. JMIWrapper.invokeAndWait(new ModifyCodebaseCallable(_previousRemoteClassPath, newClassPath));
  249. _previousRemoteClassPath = newClassPath;
  250. }
  251. catch(MatlabInvocationException e)
  252. {
  253. System.err.println("Unable to update MATLAB's class loader; issues may arise interacting with " +
  254. "classes not defined in MATLAB's Java Virtual Machine");
  255. e.printStackTrace();
  256. }
  257. //Create the remote JMI wrapper and then pass it over RMI to the Java application in its own JVM
  258. receiver.receiveJMIWrapper(new JMIWrapperRemoteImpl(), _existingSession);
  259. }
  260. catch(RemoteException ex)
  261. {
  262. System.err.println("Connection to Java application could not be established");
  263. ex.printStackTrace();
  264. }
  265. _connectionInProgress.set(false);
  266. }
  267. }
  268. /**
  269. * Modifies MATLAB's dynamic class path. Retrieves the current dynamic class path, removes all of the entries
  270. * from the previously connected JVM, adds the new ones, and if the classpath is now different, sets a new dynamic
  271. * classpath.
  272. */
  273. private static class ModifyCodebaseCallable implements MatlabThreadCallable<Void>
  274. {
  275. private final String[] _toRemove;
  276. private final String[] _toAdd;
  277. public ModifyCodebaseCallable(String[] oldRemoteClassPath, String[] newRemoteClassPath)
  278. {
  279. _toRemove = oldRemoteClassPath;
  280. _toAdd = newRemoteClassPath;
  281. }
  282. @Override
  283. public Void call(MatlabThreadProxy proxy) throws MatlabInvocationException
  284. {
  285. //Current dynamic class path
  286. String[] curr = (String[]) proxy.returningFeval("javaclasspath", 1, "-dynamic")[0];
  287. //Build new dynamic class path
  288. List<String> newDynamic = new ArrayList<String>();
  289. newDynamic.addAll(Arrays.asList(curr));
  290. newDynamic.removeAll(Arrays.asList(_toRemove));
  291. newDynamic.addAll(Arrays.asList(_toAdd));
  292. //If the class path is different, set it
  293. if(!newDynamic.equals(Arrays.asList(curr)))
  294. {
  295. proxy.feval("javaclasspath", new Object[] { newDynamic.toArray(new String[newDynamic.size()]) });
  296. }
  297. return null;
  298. }
  299. }
  300. }