Once again I welcome Donn Shull, with another article about Matlab’s internal UDD mechanism
Extending a Java class with UDD
During the series on UDD, we have mentioned the connection between UDD and Java. In UDD Events and Listeners we described how in Matlab, each Java object can have a UDD companion. In Hierarchical Systems with UDD we briefly noted that a UDD hierarchy may be passed to Java. In the numerous posts on handle graphics and callbacks, Yair has discussed the UDD packages javahandle
and javahandle_withcallbacks
. Based on this information, it seems reasonable to speculate that it may be possible to extend a Java class with UDD using UDD’s class inheritance mechanism.
This can be extremely useful in two cases:
- You don’t know Java but found a Java class you would like to use in Matlab, it just needs minor modifications for your specific needs
- You do know Java, but don’t have access to the original source code, and choose to extend the Java class with Matlab code, rather than Java code
Today I will show how this can be done using a simple example. Our example will illustrate the following things:
- Subclassing a Java class with UDD
- Adding UDD properties to the to the subclass
- Overloading a Java method with Matlab code
- Directly accessing the superclass methods
The example will show extending Java socket classes to provide a simple method for communication between two Matlab sessions. The protocol has been kept purposely simple and is not robust. Additional work would need to be done to create a real-life socket-based communication between Matlab systems (see for example this FEX submission).
Today’s example consists of two subclasses: a subclass of java.net.ServerSocket
and a subclass of java.net.Socket
. The protocol will be sending strings back and forth between the two sessions. In each direction the information exchange will consist of two bytes containing the string length, followed by the actual string. The entire source code can be downloaded from here.
Creating the simple.ServerSocket class
As in the UDD series, we will use the simple package for our classes and in this package create a ServerSocket
class and a Socket
class. Recall the simple package definition class is placed in a file named schema.m in a directory called @simple, placed somewhere on the Matlab path. schema.m consists of:
function schema() %SCHEMA simple package definition function. schema.package('simple'); end |
In our ServerSocket
class we will add three UDD properties and overload two of the Java class methods. It is worth noting that our final class will have all the parent Java classes public properties and methods and if necessary we can access the parent or super class methods directly. As before, we create a subfolder of the @simple folder named @ServerSocket; in this folder we place four files:
- schema.m – the class definition file
- ServerSocket.m – the class constructor
- accept.m – one of the Java methods that we will overload
- bind.m – the other Java method that we will overload
At the beginning of our schema.m file, we will use the following code to subclass the Java class:
function schema %SCHEMA simple.ServerSocket class definition function. % parent schema.class definition javaPackage = findpackage('javahandle'); javaClass = findclass(javaPackage, 'java.net.ServerSocket'); % class package (schema.package) simplePackage = findpackage('simple'); % class definition simpleClass = schema.class(simplePackage, 'ServerSocket', javaClass); |
Here, we use findpackage and findclass to obtain the schema.class for the Java class that we are going to use as our parent. We then obtain a handle to the containing package, and finally use the subclass variation to define our ServerSocket
as a variation of the Java parent’s schema.class.
Next, in the class definition file we place the code to define the signatures for the methods we are overloading:
% accept.m overloads java accept method and adds communication protocol m = schema.method(simpleClass, 'accept'); s = m.Signature; s.varargin = 'off'; s.InputTypes = {'handle'}; s.OutputTypes = {'string'}; % bind.m overloads java bind method m = schema.method(simpleClass, 'bind'); s = m.Signature; s.varargin = 'off'; s.InputTypes = {'handle'}; s.OutputTypes = {};be |
Finally, we add three UDD properties to the class: The first will be used to hold a string representation of the address of our ServerSocket
; the second will store the communication port number; the third is a handle property that will hold the reference to the socket used by the actual communication.
% holds remote address as a matlab string p = schema.prop(simpleClass, 'address', 'string'); p.FactoryValue = 'localhost'; % holds remote port as a matlab int p = schema.prop(simpleClass, 'port', 'int16'); p.FactoryValue = 2222; % holds a handle reference to the socket created in the accept method p = schema.prop(simpleClass, 'socket', 'handle'); end |
We now need to write our overloaded methods. The bind method is simple: it first creates a Java internet address using the new address and port properties; then it uses the standard Java class methods to call the superclass’s bind method with the specified internet address:
function bind(this) % use the object socket and port port properties to bind this instance % to a address calling the superclass bind method inetAddress = java.net.InetSocketAddress(this.address, this.port); this.java.bind(inetAddress); end |
The overloaded accept method is a bit more complicated and crude: It starts by calling the superclass accept method to create a communication socket and stores the created socket in our class’s socket property. Then it goes into an infinite loop of waiting for incoming commands, uses evalc to execute them, and returns the captured result to the caller. The only way out of this loop is using Ctrl-C from the keyboard.
function accept(this) % use the superclass accept this.socket = handle(this.java.accept); % infinite loop use ctrl-c to exit while 1 % wait for a command then execute it capturing output while this.socket.getInputStream.available < 2 end msb = this.socket.getInputStream.read; lsb = this.socket.getInputStream.read; numChar = 256 * msb + lsb; cmd = uint8(zeros(1, numChar)); for index = 1:numChar cmd(index) = this.socket.getInputStream.read; end result = evalc(char(cmd)); % send the result back to the calling system len = numel(result); msb = uint8(floor(len/256)); lsb = uint8(mod(len,256)); this.socket.getOutputStream.write(uint8([msb, lsb, result])); end end |
Creating the simple.Socket class
The simple.Socket
class is created like ServerSocket
, this time in the @Socket folder under the @simple folder. In this subclass we add properties for the address and port, just as in ServerSocket
. We overload the superclass’s connect method with our own variant, and add a new method to make the remote calls to the ServerSocket
running in another Matlab instance. Beginning with the schema.m file we have:
function schema %SCHEMA simple.Socket class definition function. % package definition simplePackage = findpackage('simple'); javaPackage = findpackage('javahandle'); javaClass = findclass(javaPackage, 'java.net.Socket'); % class definition simpleClass = schema.class(simplePackage, 'Socket', javaClass); % define class methods % connect.m overloads java connect method m = schema.method(simpleClass, 'connect'); s = m.Signature; s.varargin = 'off'; s.InputTypes = {'handle'}; s.OutputTypes = {}; % remoteEval.m matlab method for remote evaluation of Matlab commands m = schema.method(simpleClass, 'remoteEval'); s = m.Signature; s.varargin = 'off'; s.InputTypes = {'handle', 'string'}; s.OutputTypes = {'string'}; % add properties to this class % holds remote address as a Matlab string p = schema.prop(simpleClass, 'address', 'string'); p.FactoryValue = 'localhost'; % holds remote port as a Matlab int p = schema.prop(simpleClass, 'port', 'int16'); p.FactoryValue = 2222; end |
The class constructor Socket.m is simply:
function skt = Socket %SOCKET constructor for the simple.Socket class skt = simple.Socket; end |
The overloaded connect method is almost identical to the overloaded bind method we used for ServerSocket
. We form a Java internet address from our new properties and then invoke the superclass’s connect Java method:
function connect(this) %CONNECT overload of the java.net.Socket connect method % use the object address and port properties to connect to the remote % session via the superclass connect method inetAddress = java.net.InetSocketAddress(this.address, this.port); this.java.connect(inetAddress); end |
Finally, our remoteEval method is very similar to the loop portion of the overloaded accept method we wrote for simple.ServerSocket
. We take the command string input and convert it into a series of bytes prepended by the length of the string, send it to the other Matlab session and wait for a response:
function result = remoteEval(this, cmd) %REMOTEEVAL evaluate a Matlab command on a remotely connected Matlab % The command string is sent as a series of bytes preceded by a pair of % bytes which represents the length of the string cmd = uint8(cmd); len = numel(cmd); msb = uint8(floor(len/256)); lsb = uint8(mod(len,256)); this.getOutputStream.write([msb, lsb, cmd]); % We will expect the remote session to return a string in the same format % as the command while this.getInputStream.available < 2 end msb = this.getInputStream.read; lsb = this.getInputStream.read; numChar = 256 * msb + lsb; result = uint8(zeros(1, numChar)); for index = 1:numChar result(index) = this.getInputStream.read; end result = char(result); end |
Using simple.ServerSocket and simple.Socket to communicate between Matlab sessions
To use this example, add the zip contents to your Matlab path, then open an instance of Matlab and issue the following commands:
>> ss = simple.ServerSocket; >> ss.bind; >> ss.accept; |
Then open another Matlab instance and issue these commands:
>> s = simple.Socket; >> s.connect; |
At this point you can send commands from this Matlab instance (the client) to the first instance (the server) using the remoteEval method. The command will then be transmitted to the server, executed, and the server will return the captured string result to the client:
>> remoteResult = s.remoteEval('pi') remoteResult = 3.1416 |
The defaults are for localhost and port 2222. These can be changed prior to using the server’s bind method and the client’s connect method. To keep things as simple as possible, error checking etc. has been left out, so this is just a demonstration and is far from robust.
There are some things to note about our new classes. If we type methods(s) or s.methods at the Matlab command prompt in our simple.Socket
session we obtain:
>> s.methods Methods for class simple.Socket: Socket getOOBInline isClosed setReuseAddress bind getOutputStream isConnected setSendBufferSize close getPort isInputShutdown setSoLinger connect getReceiveBufferSize isOutputShutdown setSoTimeout equals getRemoteSocketAddress java setSocketImplFactory getChannel getReuseAddress notify setTcpNoDelay getClass getSendBufferSize notifyAll setTrafficClass getInetAddress getSoLinger remoteEval shutdownInput getInputStream getSoTimeout sendUrgentData shutdownOutput getKeepAlive getTcpNoDelay setKeepAlive toString getLocalAddress getTrafficClass setOOBInline wait getLocalPort hashCode setPerformancePreferences getLocalSocketAddress isBound setReceiveBufferSize |
This shows that our simple.Socket
class has all of the methods of the Java superclass, plus our added remoteEval
method and the java
method that was automatically added by Matlab. This means that all of the Java methods are methods of our class instance and the added java
means that we can access the superclass methods from our class instance if the need arises. If we use the struct function which Yair has previously discussed, we obtain:
>> struct(s) ans = OOBInline: 0 Bound: 1 Channel: [] Class: [1x1 java.lang.Class] Closed: 0 Connected: 1 InetAddress: [1x1 java.net.Inet4Address] InputShutdown: 0 InputStream: [1x1 java.net.SocketInputStream] KeepAlive: 0 LocalAddress: [1x1 java.net.Inet4Address] LocalPort: 51269 LocalSocketAddress: [1x1 java.net.InetSocketAddress] OutputShutdown: 0 OutputStream: [1x1 java.net.SocketOutputStream] ReceiveBufferSize: 8192 RemoteSocketAddress: [1x1 java.net.InetSocketAddress] ReuseAddress: 0 SendBufferSize: 8192 SoLinger: -1 SoTimeout: 0 TcpNoDelay: 0 TrafficClass: 0 address: 'localhost' port: 2222 |
We see that we have access to all of the public properties of the Java superclass, as well as the UDD properties that we have added.
Conclusion
At the beginning of this post I said that this would be a simple non-robust communications method. In order to make this anything more than that, a number of things would need to be implemented, for example:
- Improve the accept method to exit after a timeout or when a connection has been made and then terminated
- Add checksums and timeouts for communication to determine the reliability of the communication
- Add a retry request protocol for instances of communication failure
- Add support for any serializable Matlab type, not just strings
The intent here was just to show that extending Java classes with Matlab is possible, relatively simple, and can be extremely useful. After all, with over 10 million Java developers out there, chances are that somebody somewhere has already posted a Java class that answers your exact need, or at least close enough that it can be used in Matlab with only some small modifications.
Excellent series on UDD. Thank you so much for taking the time to write this! A quick question. How would one extend a user defined java class. For example, say that I have a java class “AddressBook” in the package “mypackage”. Calling the following doesn’t work.
How would I obtain the super schema.class to use when creating my subclass
Hi Michael,
When you use mypackage.AddressBook can you create an instance without any arguments? If the constructor requires arguments then you will not be able to use find class to get your superclass. A work around would be to create an instance of the class in schema.m and get the schema.class from the instance using classhandle.
Donn
Hi Donn,
Many thanks for the very interesting and insightful articles. I have just tried to create a class
myTableModel
with superclassjavax.swing.table.DefaultTableModel
, to override the isCellEditable method to always return false, and it works perfectly.However when I try to set the model of a
javax.swing.JTable
to an instance of my classmyTableModel
I get the following error message:Whereas I can easily set:
Do you know why I may be getting this error? I really appreciate your help.
Many thanks
Augustin
@Augustin – Unfortunately when you extend a java class using UDD it becomes a UDD class and is no longer a java class that you can use the same way as before. In your application it would be easier to simply extend
javax.swing.table.DefaultTableModel
with java.@Donn,
Thanks for your help, that’s what I had done before reading your article. It would have been nice to keep all the code in matlab but if it’s not possible..
Thanks anyway, Happy new year.
I have enjoyed this series and have found it to be very useful as I am creating a MATLAB wrapping of my java classes. The one question that I have concerns constants.
My java class defines several static constants that I want to expose to the matlab class. I want them to be referenced without having to have instantiated the MATLAB class. If this is possible, what is the syntax.
Thanks,
Ed
@Ed – assuming your static constants are
public
(which they must be, otherwise you cannot use them in Matlab), you can simply use the classname:myField = MyJavaClassName.static_field_name;
Yair,
If I need to use the java class directly, I need help on one other piece. My MATLAB class is derived from one of two java classes. This decision is made in the schema.m file and is based on an environment variable. Using UDD, can I get the java class that is used without having to instantiate an object?
PS. Sorry if this is posted out of order, but there was no reply button under your helpful suggestion.
Correction. Using MATLAB, can I get the JAVA base class of my UDD based extension?
@Ed – I’m not exactly sure what you mean, but if you’re saying that you only have the class name and wish to avoid instantiating a class object, then you can use Matlab’s eval function to evaluate it in run-time. If you need additional assistance then contact me by email (altmany at gmail) for a short consultancy.
Yair,
I have a couple of questions:
1. Is this sample compatible with Matlab 2014b?
2. How this sample would look with
+package
andclassdef
definitions?Currently I get the following error when I try your sample (everything relevant is added on the matlab path)
ss = simple.SeverSocket
Undefined variable “simple” or function “simple.SeverSocket”.
Hi Yuri,
This example works with most MATLAB releases through R2016a. If you download the zip file and use those files it should work. If you are copying the code from the text of the article there is a syntax error in the schema.m file for simple.ServerSocket. Line 1 should be:
function schema
rather than:
Donn
I fixed the post text
It appears that only with static path I was able to run the example.
However the virtual function override does not catch: is it parameters problem?
onRequest.m:
schema.m:
However, I see only the method of the base class running (the override does not catch):