001package jmri.implementation;
002
003import java.beans.PropertyChangeEvent;
004import java.beans.PropertyVetoException;
005import java.util.ArrayList;
006import java.util.List;
007
008import jmri.Block;
009import jmri.EntryPoint;
010import jmri.JmriException;
011import jmri.NamedBean;
012import jmri.NamedBeanUsageReport;
013import jmri.Section;
014import jmri.Sensor;
015import jmri.Transit;
016import jmri.TransitSection;
017import jmri.TransitSectionAction;
018
019/**
020 * A Transit is a group of Sections representing a specified path through a
021 * layout.
022 * <p>
023 * A Transit may have the following states:
024 * <dl>
025 * <dt>IDLE</dt>
026 * <dd>available for assignment to a train</dd>
027 * <dt>ASSIGNED</dt>
028 * <dd>linked to a train in an {@link jmri.jmrit.dispatcher.ActiveTrain}</dd>
029 * </dl>
030 * <p>
031 * When assigned to a Transit, options may be set for the assigned Section. The
032 * Section and its options are kept in a {@link jmri.TransitSection} object.
033 * <p>
034 * To accommodate passing sidings and other track features, there may be
035 * multiple Sections connecting two other Sections in a Transit. If so, one
036 * Section is assigned as primary, and the other connecting Sections are
037 * assigned as alternates.
038 * <p>
039 * A Section may be in a Transit more than once, for example if a train is to
040 * make two or more loops around a layout before going elsewhere.
041 * <p>
042 * A Transit is normally traversed in the forward direction, that is, the
043 * direction of increasing Section Numbers. When a Transit traversal is started
044 * up, it is always started in the forward direction. However, to accommodate
045 * point-to-point (back and forth) route designs, the direction of travel in a
046 * Transit may be "reversed". While the Transit direction is "reversed", the
047 * direction of travel is the direction of decreasing Section numbers. Whether a
048 * Transit is in the "reversed" direction is kept in the ActiveTrain using the
049 * Transit.
050 *
051 * @author Dave Duchamp Copyright (C) 2008-2011
052 */
053public class DefaultTransit extends AbstractNamedBean implements Transit {
054
055    /*
056     * Instance variables (not saved between runs)
057     */
058    private static final NamedBean.DisplayOptions USERSYS = NamedBean.DisplayOptions.USERNAME_SYSTEMNAME;
059    private int mState = Transit.IDLE;
060    private final ArrayList<TransitSection> mTransitSectionList = new ArrayList<>();
061    private int mMaxSequence = 0;
062    private final ArrayList<Integer> blockSecSeqList = new ArrayList<>();
063    private final ArrayList<Integer> destBlocksSeqList = new ArrayList<>();
064
065    public DefaultTransit(String systemName, String userName) {
066        super(systemName, userName);
067    }
068
069    public DefaultTransit(String systemName) {
070        super(systemName);
071    }
072
073    /**
074     * Query the state of this Transit.
075     *
076     * @return {@link #IDLE} or {@link #ASSIGNED}
077     */
078    @Override
079    public int getState() {
080        return mState;
081    }
082
083    /**
084     * Set the state of this Transit.
085     *
086     * @param state {@link #IDLE} or {@link #ASSIGNED}
087     */
088    @Override
089    public void setState(int state) {
090        if ((state == Transit.IDLE) || (state == Transit.ASSIGNED)) {
091            int old = mState;
092            mState = state;
093            firePropertyChange("state", old, mState);
094        } else {
095            log.error("Attempt to set Transit state to illegal value - {}", state);
096        }
097    }
098
099    /**
100     * Add a Section to this Transit.
101     *
102     * @param s the Section object to add
103     */
104    @Override
105    public void addTransitSection(TransitSection s) {
106        mTransitSectionList.add(s);
107        mMaxSequence = s.getSequenceNumber();
108    }
109
110    /**
111     * Get the list of TransitSections.
112     *
113     * @return a copy of the internal list of TransitSections or an empty list
114     */
115    @Override
116    public ArrayList<TransitSection> getTransitSectionList() {
117        return new ArrayList<>(mTransitSectionList);
118    }
119
120    /**
121     * Get the maximum sequence number used in this Transit.
122     *
123     * @return the maximum sequence
124     */
125    @Override
126    public int getMaxSequence() {
127        return mMaxSequence;
128    }
129
130    /**
131     * Remove all TransitSections in this Transit.
132     */
133    @Override
134    public void removeAllSections() {
135        mTransitSectionList.clear();
136    }
137
138    /**
139     * Check if a Section is in this Transit.
140     *
141     * @param s the section to check for
142     * @return true if the section is present; false otherwise
143     */
144    @Override
145    public boolean containsSection(Section s) {
146        return mTransitSectionList.stream().anyMatch((ts) -> (ts.getSection() == s));
147    }
148
149    /**
150     * Get a List of Sections with a given sequence number.
151     *
152     * @param seq the sequence number
153     * @return the list of of matching sections or an empty list if none
154     */
155    @Override
156    public ArrayList<Section> getSectionListBySeq(int seq) {
157        ArrayList<Section> list = new ArrayList<>();
158        for (TransitSection ts : mTransitSectionList) {
159            if (seq == ts.getSequenceNumber()) {
160                list.add(ts.getSection());
161            }
162        }
163        return list;
164    }
165
166    /**
167     * Get a List of TransitSections with a given sequence number.
168     *
169     * @param seq the sequence number
170     * @return the list of of matching sections or an empty list if none
171     */
172    @Override
173    public ArrayList<TransitSection> getTransitSectionListBySeq(int seq) {
174        ArrayList<TransitSection> list = new ArrayList<>();
175        for (TransitSection ts : mTransitSectionList) {
176            if (seq == ts.getSequenceNumber()) {
177                list.add(ts);
178            }
179        }
180        return list;
181    }
182
183    /**
184     * Get a List of sequence numbers for a given Section.
185     *
186     * @param s the section to match
187     * @return the list of matching sequence numbers or an empty list if none
188     */
189    @Override
190    public ArrayList<Integer> getSeqListBySection(Section s) {
191        ArrayList<Integer> list = new ArrayList<>();
192        for (TransitSection ts : mTransitSectionList) {
193            if (s == ts.getSection()) {
194                list.add(ts.getSequenceNumber());
195            }
196        }
197        return list;
198    }
199
200    /**
201     * Check if a Block is in this Transit.
202     *
203     * @param block the block to check for
204     * @return true if block is present; false otherwise
205     */
206    @Override
207    public boolean containsBlock(Block block) {
208        for (Block b : getInternalBlocksList()) {
209            if (b == block) {
210                return true;
211            }
212        }
213        return false;
214    }
215
216    /**
217     * Get the number of times a Block is in this Transit.
218     *
219     * @param block the block to check for
220     * @return the number of times block is present; 0 if block is not present
221     */
222    @Override
223    public int getBlockCount(Block block) {
224        int count = 0;
225        for (Block b : getInternalBlocksList()) {
226            if (b == block) {
227                count++;
228            }
229        }
230        return count;
231    }
232
233    /**
234     * Get a Section from one of its Blocks and its sequence number.
235     *
236     * @param b   the block within the Section
237     * @param seq the sequence number of the Section
238     * @return the Section or null if no matching Section is present
239     */
240    @Override
241    public Section getSectionFromBlockAndSeq(Block b, int seq) {
242        for (TransitSection ts : mTransitSectionList) {
243            if (ts.getSequenceNumber() == seq) {
244                Section s = ts.getSection();
245                if (s.containsBlock(b)) {
246                    return s;
247                }
248            }
249        }
250        return null;
251    }
252
253    /**
254     * Get Section from one of its EntryPoint Blocks and its sequence number.
255     *
256     * @param b   the connecting block to the Section
257     * @param seq the sequence number of the Section
258     * @return the Section or null if no matching Section is present
259     */
260    @Override
261    public Section getSectionFromConnectedBlockAndSeq(Block b, int seq) {
262        for (TransitSection ts : mTransitSectionList) {
263            if (ts.getSequenceNumber() == seq) {
264                Section s = ts.getSection();
265                if (s.connectsToBlock(b)) {
266                    return s;
267                }
268            }
269        }
270        return null;
271    }
272
273    /**
274     * Get the direction of a Section in the transit from its sequence number.
275     *
276     * @param s   the Section to check
277     * @param seq the sequence number of the Section
278     * @return the direction of the Section (one of {@link jmri.Section#FORWARD}
279     *         or {@link jmri.Section#REVERSE} or zero if s and seq are not in a
280     *         TransitSection together
281     */
282    @Override
283    public int getDirectionFromSectionAndSeq(Section s, int seq) {
284        for (TransitSection ts : mTransitSectionList) {
285            if ((ts.getSection() == s) && (ts.getSequenceNumber() == seq)) {
286                return ts.getDirection();
287            }
288        }
289        return 0;
290    }
291
292    /**
293     * Get a TransitSection in the transit from its Section and sequence number.
294     *
295     * @param s   the Section to check
296     * @param seq the sequence number of the Section
297     * @return the transit section or null if not found
298     */
299    @Override
300    public TransitSection getTransitSectionFromSectionAndSeq(Section s, int seq) {
301        for (TransitSection ts : mTransitSectionList) {
302            if ((ts.getSection() == s) && (ts.getSequenceNumber() == seq)) {
303                return ts;
304            }
305        }
306        return null;
307    }
308
309    /**
310     * Get a list of all blocks internal to this Transit. Since Sections may be
311     * present more than once, blocks may be listed more than once. The sequence
312     * numbers of the Section the Block was found in are accumulated in a
313     * parallel list, which can be accessed by immediately calling
314     * {@link #getBlockSeqList()}.
315     *
316     * @return the list of all Blocks or an empty list if none are present
317     */
318    @Override
319    public ArrayList<Block> getInternalBlocksList() {
320        ArrayList<Block> list = new ArrayList<>();
321        blockSecSeqList.clear();
322        mTransitSectionList.forEach((ts) -> {
323            ts.getSection().getBlockList().stream().forEach((b) -> {
324                list.add(b);
325                blockSecSeqList.add(ts.getSequenceNumber());
326            });
327        });
328        return list;
329    }
330
331    /**
332     * Get a list of sequence numbers in this Transit. This list is generated by
333     * calling {@link #getInternalBlocksList()} or
334     * {@link #getEntryBlocksList()}.
335     *
336     * @return the list of all sequence numbers or an empty list if no Blocks
337     *         are present
338     */
339    @Override
340    public ArrayList<Integer> getBlockSeqList() {
341        return new ArrayList<>(blockSecSeqList);
342    }
343
344    /**
345     * Get a list of all entry Blocks to this Transit. These are Blocks that a
346     * Train might enter from and be going in the direction of this Transit. The
347     * sequence numbers of the Section the Block will enter are accumulated in a
348     * parallel list, which can be accessed by immediately calling
349     * {@link #getBlockSeqList()}.
350     *
351     * @return the list of all blocks or an empty list if none are present
352     */
353    @Override
354    public ArrayList<Block> getEntryBlocksList() {
355        ArrayList<Block> list = new ArrayList<>();
356        ArrayList<Block> internalBlocks = getInternalBlocksList();
357        blockSecSeqList.clear();
358        for (TransitSection ts : mTransitSectionList) {
359            List<EntryPoint> ePointList;
360            if (ts.getDirection() == Section.FORWARD) {
361                ePointList = ts.getSection().getForwardEntryPointList();
362            } else {
363                ePointList = ts.getSection().getReverseEntryPointList();
364            }
365            for (EntryPoint ep : ePointList) {
366                Block eb = ep.getFromBlock();
367                boolean isInternal = false;
368                for (Block ib : internalBlocks) {
369                    if (eb == ib) {
370                        isInternal = true;
371                    }
372                }
373                if (!isInternal) {
374                    // not an internal Block, keep it
375                    list.add(eb);
376                    blockSecSeqList.add(ts.getSequenceNumber());
377                }
378            }
379        }
380        return list;
381    }
382
383    /**
384     * Get a list of all destination blocks that can be reached from a specified
385     * starting block. The sequence numbers of the Sections destination blocks
386     * were found in are accumulated in a parallel list, which can be accessed
387     * by immediately calling {@link #getDestBlocksSeqList()}.
388     * <p>
389     * <strong>Note:</strong> A Train may not terminate in the same Section in
390     * which it starts.
391     * <p>
392     * <strong>Note:</strong> A Train must terminate in a Block within the
393     * Transit.
394     *
395     * @param startBlock     the starting Block to find destinations for
396     * @param startInTransit true if startBlock is within this Transit; false
397     *                       otherwise
398     * @return a list of destination Blocks or an empty list if none exist
399     */
400    @Override
401    public ArrayList<Block> getDestinationBlocksList(Block startBlock, boolean startInTransit) {
402        ArrayList<Block> list = new ArrayList<>();
403        destBlocksSeqList.clear();
404        if (startBlock == null) {
405            return list;
406        }
407        // get the sequence number of the Section of the starting Block
408        int startSeq = -1;
409        ArrayList<Block> startBlocks;
410        if (startInTransit) {
411            startBlocks = getInternalBlocksList();
412        } else {
413            startBlocks = getEntryBlocksList();
414        }
415        // programming note: the above calls initialize blockSecSeqList.
416        for (int k = 0; ((k < startBlocks.size()) && (startSeq == -1)); k++) {
417            if (startBlock == startBlocks.get(k)) {
418                startSeq = (blockSecSeqList.get(k));
419            }
420        }
421        ArrayList<Block> internalBlocks = getInternalBlocksList();
422        //allow for transits of length 1
423        if (startInTransit) {
424            for (int i = internalBlocks.size(); i > 0; i--) {
425                if (blockSecSeqList.get(i - 1) > startSeq) {
426                    // could stop in this block, keep it
427                    list.add(internalBlocks.get(i - 1));
428                    destBlocksSeqList.add(blockSecSeqList.get(i - 1));
429                }
430            }
431        } else {
432            for (int i = internalBlocks.size(); i > 0; i--) {
433                if (blockSecSeqList.get(i - 1) >= startSeq) {
434                    // could stop in this block, keep it
435                    list.add(internalBlocks.get(i - 1));
436                    destBlocksSeqList.add(blockSecSeqList.get(i - 1));
437                }
438            }
439        }
440        return list;
441    }
442
443    /**
444     * Get a list of destination Block sequence numbers in this Transit. This
445     * list is generated by calling
446     * {@link #getDestinationBlocksList(jmri.Block, boolean)}.
447     *
448     * @return the list of all destination Block sequence numbers or an empty
449     *         list if no destination Blocks are present
450     */
451    @Override
452    public ArrayList<Integer> getDestBlocksSeqList() {
453        ArrayList<Integer> list = new ArrayList<>();
454        for (int i = 0; i < destBlocksSeqList.size(); i++) {
455            list.add(destBlocksSeqList.get(i));
456        }
457        return list;
458    }
459
460    /**
461     * Check if this Transit is capable of continuous running.
462     * <p>
463     * A Transit is capable of continuous running if, after an Active Train
464     * completes the Transit, it can automatically be restarted. To be
465     * restartable, the first Section and the last Section must be the same
466     * Section, and the first and last Sections must be defined to run in the
467     * same direction. If the last Section is an alternate Section, the previous
468     * Section is tested. However, if the Active Train does not complete its
469     * Transit in the same Section it started in, the restart will not take
470     * place.
471     *
472     * @return true if continuous running is possible; otherwise false
473     */
474    @Override
475    public boolean canBeResetWhenDone() {
476        TransitSection firstTS = mTransitSectionList.get(0);
477        int lastIndex = mTransitSectionList.size() - 1;
478        TransitSection lastTS = mTransitSectionList.get(lastIndex);
479        boolean OK = false;
480        while (!OK) {
481            if (firstTS.getSection() != lastTS.getSection()) {
482                if (lastTS.isAlternate() && (lastIndex > 1)) {
483                    lastIndex--;
484                    lastTS = mTransitSectionList.get(lastIndex);
485                } else {
486                    log.warn("Section mismatch {} {}", (firstTS.getSection()).getDisplayName(USERSYS), (lastTS.getSection()).getDisplayName(USERSYS));
487                    return false;
488                }
489            }
490            OK = true;
491        }
492        // same Section, check direction
493        if (firstTS.getDirection() != lastTS.getDirection()) {
494            log.warn("Direction mismatch {} {}", (firstTS.getSection()).getDisplayName(USERSYS), (lastTS.getSection()).getDisplayName(USERSYS));
495            return false;
496        }
497        return true;
498    }
499
500    /**
501     * Initialize blocking sensors for Sections in this Transit. This should be
502     * done before any Sections are allocated for this Transit. Only Sections
503     * that are {@link jmri.Section#FREE} are initialized, so as not to
504     * interfere with running active trains. If any Section does not have
505     * blocking sensors, warning messages are logged.
506     *
507     * @return 0 if no errors, number of errors otherwise.
508     */
509    @Override
510    public int initializeBlockingSensors() {
511        int numErrors = 0;
512        for (int i = 0; i < mTransitSectionList.size(); i++) {
513            Section s = mTransitSectionList.get(i).getSection();
514            try {
515                if (s.getForwardBlockingSensor() != null) {
516                    if (s.getState() == Section.FREE) {
517                        s.getForwardBlockingSensor().setState(Sensor.ACTIVE);
518                    }
519                } else {
520                    log.warn("Missing forward blocking sensor for section {}", s.getDisplayName(USERSYS));
521                    numErrors++;
522                }
523            } catch (JmriException reason) {
524                log.error("Exception when initializing forward blocking Sensor for Section {}", s.getDisplayName(USERSYS));
525                numErrors++;
526            }
527            try {
528                if (s.getReverseBlockingSensor() != null) {
529                    if (s.getState() == Section.FREE) {
530                        s.getReverseBlockingSensor().setState(Sensor.ACTIVE);
531                    }
532                } else {
533                    log.warn("Missing reverse blocking sensor for section {}", s.getDisplayName(USERSYS));
534                    numErrors++;
535                }
536            } catch (JmriException reason) {
537                log.error("Exception when initializing reverse blocking Sensor for Section {}", s.getDisplayName(USERSYS));
538                numErrors++;
539            }
540        }
541        return numErrors;
542    }
543
544    //@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "UC_USELESS_OBJECT",
545    //         justification = "SpotBugs doesn't see that toBeRemoved is being read by the forEach clause")
546    @Override
547    public void removeTemporarySections() {
548        ArrayList<TransitSection> toBeRemoved = new ArrayList<>();
549        for (TransitSection ts : mTransitSectionList) {
550            if (ts.isTemporary()) {
551                toBeRemoved.add(ts);
552            }
553        }
554        toBeRemoved.forEach((ts) -> {
555            mTransitSectionList.remove(ts);
556        });
557    }
558
559    @Override
560    public boolean removeLastTemporarySection(Section s) {
561        TransitSection last = mTransitSectionList.get(mTransitSectionList.size() - 1);
562        if (last.getSection() != s) {
563            log.info("Section asked to be removed is not the last one");
564            return false;
565        }
566        if (!last.isTemporary()) {
567            log.info("Section asked to be removed is not a temporary section");
568            return false;
569        }
570        mTransitSectionList.remove(last);
571        return true;
572
573    }
574
575    @Override
576    public String getBeanType() {
577        return Bundle.getMessage("BeanNameTransit");
578    }
579
580    @Override
581    public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException {
582        if ("CanDelete".equals(evt.getPropertyName())) { // NOI18N
583            NamedBean nb = (NamedBean) evt.getOldValue();
584            if (nb instanceof Section) {
585                if (containsSection((Section) nb)) {
586                    throw new PropertyVetoException(Bundle.getMessage("VetoTransitSection", getDisplayName()), evt);
587                }
588            }
589        }
590        // we ignore the property setConfigureManager
591    }
592
593    @Override
594    public List<NamedBeanUsageReport> getUsageReport(NamedBean bean) {
595        List<NamedBeanUsageReport> report = new ArrayList<>();
596        jmri.SensorManager sm = jmri.InstanceManager.getDefault(jmri.SensorManager.class);
597        jmri.SignalHeadManager head = jmri.InstanceManager.getDefault(jmri.SignalHeadManager.class);
598        jmri.SignalMastManager mast = jmri.InstanceManager.getDefault(jmri.SignalMastManager.class);
599        if (bean != null) {
600            getTransitSectionList().forEach((transitSection) -> {
601                if (bean.equals(transitSection.getSection())) {
602                    report.add(new NamedBeanUsageReport("TransitSection"));
603                }
604                if (bean.equals(sm.getSensor(transitSection.getStopAllocatingSensor()))) {
605                    report.add(new NamedBeanUsageReport("TransitSensorStopAllocation"));
606                }
607                // Process actions
608                transitSection.getTransitSectionActionList().forEach((action) -> {
609                    int whenCode = action.getWhenCode();
610                    int whatCode = action.getWhatCode();
611                    if (whenCode == TransitSectionAction.SENSORACTIVE || whenCode == TransitSectionAction.SENSORINACTIVE) {
612                        if (bean.equals(sm.getSensor(action.getStringWhen()))) {
613                            report.add(new NamedBeanUsageReport("TransitActionSensorWhen", transitSection.getSection()));
614                        }
615                    }
616                    if (whatCode == TransitSectionAction.SETSENSORACTIVE || whatCode == TransitSectionAction.SETSENSORINACTIVE) {
617                        if (bean.equals(sm.getSensor(action.getStringWhat()))) {
618                            report.add(new NamedBeanUsageReport("TransitActionSensorWhat", transitSection.getSection()));
619                        }
620                    }
621                    if (whatCode == TransitSectionAction.HOLDSIGNAL || whatCode == TransitSectionAction.RELEASESIGNAL) {
622                        // Could be a signal head or a signal mast.
623                        if (bean.equals(head.getSignalHead(action.getStringWhat()))) {
624                            report.add(new NamedBeanUsageReport("TransitActionSignalHeadWhat", transitSection.getSection()));
625                        }
626                        if (bean.equals(mast.getSignalMast(action.getStringWhat()))) {
627                            report.add(new NamedBeanUsageReport("TransitActionSignalMastWhat", transitSection.getSection()));
628                        }
629                    }
630                });
631            });
632        }
633        return report;
634    }
635
636
637    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DefaultTransit.class);
638
639}