001package jmri.jmrix.lenz;
002
003import java.util.HashMap;
004import java.util.concurrent.LinkedBlockingQueue;
005
006import jmri.jmrix.AbstractMRListener;
007import jmri.jmrix.AbstractMRMessage;
008import jmri.jmrix.AbstractMRReply;
009import jmri.jmrix.AbstractMRTrafficController;
010import jmri.jmrix.lenz.XNetProgrammer.XNetConfigurator;
011
012import net.jcip.annotations.GuardedBy;
013
014import org.slf4j.Logger;
015import org.slf4j.LoggerFactory;
016
017/**
018 * Abstract base class for implementations of XNetInterface.
019 * <p>
020 * This provides just the basic interface.
021 * @see jmri.jmrix.AbstractMRTrafficController
022 *
023 * @author Bob Jacobsen Copyright (C) 2002
024 * @author Paul Bender Copyright (C) 2004-2010
025 */
026public abstract class XNetTrafficController extends AbstractMRTrafficController implements XNetInterface {
027
028    @GuardedBy("this")
029    // PENDING: the field should be probably made private w/ accessor to force proper synchronization for reading.
030    protected final HashMap<XNetListener, Integer> mListenerMasks;
031
032    /**
033     * Create a new XNetTrafficController.
034     * Must provide a LenzCommandStation reference at creation time.
035     *
036     * @param pCommandStation reference to associated command station object,
037     *                        preserved for later.
038     */
039    XNetTrafficController(LenzCommandStation pCommandStation) {
040        mCommandStation = pCommandStation;
041        setAllowUnexpectedReply(true);
042        mListenerMasks = new HashMap<>();
043        highPriorityQueue = new LinkedBlockingQueue<>();
044        highPriorityListeners = new LinkedBlockingQueue<>();
045    }
046
047    static XNetTrafficController self = null;
048
049    // Abstract methods for the XNetInterface
050
051    /**
052     * Make connection to existing PortController object.
053     */
054    @Override
055    public void connectPort(jmri.jmrix.AbstractPortController p) {
056        super.connectPort(p);
057        if (p instanceof XNetPortController) {
058            this.addXNetListener(XNetInterface.COMMINFO, new XNetTimeSlotListener((XNetPortController) p));
059        }
060    }
061
062    /**
063     * Forward a preformatted XNetMessage to a specific listener interface.
064     *
065     * @param m Message to send
066     */
067    @Override
068    public void forwardMessage(AbstractMRListener reply, AbstractMRMessage m) {
069        if (!(reply instanceof XNetListener) || !(m instanceof XNetMessage)) {
070            throw new IllegalArgumentException("");
071        }
072        ((XNetListener) reply).message((XNetMessage) m);
073    }
074
075    /**
076     * Forward a preformatted XNetMessage to the registered XNetListeners.
077     * <p>
078     * NOTE: this drops the packet if the checksum is bad.
079     *
080     * @param client is the client getting the message
081     * @param m      Message to send
082     */
083    @Override
084    public void forwardReply(AbstractMRListener client, AbstractMRReply m) {
085        if (!(client instanceof XNetListener) || !(m instanceof XNetReply)) {
086            throw new IllegalArgumentException("");
087        }
088        // check parity
089        if (!((XNetReply) m).checkParity()) {
090            log.warn("Ignore packet with bad checksum: {}", (m));
091        } else {
092            int mask;
093            synchronized (this) {
094                mask = mListenerMasks.getOrDefault(client, XNetInterface.ALL);
095            }
096            if (mask == XNetInterface.ALL) {
097                // Note: also executing this branch, if the client is not registered at all.
098                ((XNetListener) client).message((XNetReply) m);
099            } else if ((mask & XNetInterface.COMMINFO)
100                    == XNetInterface.COMMINFO
101                    && (m.getElement(0)
102                    == XNetConstants.LI_MESSAGE_RESPONSE_HEADER)) {
103                ((XNetListener) client).message((XNetReply) m);
104            } else if ((mask & XNetInterface.CS_INFO)
105                    == XNetInterface.CS_INFO
106                    && (m.getElement(0)
107                    == XNetConstants.CS_INFO
108                    || m.getElement(0)
109                    == XNetConstants.CS_SERVICE_MODE_RESPONSE
110                    || m.getElement(0)
111                    == XNetConstants.CS_REQUEST_RESPONSE
112                    || m.getElement(0)
113                    == XNetConstants.BC_EMERGENCY_STOP)) {
114                ((XNetListener) client).message((XNetReply) m);
115            } else if ((mask & XNetInterface.FEEDBACK)
116                    == XNetInterface.FEEDBACK
117                    && (((XNetReply) m).isFeedbackMessage()
118                    || ((XNetReply) m).isFeedbackBroadcastMessage())) {
119                ((XNetListener) client).message((XNetReply) m);
120            } else if ((mask & XNetInterface.THROTTLE)
121                    == XNetInterface.THROTTLE
122                    && ((XNetReply) m).isThrottleMessage()) {
123                ((XNetListener) client).message((XNetReply) m);
124            } else if ((mask & XNetInterface.CONSIST)
125                    == XNetInterface.CONSIST
126                    && ((XNetReply) m).isConsistMessage()) {
127                ((XNetListener) client).message((XNetReply) m);
128            } else if ((mask & XNetInterface.INTERFACE)
129                    == XNetInterface.INTERFACE
130                    && (m.getElement(0)
131                    == XNetConstants.LI_VERSION_RESPONSE
132                    || m.getElement(0)
133                    == XNetConstants.LI101_REQUEST)) {
134                ((XNetListener) client).message((XNetReply) m);
135            }
136        }
137    }
138
139    // We use the pollMessage routines for high priority messages.
140    // This means responses to time critical messages (turnout off messages).
141    // PENDING: these fields should be probably made private w/ accessor to force proper synchronization for reading.
142    final LinkedBlockingQueue<XNetMessage> highPriorityQueue;
143    final LinkedBlockingQueue<XNetListener> highPriorityListeners;
144
145    public synchronized void sendHighPriorityXNetMessage(XNetMessage m, XNetListener reply) {
146        // using offer as the queue is unbounded and should never block on write.
147        // Note: the message should be inserted LAST, as the message is tested/acquired first
148        // by the reader; serves a a guard for next item processing.
149        highPriorityListeners.add(reply);
150        highPriorityQueue.add(m);
151    }
152
153    @Override
154    protected AbstractMRMessage pollMessage() {
155        try {
156            if (highPriorityQueue.peek() == null) {
157                return null;
158            } else {
159                return highPriorityQueue.take();
160            }
161        } catch (java.lang.InterruptedException ie) {
162            log.error("Interrupted while removing High Priority Message from Queue");
163        }
164        return null;
165    }
166
167    @Override
168    protected AbstractMRListener pollReplyHandler() {
169        try {
170            if (highPriorityListeners.peek() == null) {
171                return null;
172            } else {
173                return highPriorityListeners.take();
174            }
175        } catch (java.lang.InterruptedException ie) {
176            log.error("Interrupted while removing High Priority Message Listener from Queue");
177        }
178        return null;
179    }
180
181    @Override
182    public synchronized void addXNetListener(int mask, XNetListener l) {
183        addListener(l);
184        // This is adds all the mask information.  A better way to do
185        // this would be to allow updating individual bits
186        mListenerMasks.put(l, mask);
187    }
188
189    @Override
190    public synchronized void removeXNetListener(int mask, XNetListener l) {
191        removeListener(l);
192        // This is removes all the mask information.  A better way to do
193        // this would be to allow updating of individual bits
194        mListenerMasks.remove(l);
195    }
196
197    /**
198     * This method has to be available, even though it doesn't do anything on
199     * Lenz.
200     */
201    @Override
202    protected AbstractMRMessage enterProgMode() {
203        return null;
204    }
205
206    /**
207     * Return the value of getExitProgModeMsg().
208     */
209    @Override
210    protected AbstractMRMessage enterNormalMode() {
211        return XNetMessage.getExitProgModeMsg();
212    }
213
214    /**
215     * Check to see if the programmer associated with this interface is idle or
216     * not.
217     */
218    @Override
219    protected boolean programmerIdle() {
220        if (mMemo == null) {
221            return true;
222        }
223        jmri.jmrix.lenz.XNetProgrammerManager pm = mMemo.getProgrammerManager();
224        if (pm == null) {
225            return true;
226        }
227        XNetConfigurator p = (XNetConfigurator) pm.getGlobalProgrammer().getConfigurator();
228        if (p == null) {
229            return true;
230        }
231        return !(p.programmerBusy());
232    }
233
234    @Override
235    protected boolean endOfMessage(AbstractMRReply msg) {
236        int len = (msg.getElement(0) & 0x0f) + 2;  // opCode+Nbytes+ECC
237        log.debug("Message Length {} Current Size {}", len, msg.getNumDataElements());
238        return msg.getNumDataElements() >= len;
239    }
240
241    @Override
242    protected AbstractMRReply newReply() {
243        return new XNetReply();
244    }
245
246    /**
247     * Get characters from the input source, and file a message.
248     * <p>
249     * Returns only when the message is complete.
250     * <p>
251     * Only used in the Receive thread.
252     *
253     * @param msg     message to fill
254     * @param istream character source.
255     * @throws java.io.IOException when presented by the input source.
256     */
257    @Override
258    protected void loadChars(AbstractMRReply msg, java.io.DataInputStream istream) throws java.io.IOException {
259        int i;
260        for (i = 0; i < msg.maxSize(); i++) {
261            byte char1 = readByteProtected(istream);
262            msg.setElement(i, char1 & 0xFF);
263            if (endOfMessage(msg)) {
264                break;
265            }
266        }
267        if (mCurrentState == IDLESTATE) {
268            msg.setUnsolicited();
269        }
270    }
271
272    @Override
273    protected void handleTimeout(AbstractMRMessage msg, AbstractMRListener l) {
274        super.handleTimeout(msg, l);
275        if (l != null) {
276            ((XNetListener) l).notifyTimeout((XNetMessage) msg);
277        }
278    }
279
280    @Override
281    protected void notifyMessage(AbstractMRMessage m, AbstractMRListener notMe) {
282        super.notifyMessage(m, notMe);
283        if(notMe!=null) {
284            forwardMessage(notMe, m);
285        }
286    }
287
288    /**
289     * Reference to the command station in communication here.
290     */
291    final LenzCommandStation mCommandStation;
292
293    /**
294     * Get access to communicating command station object.
295     *
296     * @return associated Command Station object
297     */
298    public LenzCommandStation getCommandStation() {
299        return mCommandStation;
300    }
301
302    /**
303     * Reference to the system connection memo.
304     */
305    XNetSystemConnectionMemo mMemo = null;
306
307    /**
308     * Get access to the system connection memo associated with this traffic
309     * controller.
310     *
311     * @return associated systemConnectionMemo object
312     */
313    public XNetSystemConnectionMemo getSystemConnectionMemo() {
314        return (mMemo);
315    }
316
317    /**
318     * Set the system connection memo associated with this traffic controller.
319     *
320     * @param m associated systemConnectionMemo object
321     */
322    public void setSystemConnectionMemo(XNetSystemConnectionMemo m) {
323        mMemo = m;
324    }
325
326    private XNetFeedbackMessageCache _FeedbackCache = null;
327
328    /**
329     * Return an XNetFeedbackMessageCache object associated with this traffic
330     * controller.
331     * @return the feedback message cache. One is provided if null.
332     */
333    public XNetFeedbackMessageCache getFeedbackMessageCache() {
334        if (_FeedbackCache == null) {
335            _FeedbackCache = new XNetFeedbackMessageCache(this);
336        }
337        return _FeedbackCache;
338    }
339
340    /**
341     * @return whether or not this connection currently has a timeslot from the Command station.
342     */
343    boolean hasTimeSlot(){
344       return ((XNetPortController)controller).hasTimeSlot();
345    }
346
347    private static final Logger log = LoggerFactory.getLogger(XNetTrafficController.class);
348
349}