001package jmri.jmrit.display.layoutEditor;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004
005import java.util.*;
006
007import javax.annotation.CheckForNull;
008import javax.annotation.Nonnull;
009
010import jmri.*;
011
012/**
013 * TrackSegment is a segment of track on a layout linking two nodes of the
014 * layout. A node may be a LayoutTurnout, a LevelXing or a PositionablePoint.
015 * <p>
016 * PositionablePoints have 1 or 2 connection points. LayoutTurnouts have 3 or 4
017 * (crossovers) connection points, designated A, B, C, and D. LevelXing's have 4
018 * connection points, designated A, B, C, and D.
019 * <p>
020 * TrackSegments carry the connectivity information between the three types of
021 * nodes. Track Segments serve as the lines in a graph which shows layout
022 * connectivity. For the connectivity graph to be valid, all connections between
023 * nodes must be via TrackSegments.
024 * <p>
025 * TrackSegments carry Block information, as do LayoutTurnouts and LevelXings.
026 * <p>
027 * Arrows and bumpers are visual, presentation aspects handled in the View.
028 *
029 * @author Dave Duchamp Copyright (p) 2004-2009
030 * @author George Warner Copyright (c) 2017-2019
031 */
032public class TrackSegment extends LayoutTrack {
033
034    public TrackSegment(@Nonnull String id,
035            @CheckForNull LayoutTrack c1, HitPointType t1,
036            @CheckForNull LayoutTrack c2, HitPointType t2,
037            boolean main,
038            @Nonnull LayoutEditor models) {
039        super(id, models);
040
041        // validate input
042        if ((c1 == null) || (c2 == null)) {
043            log.error("Invalid object in TrackSegment constructor call - {}", id);
044        }
045
046        if (HitPointType.isConnectionHitType(t1)) {
047            connect1 = c1;
048            type1 = t1;
049        } else {
050            log.error("Invalid connect type 1 ('{}') in TrackSegment constructor - {}", t1, id);
051        }
052        if (HitPointType.isConnectionHitType(t2)) {
053            connect2 = c2;
054            type2 = t2;
055        } else {
056            log.error("Invalid connect type 2 ('{}') in TrackSegment constructor - {}", t2, id);
057        }
058
059        mainline = main;
060    }
061
062    // alternate constructor for loading layout editor panels
063    public TrackSegment(@Nonnull String id,
064            @CheckForNull String c1Name, HitPointType t1,
065            @CheckForNull String c2Name, HitPointType t2,
066            boolean main,
067            @Nonnull LayoutEditor models) {
068        super(id, models);
069
070        tConnect1Name = c1Name;
071        type1 = t1;
072        tConnect2Name = c2Name;
073        type2 = t2;
074
075        mainline = main;
076    }
077
078
079    // defined constants
080    // operational instance variables (not saved between sessions)
081    private NamedBeanHandle<LayoutBlock> namedLayoutBlock = null;
082
083    // persistent instances variables (saved between sessions)
084    protected LayoutTrack connect1 = null;
085    protected HitPointType type1 = HitPointType.NONE;
086    protected LayoutTrack connect2 = null;
087    protected HitPointType type2 = HitPointType.NONE;
088    private boolean mainline = false;
089
090    /**
091     * Get debugging string for the TrackSegment.
092     *
093     * @return text showing id and connections of this segment
094     */
095    @Override
096    public String toString() {
097        return "TrackSegment " + getName()
098                + " c1:{" + getConnect1Name() + " (" + type1 + ")},"
099                + " c2:{" + getConnect2Name() + " (" + type2 + ")}";
100
101    }
102
103    /*
104    * Accessor methods
105     */
106    @Nonnull
107    public String getBlockName() {
108        String result = null;
109        if (namedLayoutBlock != null) {
110            result = namedLayoutBlock.getName();
111        }
112        return ((result == null) ? "" : result);
113    }
114
115    public HitPointType getType1() {
116        return type1;
117    }
118
119    public HitPointType getType2() {
120        return type2;
121    }
122
123    public LayoutTrack getConnect1() {
124        return connect1;
125    }
126
127    public LayoutTrack getConnect2() {
128        return connect2;
129    }
130
131    /**
132     * set a new connection 1
133     *
134     * @param connectTrack   the track we want to connect to
135     * @param connectionType where on that track we want to be connected
136     */
137    protected void setNewConnect1(@CheckForNull LayoutTrack connectTrack, HitPointType connectionType) {
138        connect1 = connectTrack;
139        type1 = connectionType;
140    }
141
142    /**
143     * set a new connection 2
144     *
145     * @param connectTrack   the track we want to connect to
146     * @param connectionType where on that track we want to be connected
147     */
148    protected void setNewConnect2(@CheckForNull LayoutTrack connectTrack, HitPointType connectionType) {
149        connect2 = connectTrack;
150        type2 = connectionType;
151    }
152
153    /**
154     * Replace old track connection with new track connection.
155     *
156     * @param oldTrack the old track connection.
157     * @param newTrack the new track connection.
158     * @param newType  the hit point type.
159     * @return true if successful.
160     */
161    public boolean replaceTrackConnection(@CheckForNull LayoutTrack oldTrack, @CheckForNull LayoutTrack newTrack, HitPointType newType) {
162        boolean result = false; // assume failure (pessimist!)
163        // trying to replace old track with null?
164        if (newTrack == null) {
165            result = true;  // assume success (optimist!)
166            //(yes) remove old connection
167            if (oldTrack != null) {
168                if (connect1 == oldTrack) {
169                    connect1 = null;
170                    type1 = HitPointType.NONE;
171                } else if (connect2 == oldTrack) {
172                    connect2 = null;
173                    type2 = HitPointType.NONE;
174                } else {
175                    log.error("{}.replaceTrackConnection({}, null, {}); Attempt to remove invalid track connection",
176                            getName(), oldTrack.getName(), newType);
177                    result = false;
178                }
179            } else {
180                log.warn("{}.replaceTrackConnection(null, null, {}); Can't replace null track connection with null",
181                        getName(), newType);
182                result = false;
183            }
184        } else // already connected to newTrack?
185        if ((connect1 != newTrack) && (connect2 != newTrack)) {
186            //(no) find a connection we can connect to
187            result = true;  // assume success (optimist!)
188            if (connect1 == oldTrack) {
189                connect1 = newTrack;
190                type1 = newType;
191            } else if (connect2 == oldTrack) {
192                connect2 = newTrack;
193                type2 = newType;
194            } else {
195                log.error("{}.replaceTrackConnection({}, {}, {}); Attempt to replace invalid track connection",
196                        getName(), (oldTrack == null) ? "null" : oldTrack.getName(), newTrack.getName(), newType);
197                result = false;
198            }
199        }
200        return result;
201    }
202
203    /**
204     * @return true if track segment is a main line
205     */
206    @Override
207    public boolean isMainline() {
208        return mainline;
209    }
210
211    public void setMainline(boolean main) {
212        if (mainline != main) {
213            mainline = main;
214            models.redrawPanel();
215            models.setDirty();
216        }
217    }
218
219    public LayoutBlock getLayoutBlock() {
220        return (namedLayoutBlock != null) ? namedLayoutBlock.getBean() : null;
221    }
222
223    public String getConnect1Name() {
224        return getConnectName(connect1, type1);
225    }
226
227    public String getConnect2Name() {
228        return getConnectName(connect2, type2);
229    }
230
231    private String getConnectName(@CheckForNull LayoutTrack layoutTrack, HitPointType type) {
232        return (layoutTrack == null) ? null : layoutTrack.getName();
233    }
234
235    /**
236     * {@inheritDoc}
237     * <p>
238     * This implementation returns null because {@link #getConnect1} and
239     * {@link #getConnect2} should be used instead.
240     */
241    // only implemented here to suppress "does not override abstract method " error in compiler
242    @Override
243    public LayoutTrack getConnection(HitPointType connectionType) throws jmri.JmriException {
244        // nothing to see here, move along
245        throw new jmri.JmriException("Use getConnect1() or getConnect2() instead.");
246    }
247
248    /**
249     * {@inheritDoc}
250     * <p>
251     * This implementation does nothing because {@link #setNewConnect1} and
252     * {@link #setNewConnect2} should be used instead.
253     */
254    // only implemented here to suppress "does not override abstract method " error in compiler
255    @Override
256    public void setConnection(HitPointType connectionType, @CheckForNull LayoutTrack o, HitPointType type) throws jmri.JmriException {
257        // nothing to see here, move along
258        throw new jmri.JmriException("Use setConnect1() or setConnect2() instead.");
259    }
260
261    public void setConnect1(@CheckForNull LayoutTrack o, HitPointType type) {
262        type1 = type;
263        connect1 = o;
264    }
265
266    public void setConnect2(@CheckForNull LayoutTrack o, HitPointType type) {
267        type2 = type;
268        connect2 = o;
269    }
270
271    /**
272     * Set up a LayoutBlock for this Track Segment.
273     *
274     * @param newLayoutBlock the LayoutBlock to set
275     */
276    public void setLayoutBlock(@CheckForNull LayoutBlock newLayoutBlock) {
277        LayoutBlock layoutBlock = getLayoutBlock();
278        if (layoutBlock != newLayoutBlock) {
279            //block has changed, if old block exists, decrement use
280            if (layoutBlock != null) {
281                layoutBlock.decrementUse();
282            }
283            namedLayoutBlock = null;
284            if (newLayoutBlock != null) {
285                String newName = newLayoutBlock.getUserName();
286                if ((newName != null) && !newName.isEmpty()) {
287                    namedLayoutBlock = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(newName, newLayoutBlock);
288                }
289            }
290        }
291    }
292
293    /**
294     * Set up a LayoutBlock for this Track Segment.
295     *
296     * @param name the name of the new LayoutBlock
297     */
298    public void setLayoutBlockByName(@CheckForNull String name) {
299        if ((name != null) && !name.isEmpty()) {
300            LayoutBlock b = models.provideLayoutBlock(name);
301            if (b != null) {
302                namedLayoutBlock = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(name, b);
303            } else {
304                namedLayoutBlock = null;
305            }
306        } else {
307            namedLayoutBlock = null;
308        }
309    }
310
311    // initialization instance variables (used when loading a LayoutEditor)
312    public String tConnect1Name = "";
313    public String tConnect2Name = "";
314
315    public String tLayoutBlockName = "";
316
317    /**
318     * Initialization method. The above variables are initialized by
319     * PositionablePointXml, then the following method is called after the
320     * entire LayoutEditor is loaded to set the specific TrackSegment objects.
321     */
322    @Override
323    @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "Null check performed before using return value")
324    public void setObjects(LayoutEditor p) {
325
326        LayoutBlock lb;
327        if (!tLayoutBlockName.isEmpty()) {
328            lb = p.provideLayoutBlock(tLayoutBlockName);
329            if (lb != null) {
330                namedLayoutBlock = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(lb.getUserName(), lb);
331                lb.incrementUse();
332            } else {
333                log.error("{}.setObjects(...); bad blockname '{}' in tracksegment {}",
334                        getName(), tLayoutBlockName, getName());
335                namedLayoutBlock = null;
336            }
337            tLayoutBlockName = null; //release this memory
338        }
339
340        connect1 = p.getFinder().findObjectByName(tConnect1Name);
341        connect2 = p.getFinder().findObjectByName(tConnect2Name);
342    }
343
344    public void updateBlockInfo() {
345        LayoutBlock layoutBlock = getLayoutBlock();
346        if (layoutBlock != null) {
347            layoutBlock.updatePaths();
348        }
349        LayoutBlock b1 = getBlock(connect1, type1);
350        if ((b1 != null) && (b1 != layoutBlock)) {
351            b1.updatePaths();
352        }
353        LayoutBlock b2 = getBlock(connect2, type2);
354        if ((b2 != null) && (b2 != layoutBlock) && (b2 != b1)) {
355            b2.updatePaths();
356        }
357
358        getConnect1().reCheckBlockBoundary();
359        getConnect2().reCheckBlockBoundary();
360    }
361
362    private LayoutBlock getBlock(LayoutTrack connect, HitPointType type) {
363        LayoutBlock result = null;
364        if (connect != null) {
365            if (type == HitPointType.POS_POINT) {
366                PositionablePoint p = (PositionablePoint) connect;
367                if (p.getConnect1() != this) {
368                    if (p.getConnect1() != null) {
369                        result = p.getConnect1().getLayoutBlock();
370                    }
371                } else {
372                    if (p.getConnect2() != null) {
373                        result = p.getConnect2().getLayoutBlock();
374                    }
375                }
376            } else {
377                result = models.getAffectedBlock(connect, type);
378            }
379        }
380        return result;
381    }
382
383    /**
384     * {@inheritDoc}
385     */
386    @Override
387    public boolean canRemove() {
388        List<String> itemList = new ArrayList<>();
389
390        HitPointType type1 = getType1();
391        LayoutTrack conn1 = getConnect1();
392        itemList.addAll(getPointReferences(type1, conn1));
393
394        HitPointType type2 = getType2();
395        LayoutTrack conn2 = getConnect2();
396        itemList.addAll(getPointReferences(type2, conn2));
397
398        if (!itemList.isEmpty()) {
399            models.displayRemoveWarning(this, itemList, "TrackSegment");  // NOI18N
400        }
401        return itemList.isEmpty();
402    }
403
404    public ArrayList<String> getPointReferences(HitPointType type, LayoutTrack conn) {
405        ArrayList<String> result = new ArrayList<>();
406
407        if (type == HitPointType.POS_POINT && conn instanceof PositionablePoint) {
408            PositionablePoint pt = (PositionablePoint) conn;
409            if (!pt.getEastBoundSignal().isEmpty()) {
410                result.add(pt.getEastBoundSignal());
411            }
412            if (!pt.getWestBoundSignal().isEmpty()) {
413                result.add(pt.getWestBoundSignal());
414            }
415            if (!pt.getEastBoundSignalMastName().isEmpty()) {
416                result.add(pt.getEastBoundSignalMastName());
417            }
418            if (!pt.getWestBoundSignalMastName().isEmpty()) {
419                result.add(pt.getWestBoundSignalMastName());
420            }
421            if (!pt.getEastBoundSensorName().isEmpty()) {
422                result.add(pt.getEastBoundSensorName());
423            }
424            if (!pt.getWestBoundSensorName().isEmpty()) {
425                result.add(pt.getWestBoundSensorName());
426            }
427            if (pt.getType() == PositionablePoint.PointType.EDGE_CONNECTOR && pt.getLinkedPoint() != null) {
428                result.add(Bundle.getMessage("DeleteECisActive"));   // NOI18N
429            }
430        }
431
432        if (HitPointType.isTurnoutHitType(type) && conn instanceof LayoutTurnout) {
433            LayoutTurnout lt = (LayoutTurnout) conn;
434            switch (type) {
435                case TURNOUT_A: {
436                    result = lt.getBeanReferences("A");  // NOI18N
437                    break;
438                }
439                case TURNOUT_B: {
440                    result = lt.getBeanReferences("B");  // NOI18N
441                    break;
442                }
443                case TURNOUT_C: {
444                    result = lt.getBeanReferences("C");  // NOI18N
445                    break;
446                }
447                case TURNOUT_D: {
448                    result = lt.getBeanReferences("D");  // NOI18N
449                    break;
450                }
451                default: {
452                    log.error("Unexpected HitPointType: {}", type);
453                }
454            }
455        }
456
457        if (HitPointType.isLevelXingHitType(type) && conn instanceof LevelXing) {
458            LevelXing lx = (LevelXing) conn;
459            switch (type) {
460                case LEVEL_XING_A: {
461                    result = lx.getBeanReferences("A");  // NOI18N
462                    break;
463                }
464                case LEVEL_XING_B: {
465                    result = lx.getBeanReferences("B");  // NOI18N
466                    break;
467                }
468                case LEVEL_XING_C: {
469                    result = lx.getBeanReferences("C");  // NOI18N
470                    break;
471                }
472                case LEVEL_XING_D: {
473                    result = lx.getBeanReferences("D");  // NOI18N
474                    break;
475                }
476                default: {
477                    log.error("Unexpected HitPointType: {}", type);
478                }
479            }
480        }
481
482        if (HitPointType.isSlipHitType(type) && conn instanceof LayoutSlip) {
483            LayoutSlip ls = (LayoutSlip) conn;
484            switch (type) {
485                case SLIP_A: {
486                    result = ls.getBeanReferences("A");  // NOI18N
487                    break;
488                }
489                case SLIP_B: {
490                    result = ls.getBeanReferences("B");  // NOI18N
491                    break;
492                }
493                case SLIP_C: {
494                    result = ls.getBeanReferences("C");  // NOI18N
495                    break;
496                }
497                case SLIP_D: {
498                    result = ls.getBeanReferences("D");  // NOI18N
499                    break;
500                }
501                default: {
502                    log.error("Unexpected HitPointType: {}", type);
503                }
504            }
505        }
506
507        return result;
508    }
509
510    /**
511     * Remove this object from display and persistance.
512     */
513    public void remove() {
514        // remove from persistance by flagging inactive
515        active = false;
516    }
517
518    private boolean active = true;
519
520    /**
521     * Get state. "active" means that the object is still displayed, and should
522     * be stored.
523     *
524     * @return true if still displayed, else false.
525     */
526    public boolean isActive() {
527        return active;
528    }
529
530    /**
531     * temporary fill of abstract from above
532     */
533    @Override
534    public void reCheckBlockBoundary() {
535        log.info("reCheckBlockBoundary is temporary, but was invoked", new Exception("traceback"));
536    }
537
538
539    /**
540     * {@inheritDoc}
541     */
542    @Override
543    protected List<LayoutConnectivity> getLayoutConnectivity() {
544        List<LayoutConnectivity> results = new ArrayList<>();
545
546        LayoutConnectivity lc = null;
547        LayoutBlock lb1 = getLayoutBlock(), lb2 = null;
548        // ensure that block is assigned
549        if (lb1 != null) {
550            // check first connection for turnout
551            if (HitPointType.isTurnoutHitType(type1)) {
552                // have connection to a turnout, is block different
553                LayoutTurnout lt = (LayoutTurnout) getConnect1();
554                lb2 = lt.getLayoutBlock();
555                if (lt.hasEnteringDoubleTrack()) {
556                    // not RH, LH, or WYE turnout - other blocks possible
557                    if ((type1 == HitPointType.TURNOUT_B) && (lt.getLayoutBlockB() != null)) {
558                        lb2 = lt.getLayoutBlockB();
559                    }
560                    if ((type1 == HitPointType.TURNOUT_C) && (lt.getLayoutBlockC() != null)) {
561                        lb2 = lt.getLayoutBlockC();
562                    }
563                    if ((type1 == HitPointType.TURNOUT_D) && (lt.getLayoutBlockD() != null)) {
564                        lb2 = lt.getLayoutBlockD();
565                    }
566                }
567                if ((lb2 != null) && (lb1 != lb2)) {
568                    // have a block boundary, create a LayoutConnectivity
569                    log.debug("Block boundary  (''{}''<->''{}'') found at {}", lb1, lb2, this);
570                    lc = new LayoutConnectivity(lb1, lb2);
571                    lc.setConnections(this, lt, type1, null);
572                    lc.setDirection(models.computeDirection(
573                                        getConnect2(), type2,
574                                        getConnect1(), type1 ) );
575                    results.add(lc);
576                }
577            } else if (HitPointType.isLevelXingHitType(type1)) {
578                // have connection to a level crossing
579                LevelXing lx = (LevelXing) getConnect1();
580                if ((type1 == HitPointType.LEVEL_XING_A) || (type1 == HitPointType.LEVEL_XING_C)) {
581                    lb2 = lx.getLayoutBlockAC();
582                } else {
583                    lb2 = lx.getLayoutBlockBD();
584                }
585                if ((lb2 != null) && (lb1 != lb2)) {
586                    // have a block boundary, create a LayoutConnectivity
587                    log.debug("Block boundary  (''{}''<->''{}'') found at {}", lb1, lb2, this);
588                    lc = new LayoutConnectivity(lb1, lb2);
589                    lc.setConnections(this, lx, type1, null);
590                    lc.setDirection(models.computeDirection(
591                                        getConnect2(), type2,
592                                        getConnect1(), type1 ) );
593                    results.add(lc);
594                }
595            } else if (HitPointType.isSlipHitType(type1)) {
596                // have connection to a slip crossing
597                LayoutSlip ls = (LayoutSlip) getConnect1();
598                lb2 = ls.getLayoutBlock();
599                if ((lb2 != null) && (lb1 != lb2)) {
600                    // have a block boundary, create a LayoutConnectivity
601                    log.debug("Block boundary  (''{}''<->''{}'') found at {}", lb1, lb2, this);
602                    lc = new LayoutConnectivity(lb1, lb2);
603                    lc.setConnections(this, ls, type1, null);
604                    lc.setDirection(models.computeDirection(
605                                        getConnect2(), type2,
606                                        getConnect1(), type1 ) );
607                    results.add(lc);
608                }
609            }
610            // check second connection for turnout
611            if (HitPointType.isTurnoutHitType(type2)) {
612                // have connection to a turnout
613                LayoutTurnout lt = (LayoutTurnout) getConnect2();
614                lb2 = lt.getLayoutBlock();
615                if (lt.hasEnteringDoubleTrack()) {
616                    // not RH, LH, or WYE turnout - other blocks possible
617                    if ((type2 == HitPointType.TURNOUT_B) && (lt.getLayoutBlockB() != null)) {
618                        lb2 = lt.getLayoutBlockB();
619                    }
620                    if ((type2 == HitPointType.TURNOUT_C) && (lt.getLayoutBlockC() != null)) {
621                        lb2 = lt.getLayoutBlockC();
622                    }
623                    if ((type2 == HitPointType.TURNOUT_D) && (lt.getLayoutBlockD() != null)) {
624                        lb2 = lt.getLayoutBlockD();
625                    }
626                }
627                if ((lb2 != null) && (lb1 != lb2)) {
628                    // have a block boundary, create a LayoutConnectivity
629                    log.debug("Block boundary  (''{}''<->''{}'') found at {}", lb1, lb2, this);
630                    lc = new LayoutConnectivity(lb1, lb2);
631                    lc.setConnections(this, lt, type2, null);
632                    lc.setDirection(models.computeDirection(
633                                        getConnect1(), type1,
634                                        getConnect2(), type2 ) );
635                    results.add(lc);
636                }
637            } else if (HitPointType.isLevelXingHitType(type2)) {
638                // have connection to a level crossing
639                LevelXing lx = (LevelXing) getConnect2();
640                if ((type2 == HitPointType.LEVEL_XING_A) || (type2 == HitPointType.LEVEL_XING_C)) {
641                    lb2 = lx.getLayoutBlockAC();
642                } else {
643                    lb2 = lx.getLayoutBlockBD();
644                }
645                if ((lb2 != null) && (lb1 != lb2)) {
646                    // have a block boundary, create a LayoutConnectivity
647                    log.debug("Block boundary  (''{}''<->''{}'') found at {}", lb1, lb2, this);
648                    lc = new LayoutConnectivity(lb1, lb2);
649                    lc.setConnections(this, lx, type2, null);
650                    lc.setDirection(models.computeDirection(
651                                        getConnect1(), type1,
652                                        getConnect2(), type2 ) );
653                    results.add(lc);
654                }
655            } else if (HitPointType.isSlipHitType(type2)) {
656                // have connection to a slip crossing
657                LayoutSlip ls = (LayoutSlip) getConnect2();
658                lb2 = ls.getLayoutBlock();
659                if ((lb2 != null) && (lb1 != lb2)) {
660                    // have a block boundary, create a LayoutConnectivity
661                    log.debug("Block boundary  (''{}''<->''{}'') found at {}", lb1, lb2, this);
662                    lc = new LayoutConnectivity(lb1, lb2);
663                    lc.setConnections(this, ls, type2, null);
664                    lc.setDirection(models.computeDirection(
665                                        getConnect1(), type1,
666                                        getConnect2(), type2 ) );
667                    results.add(lc);
668                }
669            }
670        }   // if (lb1 != null)
671        return results;
672    }   // getLayoutConnectivity()
673
674    /**
675     * {@inheritDoc}
676     */
677    @Override
678    public List<HitPointType> checkForFreeConnections() {
679        return new ArrayList<>();
680    }
681
682    /**
683     * {@inheritDoc}
684     */
685    @Override
686    public boolean checkForUnAssignedBlocks() {
687        return (getLayoutBlock() != null);
688    }
689
690    /**
691     * {@inheritDoc}
692     */
693    @Override
694    public void checkForNonContiguousBlocks(
695            @Nonnull HashMap<String, List<Set<String>>> blockNamesToTrackNameSetsMap) {
696        /*
697        * For each (non-null) blocks of this track do:
698        * #1) If it's got an entry in the blockNamesToTrackNameSetMap then
699        * #2) If this track is already in the TrackNameSet for this block
700        *     then return (done!)
701        * #3) else add a new set (with this block/track) to
702        *     blockNamesToTrackNameSetMap and
703        * #4) collect all the connections in this block
704        * <p>
705        *     Basically, we're maintaining contiguous track sets for each block found
706        *     (in blockNamesToTrackNameSetMap)
707         */
708        List<Set<String>> TrackNameSets = null;
709        Set<String> TrackNameSet = null;    // assume not found (pessimist!)
710        String blockName = getBlockName();
711        if (!blockName.isEmpty()) {
712            TrackNameSets = blockNamesToTrackNameSetsMap.get(blockName);
713            if (TrackNameSets != null) { //(#1)
714                for (Set<String> checkTrackNameSet : TrackNameSets) {
715                    if (checkTrackNameSet.contains(getName())) { //(#2)
716                        TrackNameSet = checkTrackNameSet;
717                        break;
718                    }
719                }
720            } else {    //(#3)
721                log.debug("*New block (''{}'') trackNameSets", blockName);
722                TrackNameSets = new ArrayList<>();
723                blockNamesToTrackNameSetsMap.put(blockName, TrackNameSets);
724            }
725            if (TrackNameSet == null) {
726                TrackNameSet = new LinkedHashSet<>();
727                TrackNameSets.add(TrackNameSet);
728            }
729            if (TrackNameSet.add(getName())) {
730                log.debug("*    Add track ''{}'' to TrackNameSets for block ''{}''", getName(), blockName);
731            }
732            //(#4)
733            if (connect1 != null) {
734                connect1.collectContiguousTracksNamesInBlockNamed(blockName, TrackNameSet);
735            }
736            if (connect2 != null) { //(#4)
737                connect2.collectContiguousTracksNamesInBlockNamed(blockName, TrackNameSet);
738            }
739        }
740    }
741
742    /**
743     * {@inheritDoc}
744     */
745    @Override
746    public void collectContiguousTracksNamesInBlockNamed(@Nonnull String blockName,
747            @Nonnull Set<String> TrackNameSet) {
748        if (!TrackNameSet.contains(getName())) {
749            // is this the blockName we're looking for?
750            if (getBlockName().equals(blockName)) {
751                // if we are added to the TrackNameSet
752                if (TrackNameSet.add(getName())) {
753                    log.debug("*    Add track ''{}''for block ''{}''", getName(), blockName);
754                }
755                // these should never be null... but just in case...
756                // it's time to play... flood your neighbours!
757                if (connect1 != null) {
758                    connect1.collectContiguousTracksNamesInBlockNamed(blockName, TrackNameSet);
759                }
760                if (connect2 != null) {
761                    connect2.collectContiguousTracksNamesInBlockNamed(blockName, TrackNameSet);
762                }
763            }
764        }
765    }
766
767    /**
768     * {@inheritDoc}
769     */
770    @Override
771    public void setAllLayoutBlocks(LayoutBlock layoutBlock) {
772        setLayoutBlock(layoutBlock);
773    }
774
775    /**
776     * {@inheritDoc}
777     */
778    @Override
779    public String getTypeName() {
780        return Bundle.getMessage("TypeName_TrackSegment");
781    }
782
783    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TrackSegment.class);
784}