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    @SuppressWarnings("deprecation")
323    // NOTE: findObjectByTypeAndName is @Deprecated;
324    // we're using it here for backwards compatibility until it can be removed
325    @Override
326    @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "Null check performed before using return value")
327    public void setObjects(LayoutEditor p) {
328
329        LayoutBlock lb;
330        if (!tLayoutBlockName.isEmpty()) {
331            lb = p.provideLayoutBlock(tLayoutBlockName);
332            if (lb != null) {
333                namedLayoutBlock = InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(lb.getUserName(), lb);
334                lb.incrementUse();
335            } else {
336                log.error("{}.setObjects(...); bad blockname '{}' in tracksegment {}",
337                        getName(), tLayoutBlockName, getName());
338                namedLayoutBlock = null;
339            }
340            tLayoutBlockName = null; //release this memory
341        }
342
343        connect1 = p.getFinder().findObjectByName(tConnect1Name);
344        connect2 = p.getFinder().findObjectByName(tConnect2Name);
345    }
346
347    public void updateBlockInfo() {
348        LayoutBlock layoutBlock = getLayoutBlock();
349        if (layoutBlock != null) {
350            layoutBlock.updatePaths();
351        }
352        LayoutBlock b1 = getBlock(connect1, type1);
353        if ((b1 != null) && (b1 != layoutBlock)) {
354            b1.updatePaths();
355        }
356        LayoutBlock b2 = getBlock(connect2, type2);
357        if ((b2 != null) && (b2 != layoutBlock) && (b2 != b1)) {
358            b2.updatePaths();
359        }
360
361        getConnect1().reCheckBlockBoundary();
362        getConnect2().reCheckBlockBoundary();
363    }
364
365    private LayoutBlock getBlock(LayoutTrack connect, HitPointType type) {
366        LayoutBlock result = null;
367        if (connect != null) {
368            if (type == HitPointType.POS_POINT) {
369                PositionablePoint p = (PositionablePoint) connect;
370                if (p.getConnect1() != this) {
371                    if (p.getConnect1() != null) {
372                        result = p.getConnect1().getLayoutBlock();
373                    }
374                } else {
375                    if (p.getConnect2() != null) {
376                        result = p.getConnect2().getLayoutBlock();
377                    }
378                }
379            } else {
380                result = models.getAffectedBlock(connect, type);
381            }
382        }
383        return result;
384    }
385
386    /**
387     * {@inheritDoc}
388     */
389    @Override
390    public boolean canRemove() {
391        List<String> itemList = new ArrayList<>();
392
393        HitPointType type1 = getType1();
394        LayoutTrack conn1 = getConnect1();
395        itemList.addAll(getPointReferences(type1, conn1));
396
397        HitPointType type2 = getType2();
398        LayoutTrack conn2 = getConnect2();
399        itemList.addAll(getPointReferences(type2, conn2));
400
401        if (!itemList.isEmpty()) {
402            models.displayRemoveWarning(this, itemList, "TrackSegment");  // NOI18N
403        }
404        return itemList.isEmpty();
405    }
406
407    public ArrayList<String> getPointReferences(HitPointType type, LayoutTrack conn) {
408        ArrayList<String> result = new ArrayList<>();
409
410        if (type == HitPointType.POS_POINT && conn instanceof PositionablePoint) {
411            PositionablePoint pt = (PositionablePoint) conn;
412            if (!pt.getEastBoundSignal().isEmpty()) {
413                result.add(pt.getEastBoundSignal());
414            }
415            if (!pt.getWestBoundSignal().isEmpty()) {
416                result.add(pt.getWestBoundSignal());
417            }
418            if (!pt.getEastBoundSignalMastName().isEmpty()) {
419                result.add(pt.getEastBoundSignalMastName());
420            }
421            if (!pt.getWestBoundSignalMastName().isEmpty()) {
422                result.add(pt.getWestBoundSignalMastName());
423            }
424            if (!pt.getEastBoundSensorName().isEmpty()) {
425                result.add(pt.getEastBoundSensorName());
426            }
427            if (!pt.getWestBoundSensorName().isEmpty()) {
428                result.add(pt.getWestBoundSensorName());
429            }
430            if (pt.getType() == PositionablePoint.PointType.EDGE_CONNECTOR && pt.getLinkedPoint() != null) {
431                result.add(Bundle.getMessage("DeleteECisActive"));   // NOI18N
432            }
433        }
434
435        if (HitPointType.isTurnoutHitType(type) && conn instanceof LayoutTurnout) {
436            LayoutTurnout lt = (LayoutTurnout) conn;
437            switch (type) {
438                case TURNOUT_A: {
439                    result = lt.getBeanReferences("A");  // NOI18N
440                    break;
441                }
442                case TURNOUT_B: {
443                    result = lt.getBeanReferences("B");  // NOI18N
444                    break;
445                }
446                case TURNOUT_C: {
447                    result = lt.getBeanReferences("C");  // NOI18N
448                    break;
449                }
450                case TURNOUT_D: {
451                    result = lt.getBeanReferences("D");  // NOI18N
452                    break;
453                }
454                default: {
455                    log.error("Unexpected HitPointType: {}", type);
456                }
457            }
458        }
459
460        if (HitPointType.isLevelXingHitType(type) && conn instanceof LevelXing) {
461            LevelXing lx = (LevelXing) conn;
462            switch (type) {
463                case LEVEL_XING_A: {
464                    result = lx.getBeanReferences("A");  // NOI18N
465                    break;
466                }
467                case LEVEL_XING_B: {
468                    result = lx.getBeanReferences("B");  // NOI18N
469                    break;
470                }
471                case LEVEL_XING_C: {
472                    result = lx.getBeanReferences("C");  // NOI18N
473                    break;
474                }
475                case LEVEL_XING_D: {
476                    result = lx.getBeanReferences("D");  // NOI18N
477                    break;
478                }
479                default: {
480                    log.error("Unexpected HitPointType: {}", type);
481                }
482            }
483        }
484
485        if (HitPointType.isSlipHitType(type) && conn instanceof LayoutSlip) {
486            LayoutSlip ls = (LayoutSlip) conn;
487            switch (type) {
488                case SLIP_A: {
489                    result = ls.getBeanReferences("A");  // NOI18N
490                    break;
491                }
492                case SLIP_B: {
493                    result = ls.getBeanReferences("B");  // NOI18N
494                    break;
495                }
496                case SLIP_C: {
497                    result = ls.getBeanReferences("C");  // NOI18N
498                    break;
499                }
500                case SLIP_D: {
501                    result = ls.getBeanReferences("D");  // NOI18N
502                    break;
503                }
504                default: {
505                    log.error("Unexpected HitPointType: {}", type);
506                }
507            }
508        }
509
510        return result;
511    }
512
513    /**
514     * Remove this object from display and persistance.
515     */
516    public void remove() {
517        // remove from persistance by flagging inactive
518        active = false;
519    }
520
521    private boolean active = true;
522
523    /**
524     * Get state. "active" means that the object is still displayed, and should
525     * be stored.
526     *
527     * @return true if still displayed, else false.
528     */
529    public boolean isActive() {
530        return active;
531    }
532
533    /**
534     * temporary fill of abstract from above
535     */
536    @Override
537    public void reCheckBlockBoundary() {
538        log.info("reCheckBlockBoundary is temporary, but was invoked", new Exception("traceback"));
539    }
540
541
542    /**
543     * {@inheritDoc}
544     */
545    @Override
546    protected List<LayoutConnectivity> getLayoutConnectivity() {
547        List<LayoutConnectivity> results = new ArrayList<>();
548
549        LayoutConnectivity lc = null;
550        LayoutBlock lb1 = getLayoutBlock(), lb2 = null;
551        // ensure that block is assigned
552        if (lb1 != null) {
553            // check first connection for turnout
554            if (HitPointType.isTurnoutHitType(type1)) {
555                // have connection to a turnout, is block different
556                LayoutTurnout lt = (LayoutTurnout) getConnect1();
557                lb2 = lt.getLayoutBlock();
558                if (lt.hasEnteringDoubleTrack()) {
559                    // not RH, LH, or WYE turnout - other blocks possible
560                    if ((type1 == HitPointType.TURNOUT_B) && (lt.getLayoutBlockB() != null)) {
561                        lb2 = lt.getLayoutBlockB();
562                    }
563                    if ((type1 == HitPointType.TURNOUT_C) && (lt.getLayoutBlockC() != null)) {
564                        lb2 = lt.getLayoutBlockC();
565                    }
566                    if ((type1 == HitPointType.TURNOUT_D) && (lt.getLayoutBlockD() != null)) {
567                        lb2 = lt.getLayoutBlockD();
568                    }
569                }
570                if ((lb2 != null) && (lb1 != lb2)) {
571                    // have a block boundary, create a LayoutConnectivity
572                    log.debug("Block boundary  (''{}''<->''{}'') found at {}", lb1, lb2, this);
573                    lc = new LayoutConnectivity(lb1, lb2);
574                    lc.setConnections(this, lt, type1, null);
575                    lc.setDirection(models.computeDirection(
576                                        getConnect2(), type2,
577                                        getConnect1(), type1 ) );
578                    results.add(lc);
579                }
580            } else if (HitPointType.isLevelXingHitType(type1)) {
581                // have connection to a level crossing
582                LevelXing lx = (LevelXing) getConnect1();
583                if ((type1 == HitPointType.LEVEL_XING_A) || (type1 == HitPointType.LEVEL_XING_C)) {
584                    lb2 = lx.getLayoutBlockAC();
585                } else {
586                    lb2 = lx.getLayoutBlockBD();
587                }
588                if ((lb2 != null) && (lb1 != lb2)) {
589                    // have a block boundary, create a LayoutConnectivity
590                    log.debug("Block boundary  (''{}''<->''{}'') found at {}", lb1, lb2, this);
591                    lc = new LayoutConnectivity(lb1, lb2);
592                    lc.setConnections(this, lx, type1, null);
593                    lc.setDirection(models.computeDirection(
594                                        getConnect2(), type2,
595                                        getConnect1(), type1 ) );
596                    results.add(lc);
597                }
598            } else if (HitPointType.isSlipHitType(type1)) {
599                // have connection to a slip crossing
600                LayoutSlip ls = (LayoutSlip) getConnect1();
601                lb2 = ls.getLayoutBlock();
602                if ((lb2 != null) && (lb1 != lb2)) {
603                    // have a block boundary, create a LayoutConnectivity
604                    log.debug("Block boundary  (''{}''<->''{}'') found at {}", lb1, lb2, this);
605                    lc = new LayoutConnectivity(lb1, lb2);
606                    lc.setConnections(this, ls, type1, null);
607                    lc.setDirection(models.computeDirection(
608                                        getConnect2(), type2,
609                                        getConnect1(), type1 ) );
610                    results.add(lc);
611                }
612            }
613            // check second connection for turnout
614            if (HitPointType.isTurnoutHitType(type2)) {
615                // have connection to a turnout
616                LayoutTurnout lt = (LayoutTurnout) getConnect2();
617                lb2 = lt.getLayoutBlock();
618                if (lt.hasEnteringDoubleTrack()) {
619                    // not RH, LH, or WYE turnout - other blocks possible
620                    if ((type2 == HitPointType.TURNOUT_B) && (lt.getLayoutBlockB() != null)) {
621                        lb2 = lt.getLayoutBlockB();
622                    }
623                    if ((type2 == HitPointType.TURNOUT_C) && (lt.getLayoutBlockC() != null)) {
624                        lb2 = lt.getLayoutBlockC();
625                    }
626                    if ((type2 == HitPointType.TURNOUT_D) && (lt.getLayoutBlockD() != null)) {
627                        lb2 = lt.getLayoutBlockD();
628                    }
629                }
630                if ((lb2 != null) && (lb1 != lb2)) {
631                    // have a block boundary, create a LayoutConnectivity
632                    log.debug("Block boundary  (''{}''<->''{}'') found at {}", lb1, lb2, this);
633                    lc = new LayoutConnectivity(lb1, lb2);
634                    lc.setConnections(this, lt, type2, null);
635                    lc.setDirection(models.computeDirection(
636                                        getConnect1(), type1,
637                                        getConnect2(), type2 ) );
638                    results.add(lc);
639                }
640            } else if (HitPointType.isLevelXingHitType(type2)) {
641                // have connection to a level crossing
642                LevelXing lx = (LevelXing) getConnect2();
643                if ((type2 == HitPointType.LEVEL_XING_A) || (type2 == HitPointType.LEVEL_XING_C)) {
644                    lb2 = lx.getLayoutBlockAC();
645                } else {
646                    lb2 = lx.getLayoutBlockBD();
647                }
648                if ((lb2 != null) && (lb1 != lb2)) {
649                    // have a block boundary, create a LayoutConnectivity
650                    log.debug("Block boundary  (''{}''<->''{}'') found at {}", lb1, lb2, this);
651                    lc = new LayoutConnectivity(lb1, lb2);
652                    lc.setConnections(this, lx, type2, null);
653                    lc.setDirection(models.computeDirection(
654                                        getConnect1(), type1,
655                                        getConnect2(), type2 ) );
656                    results.add(lc);
657                }
658            } else if (HitPointType.isSlipHitType(type2)) {
659                // have connection to a slip crossing
660                LayoutSlip ls = (LayoutSlip) getConnect2();
661                lb2 = ls.getLayoutBlock();
662                if ((lb2 != null) && (lb1 != lb2)) {
663                    // have a block boundary, create a LayoutConnectivity
664                    log.debug("Block boundary  (''{}''<->''{}'') found at {}", lb1, lb2, this);
665                    lc = new LayoutConnectivity(lb1, lb2);
666                    lc.setConnections(this, ls, type2, null);
667                    lc.setDirection(models.computeDirection(
668                                        getConnect1(), type1,
669                                        getConnect2(), type2 ) );
670                    results.add(lc);
671                }
672            }
673        }   // if (lb1 != null)
674        return results;
675    }   // getLayoutConnectivity()
676
677    /**
678     * {@inheritDoc}
679     */
680    @Override
681    public List<HitPointType> checkForFreeConnections() {
682        return new ArrayList<>();
683    }
684
685    /**
686     * {@inheritDoc}
687     */
688    @Override
689    public boolean checkForUnAssignedBlocks() {
690        return (getLayoutBlock() != null);
691    }
692
693    /**
694     * {@inheritDoc}
695     */
696    @Override
697    public void checkForNonContiguousBlocks(
698            @Nonnull HashMap<String, List<Set<String>>> blockNamesToTrackNameSetsMap) {
699        /*
700        * For each (non-null) blocks of this track do:
701        * #1) If it's got an entry in the blockNamesToTrackNameSetMap then
702        * #2) If this track is already in the TrackNameSet for this block
703        *     then return (done!)
704        * #3) else add a new set (with this block/track) to
705        *     blockNamesToTrackNameSetMap and
706        * #4) collect all the connections in this block
707        * <p>
708        *     Basically, we're maintaining contiguous track sets for each block found
709        *     (in blockNamesToTrackNameSetMap)
710         */
711        List<Set<String>> TrackNameSets = null;
712        Set<String> TrackNameSet = null;    // assume not found (pessimist!)
713        String blockName = getBlockName();
714        if (!blockName.isEmpty()) {
715            TrackNameSets = blockNamesToTrackNameSetsMap.get(blockName);
716            if (TrackNameSets != null) { //(#1)
717                for (Set<String> checkTrackNameSet : TrackNameSets) {
718                    if (checkTrackNameSet.contains(getName())) { //(#2)
719                        TrackNameSet = checkTrackNameSet;
720                        break;
721                    }
722                }
723            } else {    //(#3)
724                log.debug("*New block (''{}'') trackNameSets", blockName);
725                TrackNameSets = new ArrayList<>();
726                blockNamesToTrackNameSetsMap.put(blockName, TrackNameSets);
727            }
728            if (TrackNameSet == null) {
729                TrackNameSet = new LinkedHashSet<>();
730                TrackNameSets.add(TrackNameSet);
731            }
732            if (TrackNameSet.add(getName())) {
733                log.debug("*    Add track ''{}'' to TrackNameSets for block ''{}''", getName(), blockName);
734            }
735            //(#4)
736            if (connect1 != null) {
737                connect1.collectContiguousTracksNamesInBlockNamed(blockName, TrackNameSet);
738            }
739            if (connect2 != null) { //(#4)
740                connect2.collectContiguousTracksNamesInBlockNamed(blockName, TrackNameSet);
741            }
742        }
743    }
744
745    /**
746     * {@inheritDoc}
747     */
748    @Override
749    public void collectContiguousTracksNamesInBlockNamed(@Nonnull String blockName,
750            @Nonnull Set<String> TrackNameSet) {
751        if (!TrackNameSet.contains(getName())) {
752            // is this the blockName we're looking for?
753            if (getBlockName().equals(blockName)) {
754                // if we are added to the TrackNameSet
755                if (TrackNameSet.add(getName())) {
756                    log.debug("*    Add track ''{}''for block ''{}''", getName(), blockName);
757                }
758                // these should never be null... but just in case...
759                // it's time to play... flood your neighbours!
760                if (connect1 != null) {
761                    connect1.collectContiguousTracksNamesInBlockNamed(blockName, TrackNameSet);
762                }
763                if (connect2 != null) {
764                    connect2.collectContiguousTracksNamesInBlockNamed(blockName, TrackNameSet);
765                }
766            }
767        }
768    }
769
770    /**
771     * {@inheritDoc}
772     */
773    @Override
774    public void setAllLayoutBlocks(LayoutBlock layoutBlock) {
775        setLayoutBlock(layoutBlock);
776    }
777
778    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TrackSegment.class);
779}