001package jmri.jmrix.bachrus;
002
003import java.io.DataInputStream;
004import java.io.OutputStream;
005import java.util.Vector;
006import org.slf4j.Logger;
007import org.slf4j.LoggerFactory;
008import jmri.jmrix.purejavacomm.SerialPortEvent;
009import jmri.jmrix.purejavacomm.SerialPortEventListener;
010
011/**
012 * Converts Stream-based I/O to/from Speedo messages. The "SpeedoInterface" side
013 * sends/receives message objects. The connection to a SpeedoPortController is
014 * via a pair of *Streams, which then carry sequences of characters for
015 * transmission. Note that this processing is handled in an independent thread.
016 * <p>
017 * Removed Runnable implementation and methods for it.
018 *
019 * @author Bob Jacobsen Copyright (C) 2001
020 * @author Andrew Crosland Copyright (C) 2010
021 * @author Andrew Berridge Copyright (C) 2010 for gnu io (RXTX)
022 */
023public class SpeedoTrafficController implements SpeedoInterface, SerialPortEventListener {
024
025    private SpeedoReply reply = new SpeedoReply();
026
027    /**
028     * Create a new SpeedoTrafficController instance.
029     *
030     * @param adaptermemo the associated SystemConnectionMemo
031     */
032    public SpeedoTrafficController(SpeedoSystemConnectionMemo adaptermemo) {
033    }
034
035    // The methods to implement the SpeedoInterface
036
037    protected Vector<SpeedoListener> cmdListeners = new Vector<SpeedoListener>();
038
039    @Override
040    public boolean status() {
041        return (ostream != null && istream != null);
042    }
043
044    @Override
045    public synchronized void addSpeedoListener(SpeedoListener l) {
046        // add only if not already registered
047        if (l == null) {
048            throw new java.lang.NullPointerException();
049        }
050        if (!cmdListeners.contains(l)) {
051            cmdListeners.addElement(l);
052        }
053    }
054
055    @Override
056    public synchronized void removeSpeedoListener(SpeedoListener l) {
057        if (cmdListeners.contains(l)) {
058            cmdListeners.removeElement(l);
059        }
060    }
061
062    SpeedoListener lastSender = null;
063
064    @SuppressWarnings("unchecked")
065    protected void notifyReply(SpeedoReply r) {
066        // make a copy of the listener vector to synchronized (not needed for transmit?)
067        Vector<SpeedoListener> v;
068        synchronized (this) {
069            v = (Vector<SpeedoListener>) cmdListeners.clone();
070        }
071        // forward to all listeners
072        int cnt = v.size();
073        for (int i = 0; i < cnt; i++) {
074            SpeedoListener client = v.elementAt(i);
075            try {
076                // skip forwarding to the last sender for now, we'll get them later
077                if (lastSender != client) {
078                    client.reply(r);
079                }
080            } catch (Exception e) {
081                log.warn("notify: During dispatch to {} Exception", client, e);
082            }
083        }
084
085        // Forward to the last listener who send a message.
086        // This is done _second_ so monitoring can have already stored the reply
087        // before a response is sent.
088        if (lastSender != null) {
089            lastSender.reply(r);
090        }
091    }
092
093    // methods to connect/disconnect to a source of data in a LnPortController
094
095    private SpeedoPortController controller = null;
096
097    /**
098     * Make connection to existing PortController object.
099     * @param p speedo port controller.
100     */
101    public void connectPort(SpeedoPortController p) {
102        istream = p.getInputStream();
103        ostream = p.getOutputStream();
104        if (controller != null) {
105            log.warn("connectPort: connect called while connected");
106        }
107        controller = p;
108    }
109
110    /**
111     * Break connection to existing SpeedoPortController object.
112     * Once broken, attempts to send via "message" member will fail.
113     * @param p speedo port controller.
114     */
115    public void disconnectPort(SpeedoPortController p) {
116        istream = null;
117        ostream = null;
118        if (controller != p) {
119            log.warn("disconnectPort: disconnect called from non-connected LnPortController");
120        }
121        controller = null;
122    }
123
124    // data members to hold the streams
125    DataInputStream istream = null;
126    OutputStream ostream = null;
127
128    /*
129     * Speedo replies end with ";"
130     */
131    boolean endReply(SpeedoReply msg) {
132        // Detect that the reply buffer ends with ";"
133        int num = msg.getNumDataElements();
134        // ptr is offset of last element in SpeedoReply
135        int ptr = num - 1;
136        if (msg.getElement(ptr) != ';') {
137            return false;
138        }
139        unsolicited = true;
140        return true;
141    }
142
143    private boolean unsolicited;
144
145    /**
146     * Respond to an event triggered by RXTX. In this case we are
147     * only dealing with DATA_AVAILABLE but the other events are left here for
148     * reference.
149     */
150    @Override
151    public void serialEvent(SerialPortEvent event) {
152        switch (event.getEventType()) {
153            case SerialPortEvent.BI:
154            case SerialPortEvent.OE:
155            case SerialPortEvent.FE:
156            case SerialPortEvent.PE:
157            case SerialPortEvent.CD:
158            case SerialPortEvent.CTS:
159            case SerialPortEvent.DSR:
160            case SerialPortEvent.RI:
161            case SerialPortEvent.OUTPUT_BUFFER_EMPTY:
162                break;
163            case SerialPortEvent.DATA_AVAILABLE:
164                // we get here if data has been received
165                //fill the current reply with any data received
166                int replyCurrentSize = this.reply.getNumDataElements();
167                int i;
168                for (i = replyCurrentSize; i < SpeedoReply.maxSize - replyCurrentSize; i++) {
169                    try {
170                        if (istream.available() == 0) {
171                            break; //nothing waiting to be read
172                        }
173                        byte char1 = istream.readByte();
174                        this.reply.setElement(i, char1);
175
176                    } catch (Exception e) {
177                        log.debug("Exception handling reply cause {}", e.getCause(), e);
178                    }
179                    if (endReply(this.reply)) {
180                        sendreply();
181                        break;
182                    }
183                }
184
185                break;
186            default:
187                log.warn("Unhandled event type: {}", event.getEventType());
188                break;
189        }
190    }
191
192    /**
193     * Send the current reply - built using data from serialEvent.
194     */
195    private void sendreply() {
196        //send the reply
197        {
198            final SpeedoReply thisReply = this.reply;
199            if (unsolicited) {
200                thisReply.setUnsolicited();
201            }
202            final SpeedoTrafficController thisTc = this;
203            // return a notification via the queue to ensure end
204            Runnable r = new Runnable() {
205
206                SpeedoReply msgForLater = thisReply;
207                SpeedoTrafficController myTc = thisTc;
208
209                @Override
210                public void run() {
211                    myTc.notifyReply(msgForLater);
212                }
213            };
214            javax.swing.SwingUtilities.invokeLater(r);
215        }
216        //Create a new reply, ready to be filled
217        this.reply = new SpeedoReply();
218    }
219
220    private final static Logger log = LoggerFactory.getLogger(SpeedoTrafficController.class);
221
222}