/*
* Copyright (c) 1998 Sun Microsystems, Inc. All Rights Reserved.
*
* The contents of this file are subject to the Sun Community Source License,
* Jini Software Kit, v. 1.0 DC (the "License"); you may not use this
* file except in compliance with the License. You may obtain a copy of
* the License at http://java.sun.com/products/jini. Software distributed
* under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY
* OF ANY KIND, either express or implied. See the License for the
* specific language governing rights and limitations under the License.
*
* CopyrightVersion 1.0_JiniDC_Public
*/
package mde.jini.service;
import java.io.*;
import java.net.*;
import java.rmi.*;
import java.rmi.server.*;
import java.util.*;
import net.jini.discovery.*;
import net.jini.core.entry.*;
import net.jini.core.event.*;
import net.jini.core.lease.*;
import net.jini.core.lookup.*;
import net.jini.lookup.entry.*;
/**
* The PalmProxy class is a Jini proxy for the PalmPilot. A PalmProxy object is instantiated by the PalmDaemon
* when a socket connection is established. At that time, the PalmProxy participates in the discovery and join
* protocol to become part of the Jini network. The PalmProxy can then perform lookups and receive service events
* from Lego Mindstorm tank proxies.
*/
public class PalmProxy extends Thread {
private Map registrations = Collections.synchronizedMap(new HashMap());
private ServiceRegistrar[] registrars;
private BufferedReader in;
private PrintWriter out;
private Socket sock;
private TankListener listener;
private TankProxy tank;
private ServiceID tankServiceID;
private byte tankStatus = PalmProtocol.STATUS_BASE;
private Byte tankStatusSync = new Byte((byte)-1);
private static int SOCKET_TIMEOUT = 120000; // 2 minutes
/** Inner class for receiving and handling Jini service events */
private class TankListener extends UnicastRemoteObject implements Serializable, RemoteEventListener, TankEventListener {
/** Create a tank event listener */
public TankListener() throws RemoteException {
super();
}
/** RemoteEventListener interface method called by the Jini lookup service to deliver events */
public void notify(RemoteEvent event) throws UnknownEventException {
ServiceEvent se = (ServiceEvent)event;
if (se.getTransition() == ServiceRegistrar.TRANSITION_MATCH_NOMATCH) {
if (tankServiceID != null && tankServiceID.equals(se.getServiceID())) {
clearTankStatus();
updateTankStatus(PalmProtocol.STATUS_ERROR);
tank = null;
}
cancelEventLease((ServiceRegistrar)se.getSource());
}
}
/** TankEventListener interface method called by a TankProxy for notification of tank events */
public void eventOccurred(TankEvent event) throws RemoteException {
switch (event.getCommand()) {
case TankCommands.HIT:
updateTankStatus(PalmProtocol.STATUS_HIT);
break;
case TankCommands.OBSTRUCTED:
updateTankStatus(PalmProtocol.STATUS_OBSTRUCTED);
break;
}
}
}
/** Create a palm proxy */
public PalmProxy(Socket sock, ServiceRegistrar[] registrars) throws SocketException, IOException {
System.out.println("PalmProxy: accepting connection from " + sock.getInetAddress());
this.sock = sock;
this.registrars = registrars;
sock.setSoTimeout(SOCKET_TIMEOUT);
in = new BufferedReader(new InputStreamReader(sock.getInputStream()));
out = new PrintWriter(sock.getOutputStream());
}
/** Start execution of the palm proxy */
public void run() {
try {
while (true) {
String request = receivePalm();
if (request.equals(PalmProtocol.REGISTER)) {
if (!palmIsRegistered()) {
clearTankStatus();
tank = findAvailableTank();
// This is really ugly but is done so that status can be sent back to the
// PalmPilot as quickly as possible. Since registerForEvents() makes a
// remote method call to a Jini service registrar, which takes time, we
// send status to the Palm and assume the event registration succeeded.
if (tank == null) {
sendTankStatus();
continue;
}
updateTankStatus(PalmProtocol.STATUS_REGISTERED);
sendTankStatus();
registerForEvents(tank);
continue;
}
sendTankStatus();
continue;
}
if (request.equals(PalmProtocol.FORWARD)) {
sendTankStatus();
if (palmIsRegistered()) {
tank.sendCommand(TankCommands.FORWARD);
}
continue;
}
if (request.equals(PalmProtocol.BACKWARD)) {
sendTankStatus();
if (palmIsRegistered()) {
tank.sendCommand(TankCommands.BACKWARD);
}
continue;
}
if (request.equals(PalmProtocol.LEFT)) {
sendTankStatus();
if (palmIsRegistered()) {
tank.sendCommand(TankCommands.LEFT);
}
continue;
}
if (request.equals(PalmProtocol.RIGHT)) {
sendTankStatus();
if (palmIsRegistered()) {
tank.sendCommand(TankCommands.RIGHT);
}
continue;
}
if (request.equals(PalmProtocol.FIRE)) {
sendTankStatus();
if (palmIsRegistered()) {
tank.sendCommand(TankCommands.FIRE);
}
continue;
}
if (request.equals(PalmProtocol.STOP)) {
sendTankStatus();
if (palmIsRegistered()) {
tank.sendCommand(TankCommands.STOP);
}
continue;
}
if (request.equals(PalmProtocol.STATUS)) {
sendTankStatus();
continue;
}
if (request.equals(PalmProtocol.QUIT)) {
sendTankStatus();
break;
}
System.out.println("PalmProxy.run(): received invalid command: '" + request + "'");
sendTankStatus();
}
} catch (RemoteException e) {
System.err.println("PalmProxy.run(): RemoteException caught during tank command execution: " + e);
updateTankStatus(PalmProtocol.STATUS_ERROR);
} catch (IOException e) {
// InterruptedIOException thrown if connection lost during read operation
System.err.println("PalmProxy.run(): connection lost to PalmPilot: " + e);
} finally {
System.out.println("PalmProxy.run(): shutting down connection from " + sock.getInetAddress());
cancelEventLeases();
try {
sock.close();
if (tank != null) {
tank.removeTankEventListener(listener);
tank.unregister();
}
} catch (Exception e) {}
}
}
private boolean palmIsRegistered() {
return ((tankStatus & PalmProtocol.MASK_REGISTERED) == 0) ? (false) : (true);
}
private void sendTankStatus() {
System.out.println("PalmProxy.sendTankStatus(): sending " + Integer.toBinaryString((int)tankStatus));
try {
sendPalm(tankStatus);
} catch (IOException e) {
System.err.println("PalmProxy.sendTankStatus(): exception caught sending to Palm: " + e);
}
resetTankStatus();
}
private void updateTankStatus(byte value) {
synchronized (tankStatusSync) {
tankStatus |= value;
}
}
private void resetTankStatus() {
synchronized (tankStatusSync) {
tankStatus = (palmIsRegistered()) ? (PalmProtocol.STATUS_REGISTERED) : (PalmProtocol.STATUS_BASE);
}
}
private void clearTankStatus() {
synchronized (tankStatusSync) {
tankStatus = PalmProtocol.STATUS_BASE;
}
}
private TankProxy findAvailableTank() throws RemoteException {
System.out.println("PalmProxy.findAvailableTank(): " + registrars.length + " service registrars available to search");
ServiceTemplate tmpl = tankTemplate();
for (int i = 0; i < registrars.length; i++) {
ServiceRegistrar registrar = registrars[i];
ServiceMatches matches = null;
try {
matches = registrar.lookup(tmpl, 50);
} catch (RemoteException e) {
System.err.println("PalmProxy.findAvailableTank(): registrar " + registrar.getServiceID() + " threw: " + e);
continue;
}
System.out.println("PalmProxy.findAvailableTank(): found " + matches.totalMatches + " matching tank(s)");
for (int j = 0; j < matches.totalMatches; j++) {
TankProxy tank = (TankProxy)matches.items[j].service;
if (tank.register()) {
System.out.println("PalmProxy.findAvailableTank(): tank " + (tank.getTankID() & 0xf) + " is ready for action!");
tankServiceID = matches.items[j].serviceID;
return tank;
} else {
System.out.println("PalmProxy.findAvailableTank(): tank " + (tank.getTankID() & 0xf) + " is already taken");
}
}
}
return null;
}
private ServiceTemplate tankTemplate() {
Class[] classes = { null };
try {
classes[0] = Class.forName("mde.jini.service.TankProxy");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return new ServiceTemplate(null, classes, null);
}
private boolean registerForEvents(TankProxy tank) {
try {
// First, register for events from the tank proxy
listener = new TankListener();
tank.addTankEventListener(listener);
// Next, register for Jini service transition events
int transitions = ServiceRegistrar.TRANSITION_MATCH_NOMATCH;
ServiceTemplate tmpl = tankTemplate();
for (int i = 0; i < registrars.length; i++) {
ServiceRegistrar registrar = registrars[i];
EventRegistration registration = registrar.notify(tmpl, transitions, listener, null, Long.MAX_VALUE);
synchronized (registrations) {
registrations.put(registrar.getServiceID(), registration);
}
}
} catch (RemoteException e) {
System.err.println("RemoteException caught during event registration: " + e);
return false;
}
return true;
}
/** Send a response to the PalmPilot */
public void sendPalm(byte value) throws IOException {
synchronized (out) {
out.write(value);
out.write((byte)'\n');
out.flush();
}
}
private String receivePalm() throws IOException {
synchronized (in) {
String line = in.readLine();
if (line == null) {
throw new InterruptedIOException();
}
return line;
}
}
private void cancelEventLease(ServiceRegistrar registrar) {
ServiceID serviceID = registrar.getServiceID();
EventRegistration registration;
synchronized (registrations) {
registration = (EventRegistration)registrations.remove(serviceID);
}
if (registration != null) {
Lease lease = registration.getLease();
try {
lease.cancel();
} catch (UnknownLeaseException e) {
} catch (RemoteException e) {}
}
}
private void cancelEventLeases() {
for (int i = 0; i < registrars.length; i++) {
cancelEventLease(registrars[i]);
}
}
}