Contents
Interaction with a service is specified by its interface. This will be
the same across all implementations of the interface. This doesn't
allow any flexibility, and of course it shouldn't. It is the bottom level
for using this type of service. But service implementations do differ,
and there is a need to allow for this. The mechanism used in Jini is
to put these differences in Entry
objects. Typical objects
supplied by vendors may include Name
and ServiceInfo
.
Clients can make use of the interface and these additional entry items, primarily in the selection of a service. But once they have the service, are they just constrained to use it via the type interface? Many services will probably benefit from some sort of user interface. For example, a printer may supply a method to print a file, but it may have the capability to print multiple copies of the same file. Rather than relying on the client to be smart enough to figure this out, the printer vendor may want to call attention to this by supplying a user-interface object with a special component for ``number of copies''.
The user interface for a service cannot expect to have all details supplied
by the client - at best a client could only manage a fairly generic user interface.
It should come from the vendor, or maybe even third parties. When your video
player becomes Jini enabled, it would be a godsend for
someone to supply a decent user interface for it,
since the video player vendors seem generally incapable of doing so!
The Entry
objects need not just provide static data - as Java
objects they are perfectly capable of running as user-interface objects.
User interfaces are not part of the Jini standard. But the Jini community
(with a semi-formal organisation as the ``Jini Community'') is coming
towards a standard way of specifying many things, including user-interface
standards and guidelines. Guideline number one from the ``serviceUI'' group
is: user interfaces for a service should be given in Entry
objects.
In the chapter on ``More Complex Examples'' some discussion was given to the location of code, using user-interface components as examples. The chapter suggested that user interfaces should not be created on the server side but on the client side. So the user-interface should be exported as a factory object that can create the user-interface on the client side.
More arguments can be given to support this:
The service should export a user-interface factory,
with a method to create the interface, such as getUI()
.
The service and its user-interface
factory entry will both be retrieved by the client. The client will then
create the user-interface. Note that the factory will not know the service
object - if it was given one during its construction
(on the service side) it would end with another copy of the service.
So when it is asked for a
user-interface (on the client side), it should be passed the service as well.
The actual creation
may fail in lots of ways: no suitable libraries, out of memory, screen
too small, etc.
So this method may need to throw an exception.
The factory will probably know many attributes of the user-interface:
preferred and minimum screen sizes, required class libraries
etc. These can all be stored as a set of features
of the factory. Two have been singled out as being of general importance:
the name
and the role
(such as main
and admin
).
The current definition of a user-interface factory is
public class UIFactory extends AbstractEntry {
public String name;
public String role;
public Map properties;
public UIFactory(String name, String role);
public UIFactory(String name, String role, Map properties);
public Object getUI(Object service) throws UICreationException;
}
A service may have lots of these factories, each capable of creating a
different user interface object.
There are many kinds of user-interface objects that can be returned. They could be individual components such as buttons or lists, containers such as panels, or components with their own toplevel windows such as frames and dialogs. Dialogs may be modal or non-modal. These will all need to be handled by a client in different ways: a component needs to be placed within a container, a frame is shown and processing independently continues, a modal dialog blocks execution of the calling process until it is dismissed. The client will need to know these broad categories.
A user interface may just use AWT components. It may use Swing components.
But it may also use the 2-D or 3-D classes, or even things like the MPEG
class from the Media API. Perhaps it doesn't have a ``traditional'' screen
interface, but uses a speech interface instead. There may even be possibilities
of some other description apart from Java objects: JACL scripts,
perhaps (JACL is a tcl
interpreter written in Java).
The client needs to have a way of figuring out which types of objects a factory will produce, without actually going all the way through the actual creation. There are two parts to this:
getUI()
?
The first problem (what type of object is returned from getUI()
)
can be answered by using suitable subclasses of UIFactory
:
AWTFrameFactory
returns an AWT Frame
SwingFrameFactory
returns a Swing JFrame
AWTPanelFactory
returns an AWT Panel
SwingPanelFactory
returns a Swing JPanel
The second problem (what classes are needed) is even less settled.
A set of ``required libraries'' is currently favoured. This would be one
of the properties stored in the map of additional properties in each
factory. Each element of this could contain the full package name of the library
(such as "javax.swing"
) plus other information such as
version number. There are still some tricky elements in this: for example,
must the client supply these libraries, or could they be downloaded from an
HTTP server? Some libraries such as the Java Media Framework contain objects
written in pure Java, while others in this same package
require native code underneath (such as the
MPEG player). Must a native library be available even if the widget requiring
the native library isn't used?
Processes to define standards acceptable to the Jini Community are being set up. Standards within this are also being defined. Right now things are unsettled, but this will change.
The ``serviceUI'' will eventually decide on a standard set of support classes. In the meantime you will have to ``wing it'' and be aware that you are writing/using unstable code. For the purposes of the rest of this chapter this section will define classes that will approximate the final classes of the ``serviceUI'' specification.
A possible implementation of the UIFactory
class is
/**
* UIFactory.java
*
*
* Created: Sun Nov 14 18:07:12 1999
*
* @author Jan Newmarch
* @version 1.0
*/
package ui;
import net.jini.entry.AbstractEntry;
import java.util.Map;
import java.util.HashMap;
public class UIFactory extends AbstractEntry {
public String name;
public String role;
public Map properties;
public UIFactory() {
this(null, null, null);
}
public UIFactory(String name, String role) {
this(name, role, null);
}
public UIFactory(String name, String role, Map properties) {
this.name = name;
this.role = role;
if (properties == null) {
this.properties = null;
} else {
this.properties = new HashMap(properties);
}
}
public Object getUI(Object service) throws UICreationException {
throw new UICreationException();
}
} // UIFactory
(The package name is convenient for this book: it will not be the final package
name.)
A factory that can be used to return AWT Frame
's is the
AWTFrameFactory
. It does not enforce this return type, and
users of this class are expected to examine the class type in order
to determine what to do with it.
/**
* AWTFrameFactory.java
*
*
* Created: Sun Nov 14 18:18:32 1999
*
* @author Jan Newmarch
* @version 1.0
*/
package ui;
import java.util.Map;
public class AWTFrameFactory extends UIFactory {
public AWTFrameFactory() {
super();
}
public AWTFrameFactory(String name, String role) {
super(name, role);
}
public AWTFrameFactory(String name, String role, Map properties) {
super(name, role, properties);
}
} // AWTFrameFactory
User interfaces often contain images. They may be used as icons in toolbars, for general images on the screen, or for the icon image when the application is iconified. When a user interface is created on the client, these images will also need to be created and installed in the relevant part of the application. Images are not serializable, so they cannot be created on the server and exported as live objects in some manner. They need to be created from scratch on the client.
The Swing package contains a convenience class ImageIcon
. This can
be instantiated from a byte array, a filename, or most interestingly here, from
a URL. So if an image is stored where an HTTP server can find it, then the
ImageIcon
constructor can use this directly. There may be failures in
this: the URL may be incorrect or malformed, or the image may fail to exist on the
HTTP server. Suitable code is
ImageIcon icon = null;
try {
icon = new ImageIcon(new URL("http://localhost/images/mindstorms.jpg"));
switch (icon.getImageLoadStatus()) {
case MediaTracker.ABORTED:
case MediaTracker.ERRORED:
System.out.println("Error");
icon = null;
break;
case MediaTracker.COMPLETE:
System.out.println("Complete");
break;
case MediaTracker.LOADING:
System.out.println("Loading");
break;
}
} catch(java.net.MalformedURLException e) {
e.printStackTrace();
}
// icon is null or is a valid image
A MindStorms robot is primarily defined by the RCXPort
interface.
The Jini version is defined by the RCXPortImplementation
interface:
/**
* RCXPortInterface.java
*
*
* Created: Wed Jun 2 23:08:12 1999
*
* @author Jan Newmarch
* @version 1.0
*/
package rcx.jini;
import net.jini.core.event.RemoteEventListener;
public interface RCXPortInterface extends java.io.Serializable {
/**
* constants to distinguish message types
*/
public final long ERROR_EVENT = 1;
public final long MESSAGE_EVENT = 2;
/**
* Write an array of bytes that are RCX commands
* to the remote RCX.
*/
public boolean write(byte[] byteCommand) throws java.rmi.RemoteException;
/**
* Parse a string into a set of RCX command bytes
*/
public byte[] parseString(String command) throws java.rmi.RemoteException;
/**
* Add a RemoteEvent listener to the RCX for messages and errors
*/
public void addListener(RemoteEventListener listener)
throws java.rmi.RemoteException;
/**
* The last message from the RCX
*/
public byte[] getMessage(long seqNo)
throws java.rmi.RemoteException;
/**
* The error message from the RCX
*/
public String getError(long seqNo)
throws java.rmi.RemoteException;
} // RCXPortInterface
This allows programs to be downloaded and run, and instructions to be sent
for direct execution. As it stands, the client needs to call these interface
methods directly. To make it more useable for the human trying to drive a
robot, some sort of user interface would be useful.
There can be several general purpose user interfaces for the RCX robot, including:
RCXLoader
which does the second of these.
We can steal code from this and some of his other classes,
to define a class RCXLoaderFrame
package rcx.jini;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;
import net.jini.core.event.RemoteEventListener;
import net.jini.core.event.RemoteEvent;
import net.jini.core.event.UnknownEventException;
import java.rmi.server.UnicastRemoteObject;
import java.rmi.RemoteException;
import rcx.*;
/*
* RCXLoaderFrame
* @author Dario Laverde
* @author Jan Newmarch
* @version 1.1
* Copyright 1999 Dario Laverde, under terms of GNU LGPL
*/
public class RCXLoaderFrame extends Frame
implements ActionListener, WindowListener, RemoteEventListener
{
private String portName;
private RCXPortInterface rcxPort;
private Panel textPanel;
private Panel topPanel;
private TextArea textArea;
private TextField textField;
private Button tableButton;
private Properties parameters;
private int inByte;
private int charPerLine = 48;
private int lenCount;
private StringBuffer sbuffer;
private byte[] byteArray;
private Frame opcodeFrame;
private TextArea opcodeTextArea;
public static Hashtable Opcodes=new Hashtable(55);
static {
Opcodes.put(new Byte((byte)0x10),"PING ,void, void,P");
Opcodes.put(new Byte((byte)0x12),"GETVAL ,byte src byte arg, short val,P");
Opcodes.put(new Byte((byte)0x13),"SETMOTORPOWER ,byte motors byte src byte arg, void,CP");
Opcodes.put(new Byte((byte)0x14),"SETVAL ,byte index byte src byte arg, void,CP");
Opcodes.put(new Byte((byte)0x15),"GETVERSIONS ,byte key[5], short rom[2] short firmware[2],P");
Opcodes.put(new Byte((byte)0x17),"CALLSUB ,byte subroutine, void,C");
Opcodes.put(new Byte((byte)0x20),"GETMEMMAP ,void, short map[94],P");
Opcodes.put(new Byte((byte)0x21),"SETMOTOR ,byte code, void,CP");
Opcodes.put(new Byte((byte)0x22),"SETTIME ,byte hours byte minutes, void,CP");
Opcodes.put(new Byte((byte)0x23),"PLAYTONE ,short frequency byte duration, void,CP");
Opcodes.put(new Byte((byte)0x24),"ADDVAR ,byte index byte src short arg, void,CP");
Opcodes.put(new Byte((byte)0x25),"DOWNLOADTASK ,byte unknown short task short length, byte error,P");
Opcodes.put(new Byte((byte)0x27),"BRANCHALWAYSNEAR,byte offset, void,C");
Opcodes.put(new Byte((byte)0x30),"GETBATTERYPOWER ,void, short millivolts,P");
Opcodes.put(new Byte((byte)0x31),"SETTRANSMITRANGE,byte range, void,CP");
Opcodes.put(new Byte((byte)0x32),"SETSENSORTYPE ,byte sensor byte type, void,CP");
Opcodes.put(new Byte((byte)0x33),"SETDISPLAY ,byte src short arg, void,CP");
Opcodes.put(new Byte((byte)0x34),"SUBFROMVAR ,byte index byte src short arg, void,CP");
Opcodes.put(new Byte((byte)0x35),"DOWNLOADSUB ,byte unknown short subroutine short length, byte error,P");
Opcodes.put(new Byte((byte)0x40),"DELALLTASKS ,void, void,CP");
Opcodes.put(new Byte((byte)0x42),"SETSENSORMODE ,byte sensor byte code, void,CP");
Opcodes.put(new Byte((byte)0x43),"WAIT ,byte src short arg, void,C");
Opcodes.put(new Byte((byte)0x44),"DIVIDEVAR ,byte index byte src short arg, void,CP");
Opcodes.put(new Byte((byte)0x45),"TRANSFERDATA ,short index short length byte data[length] byte checksum, byte error,P");
Opcodes.put(new Byte((byte)0x50),"STOPALLTASKS ,void, void,CP");
Opcodes.put(new Byte((byte)0x51),"PLAYSOUND ,byte sound, void,CP");
Opcodes.put(new Byte((byte)0x52),"SETDATALOGSIZE ,short size, byte error,CP");
Opcodes.put(new Byte((byte)0x54),"MULTIPLYVAR ,byte index byte src short arg, void,CP");
Opcodes.put(new Byte((byte)0x60),"POWEROFF ,void, void,CP");
Opcodes.put(new Byte((byte)0x61),"DELTASK ,byte task, void,CP");
Opcodes.put(new Byte((byte)0x62),"DATALOGNEXT ,byte src byte arg, byte error,CP");
Opcodes.put(new Byte((byte)0x64),"SIGNVAR ,byte index byte src short arg, void,CP");
Opcodes.put(new Byte((byte)0x65),"DELFIRMWARE ,byte key[5], void,CP");
Opcodes.put(new Byte((byte)0x70),"DELALLSUBS ,void, void,CP");
Opcodes.put(new Byte((byte)0x71),"STARTTASK ,byte task, void,CP");
Opcodes.put(new Byte((byte)0x72),"BRANCHALWAYSFAR ,byte offset byte extension, void,C");
Opcodes.put(new Byte((byte)0x74),"ABSVAL ,byte index byte src short arg, void,CP");
Opcodes.put(new Byte((byte)0x75),"DOWNLOADFIRMWARE,short address short checksum byte unknown, void,P");
Opcodes.put(new Byte((byte)0x81),"STOPTASK ,byte task, void,CP");
Opcodes.put(new Byte((byte)0x82),"SETLOOPCOUNTER ,byte src byte arg, void,C");
Opcodes.put(new Byte((byte)0x84),"ANDVAR ,byte index byte src byte arg, void,CP");
Opcodes.put(new Byte((byte)0x90),"CLEARMESSAGE ,void, void,C");
Opcodes.put(new Byte((byte)0x91),"SETPROGRAMNUM ,byte program, void,CP");
Opcodes.put(new Byte((byte)0x92),"DECCNTRANDBRANCH,short offset, void,C");
Opcodes.put(new Byte((byte)0x94),"ORVAR ,byte index byte src byte arg, void,CP");
Opcodes.put(new Byte((byte)0x95),"TESTANDBRANCH ,byte opsrc1 byte src2 short arg1 byte arg2 short offset,void,C");
Opcodes.put(new Byte((byte)0xa1),"CLEARTIME ,byte timer, void,CP");
Opcodes.put(new Byte((byte)0xa4),"UPLOADDATALOG ,short first short count, dlrec data[length],P");
Opcodes.put(new Byte((byte)0xa5),"UNLOCKFIRMWARE ,byte key[5], byte data[25],P");
Opcodes.put(new Byte((byte)0xb1),"SETPOWERDOWN ,byte minutes, void,CP");
Opcodes.put(new Byte((byte)0xb2),"SENDMESSAGE ,byte src byte arg, void,C");
Opcodes.put(new Byte((byte)0xc1),"DELSUB ,byte subroutine, void,CP");
Opcodes.put(new Byte((byte)0xd1),"CLEARSENSOR ,byte sensor, void,CP");
Opcodes.put(new Byte((byte)0xe1),"SETMOTORDIR ,byte code, void,CP");
Opcodes.put(new Byte((byte)0xf7),"SETMESSAGE ,byte message, void,PC");
}
// added port interface parameter to Dario's code
public RCXLoaderFrame(RCXPortInterface port) {
super("RCX Loader");
// changed from Dario's code
rcxPort = port;
addWindowListener(this);
topPanel = new Panel();
topPanel.setLayout(new BorderLayout());
tableButton = new Button("table");
tableButton.addActionListener(this);
textField = new TextField();
// textField.setEditable(false);
// textField.setEnabled(false);
// tableButton.setEnabled(false);
textField.addActionListener(this);
textPanel = new Panel();
textPanel.setLayout(new BorderLayout(5,5));
topPanel.add(textField,"Center");
topPanel.add(tableButton,"East");
textPanel.add(topPanel,"North");
textArea = new TextArea();
// textArea.setEditable(false);
textArea.setFont(new Font("Courier",Font.PLAIN,12));
textPanel.add(textArea,"Center");
add(textPanel, "Center");
textArea.setText("initializing...\n");
Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
setBounds(screen.width/2-370/2,screen.height/2-370/2,370,370);
// setVisible(true);
// changed listener type from Dario's code
try {
// We are remote to the object we are listening to
// (the RCXPort), so the RCXPort must get a stub object
// for us. We have subclassed from Frame, not from
// UnicastRemoteObject. So we must export ourselves
// for the remote references to work
UnicastRemoteObject.exportObject(this);
rcxPort.addListener(this);
} catch(Exception e) {
textArea.append(e.toString());
}
tableButton.setEnabled(true);
/*
if(rcxPort.isOpen()) {
textArea.append("RCXPort initialized.\n");
String lasterror = rcxPort.getLastError();
if(lasterror!=null)
textArea.append(lasterror+"\n");
textField.setEditable(true);
textField.setEnabled(true);
textField.requestFocus();
}
else {
if(portName!=null) {
textArea.append("Failed to create RCXPort with "+portName+"\n");
textArea.append("Port "+portName+" is invalid or may be ");
textArea.append("currently used.\nTry another port.\n");
textArea.append("Edit or create a file named parameters.txt ");
textArea.append("that has:\nport=COM1\n(replace COM1 with ");
textArea.append("the correct port name)\n");
}
else
textArea.append("Please specify a port in parameters.txt\n");
textArea.append("Create a file that has:\nport=COM1\n");
textArea.append("(replace COM1 with the correct port name)\n");
}
*/
}
/*
public void receivedMessage(byte[] responseArray) {
if(responseArray==null)
return;
for(int loop=0;loop<responseArray.length;loop++) {
int newbyte = (int)responseArray[loop];
if(newbyte<0) newbyte=256+newbyte;
sbuffer = new StringBuffer(Integer.toHexString(newbyte));
if(sbuffer.length()<2)
sbuffer.insert(0,'0');
textArea.append(sbuffer+" ");
lenCount+=3;
if(lenCount==charPerLine) {
lenCount=0;
textArea.append("\n");
}
}
if(lenCount!=charPerLine)
textArea.append("\n");
}
*/
/*
public void receivedError(String error) {
textArea.append(error+"\n");
}
*/
public void actionPerformed(ActionEvent evt) {
Object obj = evt.getSource();
if(obj==textField) {
String strInput = textField.getText();
textField.setText("");
textArea.append("> "+strInput+"\n");
try {
byteArray = rcxPort.parseString(strInput);
} catch(RemoteException e) {
textArea.append(e.toString());
}
// byteArray = RCXOpcode.parseString(strInput);
if(byteArray==null) {
textArea.append("Error: illegal hex character or length\n");
return;
}
if(rcxPort!=null) {
try {
if(!rcxPort.write(byteArray)) {
textArea.append("Error: writing data to port "+portName+"\n");
}
} catch(Exception e) {
textArea.append(e.toString());
}
}
}
else if(obj==tableButton) {
// make this all in the ui side
showTable();
setLocation(0,getLocation().y);
}
}
public void windowActivated(WindowEvent e) { }
public void windowClosed(WindowEvent e) { }
public void windowDeactivated(WindowEvent e) { }
public void windowDeiconified(WindowEvent e) { }
public void windowIconified(WindowEvent e) { }
public void windowOpened(WindowEvent e) { }
public void windowClosing(WindowEvent e) {
/*
if(rcxPort!=null)
rcxPort.close();
*/
System.exit(0);
}
public void notify(RemoteEvent evt) throws UnknownEventException,
java.rmi.RemoteException {
long id = evt.getID();
long seqNo = evt.getSequenceNumber();
if (id == RCXPortInterface.MESSAGE_EVENT) {
byte[] message = rcxPort.getMessage(seqNo);
StringBuffer sbuffer = new StringBuffer();
for(int n = 0; n < message.length; n++) {
int newbyte = (int) message[n];
if (newbyte < 0) {
newbyte += 256;
}
sbuffer.append(Integer.toHexString(newbyte) + " ");
}
textArea.append(sbuffer.toString());
System.out.println("MESSAGE: " + sbuffer.toString());
} else if (id == RCXPortInterface.ERROR_EVENT) {
textArea.append(rcxPort.getError(seqNo));
} else {
throw new UnknownEventException("Unknown message " + evt.getID());
}
}
public void showTable()
{
if(opcodeFrame!=null)
{
opcodeFrame.dispose();
opcodeFrame=null;
return;
}
opcodeFrame = new Frame("RCX Opcodes Table");
Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
opcodeFrame.setBounds(screen.width/2-70,0,screen.width/2+70,screen.height-25);
opcodeTextArea = new TextArea(" Opcode ,parameters, response, C=program command P=remote command\n",60,100);
opcodeTextArea.setFont(new Font("Courier",Font.PLAIN,10));
opcodeFrame.add(opcodeTextArea);
Enumeration k = Opcodes.keys();
for (Enumeration e = Opcodes.elements(); e.hasMoreElements();) {
String tmp = Integer.toHexString(((Byte)k.nextElement()).intValue());
tmp = tmp.substring(tmp.length()-2)+" "+(String)e.nextElement()+"\n";
opcodeTextArea.append(tmp);
}
opcodeTextArea.setEditable(false);
opcodeFrame.setVisible(true);
}
}
The factory object for the RCX is now easy to define, it just returns a
RCXLoaderFrame
in the getUI()
method:
/**
* RCXLoaderFrameFactory.java
*
*
* Created: Sun Nov 14 18:22:46 1999
*
* @author Jan Newmarch
* @version 1.0
*/
package rcx.jini;
import ui.AWTFrameFactory;
import ui.UICreationException;
import java.util.Map;
public class RCXLoaderFrameFactory extends AWTFrameFactory {
public RCXLoaderFrameFactory() {
super();
}
public RCXLoaderFrameFactory(String name, String role) {
super(name, role);
}
public RCXLoaderFrameFactory(String name, String role, Map properties) {
super(name, role, properties);
}
public Object getUI(Object obj) throws UICreationException {
if (obj instanceof RCXPortInterface) {
return new RCXLoaderFrame((RCXPortInterface) obj);
} else {
throw new UICreationException();
}
}
} // RCXLoaderFrameFactory
The factory object is exported by making it one of the Entry
objects with a name and role that is agreed on by all services and clients:
Entry[] entries = {new RCXLoaderFrameFactory("generic", "user")};
JoinManager joinMgr = new JoinManager(impl,
entries,
this,
new LeaseRenewalManager());
The RCXLoaderFrame
is a general interface to any RCX robot.
Of course, there could be many other such interfaces, differing in the
classes used, the amount of internationalisation support, the appearance,
etc. All the variations, however, will just use the
standard RCXPortInterface
, as that is all they know about.
The Lego pieces can be combined in a huge variety of ways, and the RCX itself is programmable. So you can build an RCX car, an RCX crane, an RCX maze-runner, and so on. Each different robot can be driven by the general interface, but most could benefit from a custom-built interface for that type of robot. This is typical: for example, every blender could be driven from a general blender user interface (using the - possibly - forthcoming standard blender interface :-). But the blenders from individual vendors would have their own customised user interface for their brand of blender.
I have been using an RCX car. While it can do lots of things, for demonstrations it has been convenient to use five commands: forward, stop, back, left and right, with a user interface looking like
The CarJFrame
class produces the user interface as a Swing
JFrame
, with the buttons generating specific RCX code for this
model.
package rcx.jini;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import net.jini.core.event.RemoteEventListener;
import net.jini.core.event.RemoteEvent;
import net.jini.core.event.UnknownEventException;
import java.net.URL;
class CarJFrame extends JFrame
implements RemoteEventListener, ActionListener {
protected RCXPortInterface port = null;
JFrame frame;
JTextArea text;
public CarJFrame(RCXPortInterface port) {
super() ;
this.port = port;
frame = new JFrame("Lego MindStorms");
Container content = frame.getContentPane();
JLabel label = null;
ImageIcon icon = null;
try {
icon = new ImageIcon(new
URL("http://localhost/images/mindstorms.jpg"));
switch (icon.getImageLoadStatus()) {
case MediaTracker.ABORTED:
case MediaTracker.ERRORED:
System.out.println("Error");
icon = null;
break;
case MediaTracker.COMPLETE:
System.out.println("Complete");
break;
case MediaTracker.LOADING:
System.out.println("Loading");
break;
}
} catch(java.net.MalformedURLException e) {
e.printStackTrace();
}
if (icon != null) {
label = new JLabel(icon);
} else {
label = new JLabel("MINDSTORMS");
}
JPanel pane = new JPanel();
pane.setLayout(new GridLayout(2, 3));
content.add(label, "North");
content.add(pane, "Center");
JButton btn = new JButton("Forward");
pane.add(btn);
btn.addActionListener(this);
btn = new JButton("Stop");
pane.add(btn);
btn.addActionListener(this);
btn = new JButton("Back");
pane.add(btn);
btn.addActionListener(this);
btn = new JButton("Left");
pane.add(btn);
btn.addActionListener(this);
label = new JLabel("");
pane.add(label);
btn = new JButton("Right");
pane.add(btn);
btn.addActionListener(this);
frame.pack();
frame.setVisible(true);
}
public void actionPerformed(ActionEvent evt) {
String name = evt.getActionCommand();
byte[] command;
try {
if (name.equals("Forward")) {
command = port.parseString("e1 81");
if (! port.write(command)) {
System.err.println("command failed");
}
command = port.parseString("21 81");
if (! port.write(command)) {
System.err.println("command failed");
}
} else if (name.equals("Stop")) {
command = port.parseString("21 41");
if (! port.write(command)) {
System.err.println("command failed");
}
} else if (name.equals("Back")) {
command = port.parseString("e1 41");
if (! port.write(command)) {
System.err.println("command failed");
}
command = port.parseString("21 81");
if (! port.write(command)) {
System.err.println("command failed");
}
} else if (name.equals("Left")) {
command = port.parseString("e1 04");
if (! port.write(command)) {
System.err.println("command failed");
}
command = port.parseString("21 84");
if (! port.write(command)) {
System.err.println("command failed");
}
Thread.sleep(100);
command = port.parseString("21 44");
if (! port.write(command)) {
System.err.println("command failed");
}
} else if (name.equals("Straight")) {
} else if (name.equals("Right")) {
command = port.parseString("e1 84");
if (! port.write(command)) {
System.err.println("command failed");
}
command = port.parseString("21 84");
if (! port.write(command)) {
System.err.println("command failed");
}
Thread.sleep(100);
command = port.parseString("21 44");
if (! port.write(command)) {
System.err.println("command failed");
}
}
} catch(Exception e) {
e.printStackTrace();
}
}
public void notify(RemoteEvent evt) throws UnknownEventException,
java.rmi.RemoteException {
// System.out.println(evt.toString());
long id = evt.getID();
long seqNo = evt.getSequenceNumber();
if (id == RCXPortInterface.MESSAGE_EVENT) {
byte[] message = port.getMessage(seqNo);
StringBuffer sbuffer = new StringBuffer();
for(int n = 0; n < message.length; n++) {
int newbyte = (int) message[n];
if (newbyte < 0) {
newbyte += 256;
}
sbuffer.append(Integer.toHexString(newbyte) + " ");
}
System.out.println("MESSAGE: " + sbuffer.toString());
} else if (id == RCXPortInterface.ERROR_EVENT) {
System.out.println("ERROR: " + port.getError(seqNo));
} else {
throw new UnknownEventException("Unknown message " + evt.getID());
}
}
}
The factory generates a CarJFrame
object
/**
* CarJFrameFactory.java
*
*
* Created: Fri Oct 29 23:27:16 1999
*
* @author Jan Newmarch
* @version 1.0
*/
package rcx.jini;
import ui.SwingFrameFactory;
import ui.UICreationException;
import java.util.Map;
import rcx.*;
public class CarJFrameFactory extends SwingFrameFactory {
public CarJFrameFactory() {
super();
}
public CarJFrameFactory(String name, String role) {
super(name, role);
}
public CarJFrameFactory(String name, String role, Map properties) {
super(name, role, properties);
}
public Object getUI(Object service)
throws UICreationException {
if (service instanceof RCXPortInterface) {
return new CarJFrame((RCXPortInterface) service);
} else {
throw new UICreationException();
}
}
} // CarJFrameFactory
Both of the user interfaces discussed can be exported by expanding the set
of Entry
objects.
Entry[] entries = {new RCXLoaderFrameFactory("generic", "user"),
new CarJFrameFactory("car", "user")};
JoinManager joinMgr = new JoinManager(impl,
entries,
this,
new LeaseRenewalManager());
The following client will start up all user interfaces that it understands
package client;
import rcx.jini.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.rmi.RMISecurityManager;
import net.jini.discovery.LookupDiscovery;
import net.jini.discovery.DiscoveryListener;
import net.jini.discovery.DiscoveryEvent;
import net.jini.core.lookup.ServiceRegistrar;
import net.jini.core.lookup.ServiceTemplate;
import net.jini.core.event.RemoteEventListener;
import net.jini.core.event.RemoteEvent;
import net.jini.core.event.UnknownEventException;
import java.rmi.server.UnicastRemoteObject;
import java.rmi.RemoteException;
import net.jini.core.entry.Entry;
import net.jini.core.lookup.ServiceMatches;
import net.jini.core.lookup.ServiceItem;
import ui.UIFactory;
import ui.AWTFrameFactory;
import ui.SwingFrameFactory;
import ui.UICreationException;
/**
* TestRCX2.java
*
*
* Created: Wed Mar 17 14:29:15 1999
*
* @author Jan Newmarch
* @version 1.0
*/
public class TestRCX2 implements DiscoveryListener {
public static void main(String argv[]) {
new TestRCX2();
// stay around long enough to receive replies
try {
Thread.currentThread().sleep(1000000L);
} catch(java.lang.InterruptedException e) {
// do nothing
}
}
public TestRCX2() {
System.setSecurityManager(new RMISecurityManager());
LookupDiscovery discover = null;
try {
discover = new LookupDiscovery(LookupDiscovery.ALL_GROUPS);
} catch(Exception e) {
System.err.println(e.toString());
System.exit(1);
}
discover.addDiscoveryListener(this);
}
public void discovered(DiscoveryEvent evt) {
ServiceRegistrar[] registrars = evt.getRegistrars();
Class [] classes = new Class[] {RCXPortInterface.class};
RCXPortInterface port = null;
Entry[] entries = {new UIFactory(null, null)};
ServiceTemplate template = new ServiceTemplate(null, classes,
entries);
for (int n = 0; n < registrars.length; n++) {
System.out.println("Service found");
ServiceRegistrar registrar = registrars[n];
ServiceMatches matches = null;
try {
matches = registrar.lookup(template, 10);
} catch(java.rmi.RemoteException e) {
e.printStackTrace();
System.exit(2);
}
System.out.println("match count; " + matches.items.length);
for (int nn = 0; nn < matches.items.length; nn++) {
ServiceItem item = matches.items[nn];
port = (RCXPortInterface) item.service;
if (port == null) {
System.out.println("port null");
continue;
}
Entry[] attributes = item.attributeSets;
System.out.println("Attribute count: " + attributes.length);
for (int m = 0; m < attributes.length; m++) {
System.out.println(attributes[m].toString());
Entry attr = attributes[m];
if (attr instanceof UIFactory) {
showUI(port, (UIFactory) attr);
}
}
}
}
}
public void discarded(DiscoveryEvent evt) {
// empty
}
private void showUI(RCXPortInterface port, UIFactory attr) {
System.out.println("showing ui");
if (attr instanceof AWTFrameFactory) {
Frame frame = null;
try {
frame = (Frame) attr.getUI(port);
} catch(UICreationException e) {
e.printStackTrace();
return;
}
frame.setVisible(true);
} else if (attr instanceof SwingFrameFactory) {
JFrame frame = null;
try {
frame = (JFrame) attr.getUI(port);
} catch(UICreationException e) {
e.printStackTrace();
return;
}
frame.setVisible(true);
} else {
System.out.println("non-gui entry");
}
}
} // TestRCX