package mde.jini.service;
import java.awt.Frame;
import java.awt.Point;
import java.awt.event.*;
import java.applet.Applet;
import java.io.IOException;
import java.rmi.*;
import java.rmi.activation.*;
import java.rmi.server.*;
import java.security.*;
import java.util.*;
import net.jini.*;
import net.jini.discovery.*;
import net.jini.core.entry.*;
import net.jini.core.lookup.*;
import net.jini.lookup.entry.*;
import mde.jini.visual.*;
/**
* This class acts as the Jini proxy for a Lego Mindstorms tank. It is instantiated by TankDaemonImpl
* when a tank registers during the sign-up state. Once intstantiated, this class discovers and joins
* the Jini network, making itself available to be commanded by either a PalmPilot or workstation client.
*/
public class TankProxyImpl implements TankProxy {
private List commandQueue = Collections.synchronizedList(new ArrayList());
private Set pendingEvents = Collections.synchronizedSet(new HashSet());
private EventNotifier notifyThread = null;
private Vector listeners = new Vector();
private boolean registered = false;
private byte tankID;
private long proxyMaxSilence = 1000 * 5; // 30 seconds
private long leaseDuration = 1000 * 30; // 30 seconds
private Long proxyExpirationSync = new Long(-1);
private long proxyExpiration;
private TankExpirer expirer = null;
private RandomDriver driver;
private boolean driverAlive;
private String[] groups = LookupDiscovery.NO_GROUPS;
private ServiceAgent agent;
private Applet applet;
private Remote remote;
private Point[] locations = {
new Point(50,50), new Point(575,50), new Point(50,275), new Point(575,275)
};
private Frame frame;
private long lastUpdate;
private class RandomDriver extends Thread {
public void run() {
byte[] cmds = {TankCommands.FORWARD, TankCommands.BACKWARD,
TankCommands.LEFT, TankCommands.RIGHT,
TankCommands.STOP, TankCommands.FIRE};
while (driverAlive) {
try {
sendCommand(cmds[(int)(Math.random() * 6)]);
sleep((int)(Math.random() * 3000));
} catch (Exception e) {e.printStackTrace();}
}
}
}
private class TankExpirer extends Thread {
public TankExpirer() {
super("tank proxy expirer");
setDaemon(false);
extendExpiration();
}
public void run() {
while (!isInterrupted()) {
synchronized (proxyExpirationSync) {
if (proxyExpiration <= System.currentTimeMillis()) {
System.out.println("TankProxyImpl(): tank proxy expired, tankID = " + (tankID & 0x0f));
// Inform the service agent to cancel its service lease w/ all of its discovered
// service registrars. They will, in turn, send "service delete" notifications
// to any matching event listeners (ie, TankDaemonImpl)
stopApplet();
agent.unregister();
return;
}
}
try { sleep(1000); }
catch (InterruptedException e) {}
}
}
public void extendExpiration() {
synchronized (proxyExpirationSync) {
proxyExpiration = System.currentTimeMillis() + proxyMaxSilence;
}
}
}
private class EventNotifier extends Thread {
public EventNotifier() {
super("tank event notifier");
setDaemon(false);
}
public void run() {
while (!isInterrupted()) {
TankEvent event = null;
synchronized (pendingEvents) {
if (pendingEvents.isEmpty()) {
notifyThread = null;
return;
}
event = (TankEvent)pendingEvents.iterator().next();
}
notifyListeners(event);
synchronized (pendingEvents) {
pendingEvents.remove(event);
}
}
}
public void notifyListeners(TankEvent event) {
Vector l;
synchronized (listeners) {
l = (Vector)listeners.clone();
}
for (int i = l.size(); --i >= 0; ) {
TankEventListener listener = (TankEventListener)l.elementAt(i);
try {
listener.eventOccurred(event);
} catch (ConnectException e) {
synchronized (listeners) {
listeners.removeElement(listener);
}
} catch (RemoteException e) {
System.out.println("TankProxyImpl(): exception during event notification: " + e);
}
}
}
}
/** Create a new tank proxy instance */
public TankProxyImpl(ActivationID id, MarshalledObject data) throws IOException {
Activatable.exportObject(this, id, 0);
System.out.println("TankProxyImpl(): constructor called...");
}
/** Startup a tank proxy, causing it to discover and join the Jini network */
public void startup(byte tankID, String[] groups) throws RemoteException {
this.tankID = tankID;
this.groups = groups;
System.out.println("TankProxyImpl.startup() called for tankID = " + (tankID & 0x0f));
// Discover/join a Jini lookup service
//
try { remote = RemoteObject.toStub(this); }
catch (NoSuchObjectException e) { e.printStackTrace(); }
// Start a service agent to manage our Jini registration and lease renewals
try { agent = new ServiceAgent(groups); }
catch (IOException e) { e.printStackTrace(); }
String filename = "/tmp/TankProxy-" + toHex(tankID);
ServiceID serviceID = ServiceAgent.readServiceID(filename);
applet = new TankApplet((TankProxy)remote);
Entry[] attribs = { new Name(describeTank(tankID)), (Entry)applet };
ServiceItem item = new ServiceItem(serviceID, remote, attribs);
agent.register(item, leaseDuration, filename);
startApplet();
}
public void activateRandomDriver(boolean randomDriver) {
if (randomDriver) {
driverAlive = true;
driver = new RandomDriver();
driver.start();
} else {
driverAlive = false;
}
}
/** Called by clients wishing to register with this tank proxy */
public boolean register() throws RemoteException {
if (registered == true) {
return false;
}
return(registered = true);
}
/** Called by clients wishing to unregister with this tank proxy */
public void unregister() throws RemoteException {
registered = false;
}
/** Called by clients to send commands to the tank daemon */
public void sendCommand(byte command) throws RemoteException {
System.out.println("TankProxyImpl.sendCommand(): queueing command " + toHex(command));
try {
switch (command) {
case TankCommands.FORWARD:
appendCommand(TankCommands.FORWARD);
break;
case TankCommands.BACKWARD:
appendCommand(TankCommands.BACKWARD);
break;
case TankCommands.LEFT:
appendCommand(TankCommands.LEFT);
break;
case TankCommands.RIGHT:
appendCommand(TankCommands.RIGHT);
break;
case TankCommands.STOP:
appendCommand(TankCommands.STOP);
break;
case TankCommands.FIRE:
appendCommand(TankCommands.FIRE);
break;
default:
throw new InvalidParameterException("Invalid tank command: " + toHex(command));
}
} catch (Exception e) {
e.printStackTrace();
}
}
/** Called by the tank daemon to fetch the next queued tank command to execute */
public byte getCommand() throws RemoteException {
byte command = getNextCommand();
if (command != TankCommands.STATUS) {
sendTankEvent(new TankEvent(this, tankID, command));
}
return command;
}
/** Called by the tank daemon with the latest status message received from a tank */
public void updateStatus(TankEvent event) throws RemoteException {
long now = System.currentTimeMillis();
//System.out.println("TankProxyImpl(): tankID = " + (event.getTankID() & 0x0f) + ", last event " + (now-lastUpdate) + " msecs ago");
if (expirer == null) {
expirer = new TankExpirer();
expirer.start();
} else {
expirer.extendExpiration();
}
sendTankEvent(event);
lastUpdate = System.currentTimeMillis();
}
/** Called by clients wishing to be notified of tank events */
public void addTankEventListener(TankEventListener l) throws RemoteException {
System.out.println("TankProxy.addTankEventListener()...");
synchronized (listeners) {
listeners.addElement(l);
}
}
/** Called by clients no longer wishing to receive tank events */
public void removeTankEventListener(TankEventListener l) throws RemoteException {
System.out.println("TankProxy.removeTankEventListener()...");
synchronized (listeners) {
listeners.removeElement(l);
}
}
/** Returns the id of the tank being proxied */
public byte getTankID() throws RemoteException {
return tankID;
}
/** Returns the unique Jini service id for this proxy */
public ServiceID getServiceID() throws RemoteException {
return agent.getServiceID();
}
// TankProxyImpl methods
private void appendCommand(byte command) throws IOException {
synchronized (commandQueue) {
commandQueue.add(new Byte(command));
}
}
private byte getNextCommand() {
Byte nextCommand = null;
synchronized (commandQueue) {
if (commandQueue.size() == 0) {
return TankCommands.STATUS;
}
nextCommand = (Byte)commandQueue.remove(0);
}
return nextCommand.byteValue();
}
private void startApplet() {
frame = new Frame(describeTank(tankID));
frame.add(applet);
applet.init();
applet.start();
int tankNumber = tankID & 0x0f;
frame.pack();
System.out.println("TankProxyImpl().startApplet() called for tankID = " + tankNumber);
if (tankNumber >= 0 && tankNumber <= 3) {
frame.setLocation(locations[tankNumber]);
}
frame.setVisible(true);
}
private void stopApplet() {
System.out.println("TankProxyImpl().stopApplet() called for tankID = " + (tankID & 0x0f));
applet.stop();
applet.destroy();
frame.dispose();
}
private void sendTankEvent(TankEvent event) {
synchronized (pendingEvents) {
pendingEvents.add(event);
}
if (notifyThread == null) {
notifyThread = new EventNotifier();
notifyThread.start();
}
}
/** Called to shutdown the proxy */
public void shutdown() throws RemoteException {
System.out.println("TankProxyImpl.shutdown()");
driverAlive = false;
}
private String toHex(byte x) {
return ((x & 0xff) < 16 ? "0" : "") + Integer.toHexString(x & 0xff).toUpperCase();
}
private String describeTank(int tankID) {
String tankColor = TankColors.getTankColorName((byte)tankID);
switch (tankID & 0x0f) {
case 0:
return "Lego Mindstorms Tank #1 (" + tankColor + ")";
case 1:
return "Lego Mindstorms Tank #2 (" + tankColor + ")";
case 2:
return "Lego Mindstorms Tank #3 (" + tankColor + ")";
case 3:
return "Lego Mindstorms Tank #4 (" + tankColor + ")";
default:
return "Lego Mindstorms Tank";
}
}
}