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