001package jmri.jmrix.dccpp;
002
003import java.nio.charset.StandardCharsets;
004import org.slf4j.Logger;
005import org.slf4j.LoggerFactory;
006
007/**
008 * Converts Stream-based I/O to/from DCC++ messages. The "DCCppInterface" side
009 * sends/receives DCCppMessage objects. The connection to a DCCppPortController
010 * is via a pair of *Streams, which then carry sequences of characters for
011 * transmission.
012 * <p>
013 * Messages come to this via the main GUI thread, and are forwarded back to
014 * listeners in that same thread. Reception and transmission are handled in
015 * dedicated threads by RcvHandler and XmtHandler objects. Those are internal
016 * classes defined here. The thread priorities are:
017 * <ul>
018 * <li> RcvHandler - at highest available priority
019 * <li> XmtHandler - down one, which is assumed to be above the GUI
020 * <li> (everything else)
021 * </ul>
022 *
023 * @author Bob Jacobsen Copyright (C) 2001
024 * @author Mark Underwood Copyright (C) 2015
025 *
026 * Based on XNetPacketizer by Bob Jacobsen
027 */
028public class DCCppPacketizer extends DCCppTrafficController {
029
030    public DCCppPacketizer(DCCppCommandStation pCommandStation) {
031        super(pCommandStation);
032        // The instance method (from DCCppTrafficController) is deprecated.
033        // But for the moment we need to make sure we set the static
034        // self variable, and the instance method does this for us in a
035        // static way (which makes spotbugs happy).
036        //instance();
037        log.debug("DCCppPacketizer created");
038    }
039
040// The methods to implement the DCCppInterface
041
042    /**
043     * Forward a preformatted DCCppMessage to the actual interface.
044     *
045     * The message is converted to a byte array and queue for transmission
046     *
047     * @param m     Message to send
048     * @param reply Listener to notify when the reply to the message is received
049     */
050    //TODO: Can this method be folded back up into the parent
051    // DCCppTrafficController class?
052    @Override
053    public void sendDCCppMessage(DCCppMessage m, DCCppListener reply) {
054        if (m.length() != 0) {
055            log.debug("Adding '{}' to send queue", m);            
056            sendMessage(m, reply);
057            // why the next line?
058            // https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html#yield--
059            // states "It is rarely appropriate to use this method"
060            java.lang.Thread.yield();
061        }
062    }
063
064    /**
065     * Add header to the outgoing byte stream.
066     *
067     * @param msg The output byte stream
068     * @param m   ignored
069     * @return next location in the stream to fill
070     */
071    //TODO: Can this method be folded back up into the parent
072    // DCCppTrafficController class?
073    @Override
074    protected int addHeaderToOutput(byte[] msg, jmri.jmrix.AbstractMRMessage m) {
075        if (log.isTraceEnabled()) {
076            log.trace("Appending '<' to start of outgoing message. msg length = {}", msg.length);
077        }
078        msg[0] = (byte) '<';
079        return 1;
080    }
081
082    //    public void startThreads() {
083    // Doesn't do anything generically.
084    // Most Packetizers won't do anything.  The TCP
085    //}
086    /**
087     * Add trailer to the outgoing byte stream. This version adds the checksum
088     * to the last byte.
089     *
090     * @param msg    The output byte stream
091     * @param offset the first byte not yet used
092     * @param m      the message to check
093     */
094    //TODO: Can this method be folded back up into the parent
095    // DCCppTrafficController class?
096    @Override
097    protected void addTrailerToOutput(byte[] msg, int offset, jmri.jmrix.AbstractMRMessage m) {
098        if (log.isTraceEnabled()) {
099            log.trace("aTTO offset = {} message = {} msg length = {}", offset, m, msg.length);
100        }
101        if (m.getNumDataElements() == 0) {
102            return;
103        }
104        //msg[offset - 1] = (byte) m.getElement(m.getNumDataElements() - 1);
105        msg[offset] = '>';
106        if (log.isTraceEnabled()) {
107            log.trace("finished string = {}", new String(msg, StandardCharsets.UTF_8));
108        }
109    }
110
111    /**
112     * Check to see if PortController object can be sent to. returns true if
113     * ready, false otherwise May throw an Exception.
114     */
115    @Override
116    public boolean portReadyToSend(jmri.jmrix.AbstractPortController p) {
117        if (p instanceof DCCppPortController && ((DCCppPortController) p).okToSend()) {
118            ((DCCppPortController) p).setOutputBufferEmpty(false);
119            return true;
120        } else {
121            log.warn("DCC++ port not ready to send");
122            return false;
123        }
124    }
125
126    /**
127     * Get characters from the input source, and file a message.
128     * <p>
129     * Returns only when the message is complete.
130     * <p>
131     * Only used in the Receive thread.
132     *
133     * @param msg     message to fill
134     * @param istream character source.
135     * @throws java.io.IOException when presented by the input source.
136     */
137    // TODO: Can this method be folded back up into the parent DCCppTrafficController class?
138    @Override
139    protected void loadChars(jmri.jmrix.AbstractMRReply msg, java.io.DataInputStream istream) throws java.io.IOException {
140        int i;
141        StringBuilder m = new StringBuilder();
142        log.trace("loading characters from port");
143
144        if (!(msg instanceof DCCppReply)) {
145            log.error("SerialDCCppPacketizer.loadChars called on non-DCCppReply msg!");
146            return;
147        }
148
149        byte char1 = readByteProtected(istream);
150        while (char1 != '<') {
151            // Spin waiting for '<'
152            char1 = readByteProtected(istream);
153            if (char1 != '<') {
154                log.trace("skipping char: ({})", (char) char1);
155            }
156        }
157        log.trace("Message started");
158        // Pick up the rest of the command
159        for (i = 0; i < msg.maxSize(); i++) {
160            char1 = readByteProtected(istream);
161            if (char1 == '>') {
162                log.debug("Received: '{}'", m);
163                // NOTE: Cast is OK because we checked runtime type of msg above.
164                ((DCCppReply) msg).parseReply(m.toString());
165                return;
166            } else {
167                m.append((char) char1);
168                log.trace("msg char[{}]: {} ({})", i, char1, (char) char1);
169            }
170        }
171        log.warn("msg size {} exceeded before end of msg char '>' encountered.", msg.maxSize());
172        log.warn("msg truncated to: '{}'", m);
173    }
174
175    private final static Logger log = LoggerFactory.getLogger(DCCppPacketizer.class);
176
177}