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_ADVANCED_INFO_RESPONSE
110                    || m.getElement(0)
111                    == XNetConstants.CS_SERVICE_MODE_RESPONSE
112                    || m.getElement(0)
113                    == XNetConstants.CS_REQUEST_RESPONSE
114                    || m.getElement(0)
115                    == XNetConstants.BC_EMERGENCY_STOP)) {
116                ((XNetListener) client).message((XNetReply) m);
117            } else if ((mask & XNetInterface.FEEDBACK)
118                    == XNetInterface.FEEDBACK
119                    && (((XNetReply) m).isFeedbackMessage()
120                    || ((XNetReply) m).isFeedbackBroadcastMessage())) {
121                ((XNetListener) client).message((XNetReply) m);
122            } else if ((mask & XNetInterface.THROTTLE)
123                    == XNetInterface.THROTTLE
124                    && ((XNetReply) m).isThrottleMessage()) {
125                ((XNetListener) client).message((XNetReply) m);
126            } else if ((mask & XNetInterface.CONSIST)
127                    == XNetInterface.CONSIST
128                    && ((XNetReply) m).isConsistMessage()) {
129                ((XNetListener) client).message((XNetReply) m);
130            } else if ((mask & XNetInterface.INTERFACE)
131                    == XNetInterface.INTERFACE
132                    && (m.getElement(0)
133                    == XNetConstants.LI_VERSION_RESPONSE
134                    || m.getElement(0)
135                    == XNetConstants.LI101_REQUEST)) {
136                ((XNetListener) client).message((XNetReply) m);
137            }
138        }
139    }
140
141    // We use the pollMessage routines for high priority messages.
142    // This means responses to time critical messages (turnout off messages).
143    // PENDING: these fields should be probably made private w/ accessor to force proper synchronization for reading.
144    final LinkedBlockingQueue<XNetMessage> highPriorityQueue;
145    final LinkedBlockingQueue<XNetListener> highPriorityListeners;
146
147    public synchronized void sendHighPriorityXNetMessage(XNetMessage m, XNetListener reply) {
148        // using offer as the queue is unbounded and should never block on write.
149        // Note: the message should be inserted LAST, as the message is tested/acquired first
150        // by the reader; serves a a guard for next item processing.
151        highPriorityListeners.add(reply);
152        highPriorityQueue.add(m);
153    }
154
155    @Override
156    protected AbstractMRMessage pollMessage() {
157        try {
158            if (highPriorityQueue.peek() == null) {
159                return null;
160            } else {
161                return highPriorityQueue.take();
162            }
163        } catch (java.lang.InterruptedException ie) {
164            log.error("Interrupted while removing High Priority Message from Queue");
165        }
166        return null;
167    }
168
169    @Override
170    protected AbstractMRListener pollReplyHandler() {
171        try {
172            if (highPriorityListeners.peek() == null) {
173                return null;
174            } else {
175                return highPriorityListeners.take();
176            }
177        } catch (java.lang.InterruptedException ie) {
178            log.error("Interrupted while removing High Priority Message Listener from Queue");
179        }
180        return null;
181    }
182
183    @Override
184    public synchronized void addXNetListener(int mask, XNetListener l) {
185        addListener(l);
186        // This is adds all the mask information.  A better way to do
187        // this would be to allow updating individual bits
188        mListenerMasks.put(l, mask);
189    }
190
191    @Override
192    public synchronized void removeXNetListener(int mask, XNetListener l) {
193        removeListener(l);
194        // This is removes all the mask information.  A better way to do
195        // this would be to allow updating of individual bits
196        mListenerMasks.remove(l);
197    }
198
199    /**
200     * This method has to be available, even though it doesn't do anything on
201     * Lenz.
202     */
203    @Override
204    protected AbstractMRMessage enterProgMode() {
205        return null;
206    }
207
208    /**
209     * Return the value of getExitProgModeMsg().
210     */
211    @Override
212    protected AbstractMRMessage enterNormalMode() {
213        return XNetMessage.getExitProgModeMsg();
214    }
215
216    /**
217     * Check to see if the programmer associated with this interface is idle or
218     * not.
219     */
220    @Override
221    protected boolean programmerIdle() {
222        if (mMemo == null) {
223            return true;
224        }
225        jmri.jmrix.lenz.XNetProgrammerManager pm = mMemo.getProgrammerManager();
226        if (pm == null) {
227            return true;
228        }
229        XNetConfigurator p = (XNetConfigurator) pm.getGlobalProgrammer().getConfigurator();
230        if (p == null) {
231            return true;
232        }
233        return !(p.programmerBusy());
234    }
235
236    @Override
237    protected boolean endOfMessage(AbstractMRReply msg) {
238        int len = (msg.getElement(0) & 0x0f) + 2;  // opCode+Nbytes+ECC
239        log.debug("Message Length {} Current Size {}", len, msg.getNumDataElements());
240        return msg.getNumDataElements() >= len;
241    }
242
243    @Override
244    protected AbstractMRReply newReply() {
245        return new XNetReply();
246    }
247
248    /**
249     * Get characters from the input source, and file a message.
250     * <p>
251     * Returns only when the message is complete.
252     * <p>
253     * Only used in the Receive thread.
254     *
255     * @param msg     message to fill
256     * @param istream character source.
257     * @throws java.io.IOException when presented by the input source.
258     */
259    @Override
260    protected void loadChars(AbstractMRReply msg, java.io.DataInputStream istream) throws java.io.IOException {
261        int i;
262        for (i = 0; i < msg.maxSize(); i++) {
263            byte char1 = readByteProtected(istream);
264            msg.setElement(i, char1 & 0xFF);
265            if (endOfMessage(msg)) {
266                break;
267            }
268        }
269        if (mCurrentState == IDLESTATE) {
270            msg.setUnsolicited();
271        }
272    }
273
274    @Override
275    protected void handleTimeout(AbstractMRMessage msg, AbstractMRListener l) {
276        super.handleTimeout(msg, l);
277        if (l != null) {
278            ((XNetListener) l).notifyTimeout((XNetMessage) msg);
279        }
280    }
281
282    @Override
283    protected void notifyMessage(AbstractMRMessage m, AbstractMRListener notMe) {
284        super.notifyMessage(m, notMe);
285        if(notMe!=null) {
286            forwardMessage(notMe, m);
287        }
288    }
289
290    /**
291     * Reference to the command station in communication here.
292     */
293    final LenzCommandStation mCommandStation;
294
295    /**
296     * Get access to communicating command station object.
297     *
298     * @return associated Command Station object
299     */
300    public LenzCommandStation getCommandStation() {
301        return mCommandStation;
302    }
303
304    /**
305     * Reference to the system connection memo.
306     */
307    XNetSystemConnectionMemo mMemo = null;
308
309    /**
310     * Get access to the system connection memo associated with this traffic
311     * controller.
312     *
313     * @return associated systemConnectionMemo object
314     */
315    public XNetSystemConnectionMemo getSystemConnectionMemo() {
316        return (mMemo);
317    }
318
319    /**
320     * Set the system connection memo associated with this traffic controller.
321     *
322     * @param m associated systemConnectionMemo object
323     */
324    public void setSystemConnectionMemo(XNetSystemConnectionMemo m) {
325        mMemo = m;
326    }
327
328    private XNetFeedbackMessageCache _FeedbackCache = null;
329
330    /**
331     * Return an XNetFeedbackMessageCache object associated with this traffic
332     * controller.
333     * @return the feedback message cache. One is provided if null.
334     */
335    public XNetFeedbackMessageCache getFeedbackMessageCache() {
336        if (_FeedbackCache == null) {
337            _FeedbackCache = new XNetFeedbackMessageCache(this);
338        }
339        return _FeedbackCache;
340    }
341
342    /**
343     * @return whether or not this connection currently has a timeslot from the Command station.
344     */
345    boolean hasTimeSlot(){
346       return ((XNetPortController)controller).hasTimeSlot();
347    }
348
349    private static final Logger log = LoggerFactory.getLogger(XNetTrafficController.class);
350
351}