001package jmri.jmrit.display.layoutEditor;
002
003import java.awt.Color;
004import java.awt.Component;
005import java.awt.event.ActionEvent;
006import java.beans.*;
007import java.util.*;
008
009import javax.annotation.CheckForNull;
010import javax.annotation.Nonnull;
011import javax.swing.*;
012import javax.swing.colorchooser.AbstractColorChooserPanel;
013
014import jmri.*;
015import jmri.implementation.AbstractNamedBean;
016import jmri.jmrit.beantable.beanedit.*;
017import jmri.jmrit.roster.RosterEntry;
018import jmri.swing.NamedBeanComboBox;
019import jmri.util.MathUtil;
020import jmri.util.swing.JmriColorChooser;
021import jmri.util.swing.JmriJOptionPane;
022import jmri.util.swing.SplitButtonColorChooserPanel;
023
024import org.slf4j.Logger;
025import org.slf4j.LoggerFactory;
026import org.slf4j.MDC;
027
028/**
029 * A LayoutBlock is a group of track segments and turnouts on a LayoutEditor
030 * panel corresponding to a 'block'. LayoutBlock is a LayoutEditor specific
031 * extension of the JMRI Block object.
032 * <p>
033 * LayoutBlocks may have an occupancy Sensor. The getOccupancy method returns
034 * the occupancy state of the LayoutBlock - OCCUPIED, EMPTY, or UNKNOWN. If no
035 * occupancy sensor is provided, UNKNOWN is returned. The occupancy sensor if
036 * there is one, is the same as the occupancy sensor of the corresponding JMRI
037 * Block.
038 * <p>
039 * The name of each Layout Block is the same as that of the corresponding block
040 * as defined in Layout Editor. A corresponding JMRI Block object is created
041 * when a LayoutBlock is created. The JMRI Block uses the name of the block
042 * defined in Layout Editor as its user name and a unique IBnnn system name. The
043 * JMRI Block object and its associated Path objects are useful in tracking a
044 * train around the layout. Blocks may be viewed in the Block Table.
045 * <p>
046 * A LayoutBlock may have an associated Memory object. This Memory object
047 * contains a string representing the current "value" of the corresponding JMRI
048 * Block object. If the value contains a train name, for example, displaying
049 * Memory objects associated with LayoutBlocks, and displayed near each Layout
050 * Block can follow a train around the layout, displaying its name when it is in
051 * the LayoutBlock.
052 * <p>
053 * LayoutBlocks are "cross-panel", similar to sensors and turnouts. A
054 * LayoutBlock may be used by more than one Layout Editor panel simultaneously.
055 * As a consequence, LayoutBlocks are saved with the configuration, not with a
056 * panel.
057 * <p>
058 * LayoutBlocks are used by TrackSegments, LevelXings, and LayoutTurnouts.
059 * LevelXings carry two LayoutBlock designations, which may be the same.
060 * LayoutTurnouts carry LayoutBlock designations also, one per turnout, except
061 * for double crossovers and slips which can have up to four.
062 * <p>
063 * LayoutBlocks carry a use count. The use count counts the number of track
064 * segments, layout turnouts, and levelcrossings which use the LayoutBlock. Only
065 * LayoutBlocks which have a use count greater than zero are saved when the
066 * configuration is saved.
067 *
068 * @author Dave Duchamp Copyright (c) 2004-2008
069 * @author George Warner Copyright (c) 2017-2019
070 */
071public class LayoutBlock extends AbstractNamedBean implements PropertyChangeListener {
072
073    // identifies routing packets for logging; packets are never dropped by ID
074    private static final java.util.concurrent.atomic.AtomicInteger lastPacketID =
075            new java.util.concurrent.atomic.AtomicInteger();
076
077    @Deprecated (since="5.11.2",forRemoval=true) // please use the SLF4J categories.
078    public void enableDeleteRouteLog() {
079        jmri.util.LoggingUtil.warnOnce( log, "Deprecated, please use the SLF4J categories");
080    }
081
082    @Deprecated (since="5.11.2",forRemoval=true) // please use the SLF4J categories.
083    public void disableDeleteRouteLog() {
084        jmri.util.LoggingUtil.warnOnce( log, "Deprecated, please use the SLF4J categories");
085    }
086
087    // constants
088    public static final int OCCUPIED = Block.OCCUPIED;
089    public static final int EMPTY = Block.UNOCCUPIED;
090
091    /**
092     * String property constant for redraw.
093     */
094    public static final String PROPERTY_REDRAW = "redraw";
095
096    /**
097     * String property constant for routing.
098     */
099    public static final String PROPERTY_ROUTING = "routing";
100
101    /**
102     * String property constant for path.
103     */
104    public static final String PROPERTY_PATH = "path";
105
106    /**
107     * String property constant for through path added.
108     */
109    public static final String PROPERTY_THROUGH_PATH_ADDED = "through-path-added";
110
111    /**
112     * String property constant for through path removed.
113     */
114    public static final String PROPERTY_THROUGH_PATH_REMOVED = "through-path-removed";
115
116    /**
117     * String property constant for neighbour packet flow.
118     */
119    public static final String PROPERTY_NEIGHBOUR_PACKET_FLOW = "neighbourpacketflow";
120
121    /**
122     * String property constant for neighbour metric.
123     */
124    public static final String PROPERTY_NEIGHBOUR_METRIC = "neighbourmetric";
125
126    /**
127     * String property constant for neighbour length.
128     */
129    public static final String PROPERTY_NEIGHBOUR_LENGTH = "neighbourlength";
130
131    /**
132     * String property constant for valid.
133     */
134    public static final String PROPERTY_VALID = "valid";
135
136    /**
137     * String property constant for length.
138     */
139    public static final String PROPERTY_LENGTH = "length";
140
141    /**
142     * String property constant for hop.
143     */
144    public static final String PROPERTY_HOP = "hop";
145
146    /**
147     * String property constant for metric.
148     */
149    public static final String PROPERTY_METRIC = "metric";
150
151    // operational instance variables (not saved to disk)
152    private int useCount = 0;
153    private NamedBeanHandle<Sensor> occupancyNamedSensor = null;
154    private NamedBeanHandle<Memory> namedMemory = null;
155    private boolean setSensorFromBlockEnabled = true;     // Controls whether getOccupancySensor should get the sensor from the block
156
157    private Block block = null;
158
159    private final List<LayoutEditor> panels = new ArrayList<>(); // panels using this block
160    private PropertyChangeListener mBlockListener = null;
161    private int jmriblknum = 1;
162    private boolean useExtraColor = false;
163    private boolean suppressNameUpdate = false;
164
165    // persistent instances variables (saved between sessions)
166    private String occupancySensorName = "";
167    private String memoryName = "";
168    private int occupiedSense = Sensor.ACTIVE;
169    private Color blockTrackColor = Color.darkGray;
170    private Color blockOccupiedColor = Color.red;
171    private Color blockExtraColor = Color.white;
172
173    /**
174     * Creates a LayoutBlock object.
175     *
176     * Note: initializeLayoutBlock() must be called to complete the process. They are split
177     *       so  that loading of panel files will be independent of whether LayoutBlocks or
178     *       Blocks are loaded first.
179     * @param sName System name of this LayoutBlock
180     * @param uName User name of this LayoutBlock but also the user name of the associated Block
181     */
182    public LayoutBlock(String sName, String uName) {
183        super(sName, uName);
184    }
185
186    /**
187     * Completes the creation of a LayoutBlock object by adding a Block to it.
188     *
189     * The block create process takes into account that the _bean register
190     * process considers IB1 and IB01 to be the same name which results in a
191     * silent failure.
192     */
193    public void initializeLayoutBlock() {
194        // get/create a Block object corresponding to this LayoutBlock
195        block = null;   // assume failure (pessimist!)
196        String userName = getUserName();
197        if ((userName != null) && !userName.isEmpty()) {
198            block = InstanceManager.getDefault(BlockManager.class).getByUserName(userName);
199        }
200
201        if (block == null) {
202            // Not found, create a new Block
203            BlockManager bm = InstanceManager.getDefault(BlockManager.class);
204            String s;
205            while (true) {
206                if (jmriblknum > 50000) {
207                    throw new IndexOutOfBoundsException("Run away prevented while trying to create a block");
208                }
209                s = "IB" + jmriblknum;
210                jmriblknum++;
211
212                // Find an unused system name
213                block = bm.getBySystemName(s);
214                if (block != null) {
215                    log.debug("System name is already used: {}", s);
216                    continue;
217                }
218
219                // Create a new block.  User name is null to prevent user name checking.
220                block = bm.createNewBlock(s, null);
221                if (block == null) {
222                    log.debug("Null block returned: {}", s);
223                    continue;
224                }
225
226                // Verify registration
227                Block testGet = bm.getBySystemName(s);
228                if ( testGet!=null && bm.getNamedBeanSet().contains(testGet) ) {
229                    log.debug("Block is valid: {}", s);
230                    break;
231                }
232                log.debug("Registration failed: {}", s);
233            }
234            block.setUserName(getUserName());
235        }
236
237        // attach a listener for changes in the Block
238        mBlockListener = this::handleBlockChange;
239        block.addPropertyChangeListener(mBlockListener,
240                getUserName(), "Layout Block:" + getUserName());
241        if (occupancyNamedSensor != null) {
242            block.setNamedSensor(occupancyNamedSensor);
243        }
244    }
245
246    /* initializeLayoutBlockRouting */
247    public void initializeLayoutBlockRouting() {
248        // send the block status again with the next routing update
249        lastRoutingStatus = -1;
250        if (!InstanceManager.getDefault(LayoutBlockManager.class).isAdvancedRoutingEnabled()) {
251            return;
252        }
253        setBlockMetric();
254
255        block.getPaths().stream().forEach(this::addAdjacency);
256    }
257
258    /*
259     * Accessor methods
260     */
261    // TODO: deprecate and just use getUserName() directly
262    public String getId() {
263        return getUserName();
264    }
265
266    public Color getBlockTrackColor() {
267        return blockTrackColor;
268    }
269
270    public void setBlockTrackColor(Color color) {
271        blockTrackColor = color;
272        JmriColorChooser.addRecentColor(color);
273    }
274
275    public Color getBlockOccupiedColor() {
276        return blockOccupiedColor;
277    }
278
279    public void setBlockOccupiedColor(Color color) {
280        blockOccupiedColor = color;
281        JmriColorChooser.addRecentColor(color);
282    }
283
284    public Color getBlockExtraColor() {
285        return blockExtraColor;
286    }
287
288    public void setBlockExtraColor(Color color) {
289        blockExtraColor = color;
290        JmriColorChooser.addRecentColor(color);
291    }
292
293    // TODO: Java standard pattern for boolean getters is "useExtraColor()"
294    public boolean getUseExtraColor() {
295        return useExtraColor;
296    }
297
298    public void setUseExtraColor(boolean b) {
299        useExtraColor = b;
300
301        if (InstanceManager.getDefault(LayoutBlockManager.class).isAdvancedRoutingEnabled()) {
302            stateUpdateIfChanged();
303        }
304        if (getBlock() != null) {
305            getBlock().setAllocated(b);
306        }
307    }
308
309    /* setUseExtraColor */
310    public void incrementUse() {
311        useCount++;
312    }
313
314    public void decrementUse() {
315        --useCount;
316        if (useCount <= 0) {
317            useCount = 0;
318        }
319    }
320
321    public int getUseCount() {
322        return useCount;
323    }
324
325    /**
326     * Keep track of LayoutEditor panels that are using this LayoutBlock.
327     *
328     * @param panel to keep track of
329     */
330    public void addLayoutEditor(LayoutEditor panel) {
331        // add to the panels list if not already there
332        if (!panels.contains(panel)) {
333            panels.add(panel);
334        }
335    }
336
337    public void deleteLayoutEditor(LayoutEditor panel) {
338        // remove from the panels list if there
339        if (panels.contains(panel)) {
340            panels.remove(panel);
341        }
342    }
343
344    public boolean isOnPanel(LayoutEditor panel) {
345        // returns true if this Layout Block is used on panel
346        return panels.contains(panel);
347    }
348
349    /**
350     * Redraw panels using this layout block.
351     */
352    public void redrawLayoutBlockPanels() {
353        panels.stream().forEach(LayoutEditor::redrawPanel);
354        firePropertyChange(PROPERTY_REDRAW, null, null);
355    }
356
357    /**
358     * Validate that the supplied occupancy sensor name corresponds to an
359     * existing sensor and is unique among all blocks. If valid, returns the
360     * sensor and sets the block sensor name in the block. Else returns null,
361     * and does nothing to the block.
362     *
363     * @param sensorName to check
364     * @param openFrame  determines the <code>Frame</code> in which the dialog
365     *                   is displayed; if <code>null</code>, or if the
366     *                   <code>parentComponent</code> has no <code>Frame</code>,
367     *                   a default <code>Frame</code> is used
368     * @return the validated sensor
369     */
370    public Sensor validateSensor(String sensorName, Component openFrame) {
371        // check if anything entered
372        if ((sensorName == null) || sensorName.isEmpty()) {
373            // no sensor name entered
374            if (occupancyNamedSensor != null) {
375                setOccupancySensorName(null);
376            }
377            return null;
378        }
379
380        // get the sensor corresponding to this name
381        Sensor s = InstanceManager.sensorManagerInstance().getSensor(sensorName);
382        if (s == null) {
383            // There is no sensor corresponding to this name
384            JmriJOptionPane.showMessageDialog(openFrame,
385                    java.text.MessageFormat.format(Bundle.getMessage("Error7"),
386                            new Object[]{sensorName}),
387                    Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
388            return null;
389        }
390
391        // ensure that this sensor is unique among defined Layout Blocks
392        NamedBeanHandle<Sensor> savedNamedSensor = occupancyNamedSensor;
393        occupancyNamedSensor = null;
394        LayoutBlock b = InstanceManager.getDefault(LayoutBlockManager.class).
395                getBlockWithSensorAssigned(s);
396
397        if (b != this) {
398            if (b != null) {
399                if (b.getUseCount() > 0) {
400                    // new sensor is not unique, return to the old one
401                    occupancyNamedSensor = savedNamedSensor;
402                    JmriJOptionPane.showMessageDialog(openFrame,
403                        Bundle.getMessage("Error6", sensorName, b.getId()),
404                        Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
405                    return null;
406                } else {
407                    // the user is assigning a sensor which is already assigned to
408                    // layout block b. Layout block b is no longer in use so this
409                    // should be fine but it's technically possible to put
410                    // this discarded layout block back into service (possibly
411                    // by mistake) by entering its name in any edit layout block window.
412                    // That would cause a problem with the sensor being in use in
413                    // two active blocks, so as a precaution we remove the sensor
414                    // from the discarded block here.
415                    b.setOccupancySensorName(null);
416                }
417            }
418            // sensor is unique, or was only in use on a layout block not in use
419            setOccupancySensorName(sensorName);
420        }
421        return s;
422    }
423
424    /**
425     * Validate that the memory name corresponds to an existing memory. If
426     * valid, returns the memory. Else returns null, and notifies the user.
427     *
428     * @param memName   the memory name
429     * @param openFrame the frame to display any error dialog in
430     * @return the memory
431     */
432    public Memory validateMemory(String memName, Component openFrame) {
433        // check if anything entered
434        if ((memName == null) || memName.isEmpty()) {
435            // no memory entered
436            return null;
437        }
438        // get the memory corresponding to this name
439        Memory m = InstanceManager.memoryManagerInstance().getMemory(memName);
440        if (m == null) {
441            // There is no memory corresponding to this name
442            JmriJOptionPane.showMessageDialog(openFrame,
443                    java.text.MessageFormat.format(Bundle.getMessage("Error16"),
444                            new Object[]{memName}),
445                    Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
446            return null;
447        }
448        memoryName = memName;
449
450        // Go through the memory icons on the panel and see if any are linked to this layout block
451        if ((m != getMemory()) && (!panels.isEmpty())) {
452            boolean updateall = false;
453            boolean found = false;
454            for (LayoutEditor panel : panels) {
455                for (MemoryIcon memIcon : panel.getMemoryLabelList()) {
456                    if (memIcon.getLayoutBlock() == this) {
457                        if (!updateall && !found) {
458                            int n = JmriJOptionPane.showConfirmDialog(
459                                    openFrame,
460                                    "Would you like to update all memory icons on the panel linked to the block to use the new one?",
461                                    "Update Memory Icons",
462                                    JmriJOptionPane.YES_NO_OPTION);
463                            // TODO I18N in Bundle.properties
464                            found = true;
465                            if (n == JmriJOptionPane.YES_OPTION ) {
466                                updateall = true;
467                            }
468                        }
469                        if (updateall) {
470                            memIcon.setMemory(memoryName);
471                        }
472                    }
473                }
474            }
475        }
476        return m;
477    }
478
479    /**
480     * Get the color for drawing items in this block. Returns color based on
481     * block occupancy.
482     *
483     * @return color for block
484     */
485    public Color getBlockColor() {
486        if (getOccupancy() == OCCUPIED) {
487            return blockOccupiedColor;
488        } else if (useExtraColor) {
489            return blockExtraColor;
490        } else {
491            return blockTrackColor;
492        }
493    }
494
495    /**
496     * Get the Block corresponding to this LayoutBlock.
497     *
498     * @return block
499     */
500    public Block getBlock() {
501        return block;
502    }
503
504    /**
505     * Returns Memory name
506     *
507     * @return name of memory
508     */
509    public String getMemoryName() {
510        if (namedMemory != null) {
511            return namedMemory.getName();
512        }
513        return memoryName;
514    }
515
516    /**
517     * Get Memory.
518     *
519     * @return memory bean
520     */
521    public Memory getMemory() {
522        if (namedMemory == null) {
523            setMemoryName(memoryName);
524        }
525        if (namedMemory != null) {
526            return namedMemory.getBean();
527        }
528        return null;
529    }
530
531    /**
532     * Add Memory by name.
533     *
534     * @param name for memory
535     */
536    public void setMemoryName(String name) {
537        if ((name == null) || name.isEmpty()) {
538            namedMemory = null;
539            memoryName = "";
540            return;
541        }
542        memoryName = name;
543        Memory memory = InstanceManager.memoryManagerInstance().getMemory(name);
544        if (memory != null) {
545            namedMemory = InstanceManager.getDefault(NamedBeanHandleManager.class).getNamedBeanHandle(name, memory);
546        }
547    }
548
549    public void setMemory(Memory m, String name) {
550        if (m == null) {
551            namedMemory = null;
552            memoryName = name == null ? "" : name;
553            return;
554        }
555        namedMemory = InstanceManager.getDefault(NamedBeanHandleManager.class).getNamedBeanHandle(name, m);
556    }
557
558    /**
559     * Get occupancy Sensor name.
560     *
561     * @return name of occupancy sensor
562     */
563    public String getOccupancySensorName() {
564        if (occupancyNamedSensor == null) {
565            if (block != null) {
566                occupancyNamedSensor = block.getNamedSensor();
567            }
568        }
569        if (occupancyNamedSensor != null) {
570            return occupancyNamedSensor.getName();
571        }
572        return occupancySensorName;
573    }
574
575    /**
576     * Get occupancy Sensor.
577     * <p>
578     * If a sensor has not been assigned, try getting the sensor from the related
579     * block.
580     * <p>
581     * When setting the layout block sensor from the block itself using the OccupancySensorChange
582     * event, the automatic assignment has to be disabled for the sensor checking performed by
583     * {@link jmri.jmrit.display.layoutEditor.LayoutBlockManager#getBlockWithSensorAssigned}
584     *
585     * @return occupancy sensor or null
586     */
587    public Sensor getOccupancySensor() {
588        if (occupancyNamedSensor == null && setSensorFromBlockEnabled) {
589            if (block != null) {
590                occupancyNamedSensor = block.getNamedSensor();
591            }
592        }
593        if (occupancyNamedSensor != null) {
594            return occupancyNamedSensor.getBean();
595        }
596        return null;
597    }
598
599    /**
600     * Add occupancy sensor by name.
601     *
602     * @param name for senor to add
603     */
604    public void setOccupancySensorName(String name) {
605        if ((name == null) || name.isEmpty()) {
606            if (occupancyNamedSensor != null) {
607                occupancyNamedSensor.getBean().removePropertyChangeListener(mBlockListener);
608            }
609            occupancyNamedSensor = null;
610            occupancySensorName = "";
611
612            if (block != null) {
613                block.setNamedSensor(null);
614            }
615            return;
616        }
617        occupancySensorName = name;
618        Sensor sensor = InstanceManager.sensorManagerInstance().getSensor(name);
619        if (sensor != null) {
620            occupancyNamedSensor = InstanceManager.getDefault(
621                    NamedBeanHandleManager.class).getNamedBeanHandle(name, sensor);
622            if (block != null) {
623                block.setNamedSensor(occupancyNamedSensor);
624            }
625        }
626    }
627
628    /**
629     * Get occupied sensor state.
630     *
631     * @return occupied sensor state, defaults to Sensor.ACTIVE
632     */
633    public int getOccupiedSense() {
634        return occupiedSense;
635    }
636
637    /**
638     * Set occupied sensor state.
639     *
640     * @param sense eg. Sensor.INACTIVE
641     */
642    public void setOccupiedSense(int sense) {
643        occupiedSense = sense;
644    }
645
646    /**
647     * Test block occupancy.
648     *
649     * @return occupancy state
650     */
651    public int getOccupancy() {
652        if (occupancyNamedSensor == null) {
653            Sensor s = null;
654            if (!occupancySensorName.isEmpty()) {
655                s = InstanceManager.sensorManagerInstance().getSensor(occupancySensorName);
656            }
657            if (s == null) {
658                // no occupancy sensor, so base upon block occupancy state
659                if (block != null) {
660                    return block.getState();
661                }
662                // if no block or sensor return unknown
663                return UNKNOWN;
664            }
665            occupancyNamedSensor = InstanceManager.getDefault(
666                    NamedBeanHandleManager.class).getNamedBeanHandle(occupancySensorName, s);
667            if (block != null) {
668                block.setNamedSensor(occupancyNamedSensor);
669            }
670        }
671
672        Sensor s = getOccupancySensor();
673        if ( s == null) {
674            return UNKNOWN;
675        }
676
677        if (s.getKnownState() != occupiedSense) {
678            return EMPTY;
679        } else if (s.getKnownState() == occupiedSense) {
680            return OCCUPIED;
681        }
682        return UNKNOWN;
683    }
684
685    @Override
686    public int getState() {
687        return getOccupancy();
688    }
689
690    /**
691     * Does nothing, do not use.Dummy for completion of NamedBean interface
692     * @param i does nothing
693     */
694    @Override
695    public void setState(int i) {
696        log.error("this state does nothing {}", getDisplayName());
697    }
698
699    /**
700     * Get the panel with the highest connectivity to this Layout Block.
701     *
702     * @return panel with most connections to this block
703     */
704    public LayoutEditor getMaxConnectedPanel() {
705        LayoutEditor result = null;
706        // a block is attached and this LayoutBlock is used
707        if ((block != null) && (!panels.isEmpty())) {
708            // initialize connectivity as defined in first Layout Editor panel
709            int maxConnectivity = Integer.MIN_VALUE;
710            for (LayoutEditor panel : panels) {
711                List<LayoutConnectivity> c = panel.getLEAuxTools().getConnectivityList(this);
712                if (maxConnectivity < c.size()) {
713                    maxConnectivity = c.size();
714                    result = panel;
715                }
716            }
717        }
718        return result;
719    }
720
721    /**
722     * Check/Update Path objects for the attached Block
723     * <p>
724     * If multiple panels are present, Paths are set according to the panel with
725     * the highest connectivity (most LayoutConnectivity objects).
726     */
727    public void updatePaths() {
728        // Update paths is called by the panel, turnouts, xings, track segments etc
729        if ((block != null) && !panels.isEmpty()) {
730            // a block is attached and this LayoutBlock is used
731            // initialize connectivity as defined in first Layout Editor panel
732            LayoutEditor panel = panels.get(0);
733            List<LayoutConnectivity> c = panel.getLEAuxTools().getConnectivityList(this);
734
735            // if more than one panel, find panel with the highest connectivity
736            if (panels.size() > 1) {
737                for (int i = 1; i < panels.size(); i++) {
738                    if (c.size() < panels.get(i).getLEAuxTools().
739                            getConnectivityList(this).size()) {
740                        panel = panels.get(i);
741                        c = panel.getLEAuxTools().getConnectivityList(this);
742                    }
743                }
744
745                // Now try to determine if this block is across two panels due to a linked point
746                PositionablePoint point = panel.getFinder().findPositionableLinkPoint(this);
747                if ((point != null) && (point.getLinkedEditor() != null) && panels.contains(point.getLinkedEditor())) {
748                    c = panel.getLEAuxTools().getConnectivityList(this);
749                    c.addAll(point.getLinkedEditor().getLEAuxTools().getConnectivityList(this));
750                } else {
751                    // check that this connectivity is compatible with that of other panels.
752                    for (LayoutEditor tPanel : panels) {
753                        if ((tPanel != panel) && InstanceManager.getDefault(
754                                LayoutBlockManager.class).warn()
755                                && (!compareConnectivity(c, tPanel.getLEAuxTools().getConnectivityList(this)))) {
756                            // send user an error message
757                            int response = JmriJOptionPane.showOptionDialog(null,
758                                java.text.MessageFormat.format(Bundle.getMessage("Warn1"),
759                                    new Object[]{getUserName(), tPanel.getLayoutName(), panel.getLayoutName()}),
760                                    Bundle.getMessage("WarningTitle"),
761                                    JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE,
762                                    null,
763                                    new Object[]{Bundle.getMessage("ButtonOK"), Bundle.getMessage("ButtonOKPlus")},
764                                    Bundle.getMessage("ButtonOK"));
765                            if (response == 1 ) { // ButtokOKPlus pressed, user elected to disable messages
766                                InstanceManager.getDefault(
767                                        LayoutBlockManager.class).turnOffWarning();
768                            }
769                        }
770                    }
771                }
772            }
773
774            // Add turntable connectivity to the list
775            for (LayoutTurntable turntable : panel.getLayoutTurntables()) {
776                LayoutBlock turntableBlock = turntable.getLayoutBlock();
777                if (turntableBlock == null) continue;
778
779                if (this == turntableBlock) {
780                    // This is the turntable's block. Add connections to all valid ray blocks.
781                    for (int i = 0; i < turntable.getNumberRays(); i++) {
782                        TrackSegment rayConnect = turntable.getRayConnectOrdered(i);
783                        if (rayConnect != null) {
784                            LayoutBlock rayBlock = rayConnect.getLayoutBlock();
785                            if (rayBlock != null && rayBlock != this) {
786                                c.add(new LayoutConnectivity(this, rayBlock));
787                            }
788                        }
789                    }
790                } else {
791                    // This might be a ray block. Check if it connects to this turntable.
792                    for (int i = 0; i < turntable.getNumberRays(); i++) {
793                        TrackSegment rayConnect = turntable.getRayConnectOrdered(i);
794                        if (rayConnect != null && rayConnect.getLayoutBlock() == this) {
795                            // This is a ray block for this turntable. Add a connection to the turntable block.
796                            c.add(new LayoutConnectivity(this, turntableBlock));
797                            break; // Found our turntable, no need to check other rays
798                        }
799                    }
800                }
801            }
802            // Add traverser connectivity to the list
803            for (LayoutTraverser traverser : panel.getLayoutTraversers()) {
804                LayoutBlock traverserBlock = traverser.getLayoutBlock();
805                if (traverserBlock == null) continue;
806
807                if (this == traverserBlock) {
808                    // This is the traverser's block. Add connections to all valid slot blocks.
809                    for (int i = 0; i < traverser.getNumberSlots(); i++) {
810                        TrackSegment slotConnect = traverser.getSlotConnectOrdered(i);
811                        if (slotConnect != null) {
812                            LayoutBlock slotBlock = slotConnect.getLayoutBlock();
813                            if (slotBlock != null && slotBlock != this) {
814                                c.add(new LayoutConnectivity(this, slotBlock));
815                            }
816                        }
817                    }
818                } else {
819                    // This might be a slot block. Check if it connects to this traverser.
820                    for (int i = 0; i < traverser.getNumberSlots(); i++) {
821                        TrackSegment slotConnect = traverser.getSlotConnectOrdered(i);
822                        if (slotConnect != null && slotConnect.getLayoutBlock() == this) {
823                            // This is a slot block for this traverser. Add a connection to the traverser block.
824                            c.add(new LayoutConnectivity(this, traverserBlock));
825                            break; // Found our traverser, no need to check other slots
826                        }
827                    }
828                }
829            }
830            // update block Paths to reflect connectivity as needed
831            updateBlockPaths(c, panel);
832        }
833    }
834
835    /**
836     * Check/Update Path objects for the attached Block using the connectivity
837     * in the specified Layout Editor panel.
838     *
839     * @param panel to extract paths
840     */
841    public void updatePathsUsingPanel(LayoutEditor panel) {
842        if (panel == null) {
843            log.error("Null panel in call to updatePathsUsingPanel");
844            return;
845        }
846        List<LayoutConnectivity> c = panel.getLEAuxTools().getConnectivityList(this);
847        updateBlockPaths(c, panel);
848
849    }
850
851    private void updateBlockPaths(List<LayoutConnectivity> c, LayoutEditor panel) {
852        addRouteLog.debug("From {} updateBlockPaths Called", getDisplayName());
853        auxTools = panel.getLEAuxTools();
854        List<Path> paths = block.getPaths();
855        boolean[] used = new boolean[c.size()];
856        int[] need = new int[paths.size()];
857        Arrays.fill(used, false);
858        Arrays.fill(need, -1);
859
860        // cycle over existing Paths, checking against LayoutConnectivity
861        for (int i = 0; i < paths.size(); i++) {
862            Path p = paths.get(i);
863
864            // cycle over LayoutConnectivity matching to this Path
865            for (int j = 0; ((j < c.size()) && (need[i] == -1)); j++) {
866                if (!used[j]) {
867                    // this LayoutConnectivity not used yet
868                    LayoutConnectivity lc = c.get(j);
869                    if ((lc.getBlock1().getBlock() == p.getBlock()) || (lc.getBlock2().getBlock() == p.getBlock())) {
870                        // blocks match - record
871                        used[j] = true;
872                        need[i] = j;
873                    }
874                }
875            }
876        }
877
878        // update needed Paths
879        for (int i = 0; i < paths.size(); i++) {
880            if (need[i] >= 0) {
881                Path p = paths.get(i);
882                LayoutConnectivity lc = c.get(need[i]);
883                if (lc.getBlock1() == this) {
884                    p.setToBlockDirection(lc.getDirection());
885                    p.setFromBlockDirection(lc.getReverseDirection());
886                } else {
887                    p.setToBlockDirection(lc.getReverseDirection());
888                    p.setFromBlockDirection(lc.getDirection());
889                }
890                List<BeanSetting> beans = new ArrayList<>(p.getSettings());
891                for (BeanSetting bean : beans) {
892                    p.removeSetting(bean);
893                }
894                auxTools.addBeanSettings(p, lc, this);
895            }
896        }
897        // delete unneeded Paths
898        for (int i = 0; i < paths.size(); i++) {
899            if (need[i] < 0) {
900                block.removePath(paths.get(i));
901                if (InstanceManager.getDefault(
902                        LayoutBlockManager.class).isAdvancedRoutingEnabled()) {
903                    removeAdjacency(paths.get(i));
904                }
905            }
906        }
907
908        // add Paths as required
909        for (int j = 0; j < c.size(); j++) {
910            if (!used[j]) {
911                // there is no corresponding Path, add one.
912                LayoutConnectivity lc = c.get(j);
913                Path newp;
914
915                if (lc.getBlock1() == this) {
916                    newp = new Path(lc.getBlock2().getBlock(), lc.getDirection(),
917                            lc.getReverseDirection());
918                } else {
919                    newp = new Path(lc.getBlock1().getBlock(), lc.getReverseDirection(),
920                            lc.getDirection());
921                }
922                block.addPath(newp);
923
924                addRouteLog.debug("From {} addPath({})", getDisplayName(), newp.toString());
925
926                if (InstanceManager.getDefault(LayoutBlockManager.class).isAdvancedRoutingEnabled()) {
927                    addAdjacency(newp);
928                }
929                auxTools.addBeanSettings(newp, lc, this);
930            }
931        }
932
933        // djd debugging - lists results of automatic initialization of Paths and BeanSettings
934        if (log.isDebugEnabled()) {
935            block.getPaths().stream().forEach( p -> log.debug("From {} to {}", getDisplayName(), p ));
936        }
937    }
938
939    /**
940     * Make sure all the layout connectivity objects in test are in main.
941     *
942     * @param main the main list of LayoutConnectivity objects
943     * @param test the test list of LayoutConnectivity objects
944     * @return true if all test layout connectivity objects are in main
945     */
946    private boolean compareConnectivity(List<LayoutConnectivity> main, List<LayoutConnectivity> test) {
947        boolean result = false;     // assume failure (pessimsit!)
948        if (!main.isEmpty() && !test.isEmpty()) {
949            result = true;          // assume success (optimist!)
950            // loop over connectivities in test list
951            for (LayoutConnectivity tc : test) {
952                LayoutBlock tlb1 = tc.getBlock1(), tlb2 = tc.getBlock2();
953                // loop over main list to make sure the same blocks are connected
954                boolean found = false;  // assume failure (pessimsit!)
955                for (LayoutConnectivity mc : main) {
956                    LayoutBlock mlb1 = mc.getBlock1(), mlb2 = mc.getBlock2();
957                    if (((tlb1 == mlb1) && (tlb2 == mlb2))
958                            || ((tlb1 == mlb2) && (tlb2 == mlb1))) {
959                        found = true;   // success!
960                        break;
961                    }
962                }
963                if (!found) {
964                    result = false;
965                    break;
966                }
967            }
968        } else if (main.isEmpty() && test.isEmpty()) {
969            result = true;          // OK if both have no neighbors, common for turntable rays
970        }
971        return result;
972    }
973
974    /**
975     * Handle tasks when block changes
976     *
977     * @param e propChgEvent
978     */
979    void handleBlockChange(PropertyChangeEvent e) {
980        if (!suppressNameUpdate) {
981            updateMemoryFromBlock();
982        }
983
984        if ( Block.PROPERTY_USERNAME.equals(e.getPropertyName())) {
985            setUserName(e.getNewValue().toString());
986            InstanceManager.getDefault(NamedBeanHandleManager.class).
987                    renameBean(e.getOldValue().toString(), e.getNewValue().toString(), this);
988        }
989
990        if ( Block.OCC_SENSOR_CHANGE.equals(e.getPropertyName())) {
991            if (e.getNewValue() == null){
992                // Remove Sensor
993                setOccupancySensorName(null);
994            } else {
995                // Set/change sensor
996                Sensor sensor = (Sensor) e.getNewValue();
997                setSensorFromBlockEnabled = false;
998                if (validateSensor(sensor.getSystemName(), null) == null) {
999                    // Sensor change rejected, reset block sensor assignment
1000                    Sensor origSensor = (Sensor) e.getOldValue();
1001                    block.setSensor(origSensor == null ? "" : origSensor.getSystemName());
1002                }
1003                setSensorFromBlockEnabled = true;
1004            }
1005        }
1006
1007        // Redraw all Layout Editor panels using this Layout Block
1008        redrawLayoutBlockPanels();
1009
1010        if (InstanceManager.getDefault(LayoutBlockManager.class).isAdvancedRoutingEnabled()) {
1011            stateUpdateIfChanged();
1012        }
1013    }
1014
1015    // the block status last sent to our neighbours in a routing update
1016    private int lastRoutingStatus = -1;
1017
1018    /**
1019     * Send a routing update only when the block status we advertise has
1020     * changed, not for every Block property change such as a new value.
1021     */
1022    private void stateUpdateIfChanged() {
1023        if (!jmri.util.ThreadingUtil.isLayoutThread()) {
1024            // routing tables are only used on the layout thread; the status
1025            // is read when the update is sent
1026            jmri.util.ThreadingUtil.runOnLayoutEventually(this::stateUpdateIfChanged);
1027            return;
1028        }
1029        int status = getBlockStatus();
1030        if (status != lastRoutingStatus) {
1031            lastRoutingStatus = status;
1032            stateUpdate();
1033        }
1034    }
1035
1036    /**
1037     * Deactivate block listener for redraw of panels and update of memories on
1038     * change of state
1039     */
1040    private void deactivateBlock() {
1041        if ((mBlockListener != null) && (block != null)) {
1042            block.removePropertyChangeListener(mBlockListener);
1043        }
1044        mBlockListener = null;
1045    }
1046
1047    /**
1048     * Set/reset update of memory name when block goes from occupied to
1049     * unoccupied or vice versa. If set is true, name update is suppressed. If
1050     * set is false, name update works normally, and the memory is set from
1051     * the block at once, since the block may have changed while updates were
1052     * suppressed.
1053     *
1054     * @param set true, update suppress. false, update normal
1055     */
1056    public void setSuppressNameUpdate(boolean set) {
1057        boolean resume = suppressNameUpdate && !set;
1058        suppressNameUpdate = set;
1059        if (resume) {
1060            // The block may have changed while updates were suppressed
1061            updateMemoryFromBlock();
1062        }
1063    }
1064
1065    /**
1066     * Copy the block value to the memory, if there is one.
1067     */
1068    private void updateMemoryFromBlock() {
1069        Memory m = getMemory();
1070        if ((m != null) && (block != null)) {
1071            Object val = block.getValue();
1072            if (val != null) {
1073                if (!(val instanceof RosterEntry) && !(val instanceof Reportable)) {
1074                    val = val.toString();
1075                }
1076            }
1077            m.setValue(val);
1078        }
1079    }
1080
1081
1082    private final NamedBeanComboBox<Memory> memoryComboBox = new NamedBeanComboBox<>(
1083            InstanceManager.getDefault(MemoryManager.class), null, DisplayOptions.DISPLAYNAME);
1084
1085    private final JTextField metricField = new JTextField(10);
1086
1087    private final JComboBox<String> senseBox = new JComboBox<>();
1088
1089    // TODO I18N in Bundle.properties
1090    private int senseActiveIndex;
1091    private int senseInactiveIndex;
1092
1093    private JColorChooser trackColorChooser = null;
1094    private JColorChooser occupiedColorChooser = null;
1095    private JColorChooser extraColorChooser = null;
1096
1097    public void editLayoutBlock(Component callingPane) {
1098        LayoutBlockEditAction beanEdit = new LayoutBlockEditAction();
1099        if (block == null) {
1100            // Block may not have been initialised due to an error so manually set it in the edit window
1101            String userName = getUserName();
1102            if ((userName != null) && !userName.isEmpty()) {
1103                Block b = InstanceManager.getDefault(BlockManager.class).getBlock(userName);
1104                if (b != null) {
1105                    beanEdit.setBean(b);
1106                }
1107            }
1108        } else {
1109            beanEdit.setBean(block);
1110        }
1111        beanEdit.actionPerformed(null);
1112    }
1113
1114    private final String[] working = {"Bi-Directional", "Receive Only", "Send Only"};
1115
1116    // TODO I18N in ManagersBundle.properties
1117    protected List<JComboBox<String>> neighbourDir;
1118
1119    protected class LayoutBlockEditAction extends BlockEditAction {
1120
1121        @Override
1122        public String helpTarget() {
1123            return "package.jmri.jmrit.display.EditLayoutBlock";
1124        }  // NOI18N
1125
1126        @Override
1127        protected void initPanels() {
1128            super.initPanels();
1129            BeanItemPanel ld = layoutDetails();
1130            if (InstanceManager.getDefault(LayoutBlockManager.class).isAdvancedRoutingEnabled()) {
1131                blockRoutingDetails();
1132            }
1133            setSelectedComponent(ld);
1134        }
1135
1136        BeanItemPanel layoutDetails() {
1137            BeanItemPanel layout = new BeanItemPanel();
1138            layout.setName(Bundle.getMessage("LayoutEditor"));
1139
1140            LayoutEditor.setupComboBox(memoryComboBox, false, true, false);
1141
1142            layout.addItem(new BeanEditItem(new JLabel("" + useCount), Bundle.getMessage("UseCount"), null));
1143            layout.addItem(new BeanEditItem(memoryComboBox, Bundle.getMessage("BeanNameMemory"),
1144                    Bundle.getMessage("MemoryVariableTip")));
1145
1146            senseBox.removeAllItems();
1147            senseBox.addItem(Bundle.getMessage("SensorStateActive"));
1148            senseActiveIndex = 0;
1149            senseBox.addItem(Bundle.getMessage("SensorStateInactive"));
1150            senseInactiveIndex = 1;
1151
1152            layout.addItem(new BeanEditItem(senseBox, Bundle.getMessage("OccupiedSense"), Bundle.getMessage("OccupiedSenseHint")));
1153
1154            trackColorChooser = new JColorChooser(blockTrackColor);
1155            trackColorChooser.setPreviewPanel(new JPanel()); // remove the preview panel
1156            AbstractColorChooserPanel[] trackColorPanels = {new SplitButtonColorChooserPanel()};
1157            trackColorChooser.setChooserPanels(trackColorPanels);
1158            layout.addItem(new BeanEditItem(trackColorChooser, Bundle.getMessage("TrackColor"), Bundle.getMessage("TrackColorHint")));
1159
1160            occupiedColorChooser = new JColorChooser(blockOccupiedColor);
1161            occupiedColorChooser.setPreviewPanel(new JPanel()); // remove the preview panel
1162            AbstractColorChooserPanel[] occupiedColorPanels = {new SplitButtonColorChooserPanel()};
1163            occupiedColorChooser.setChooserPanels(occupiedColorPanels);
1164            layout.addItem(new BeanEditItem(occupiedColorChooser, Bundle.getMessage("OccupiedColor"), Bundle.getMessage("OccupiedColorHint")));
1165
1166            extraColorChooser = new JColorChooser(blockExtraColor);
1167            extraColorChooser.setPreviewPanel(new JPanel()); // remove the preview panel
1168            AbstractColorChooserPanel[] extraColorPanels = {new SplitButtonColorChooserPanel()};
1169            extraColorChooser.setChooserPanels(extraColorPanels);
1170            layout.addItem(new BeanEditItem(extraColorChooser, Bundle.getMessage("ExtraColor"), Bundle.getMessage("ExtraColorHint")));
1171
1172            layout.setSaveItem(new AbstractAction() {
1173                @Override
1174                public void actionPerformed(ActionEvent e) {
1175                    boolean needsRedraw = false;
1176                    int k = senseBox.getSelectedIndex();
1177                    int oldSense = occupiedSense;
1178
1179                    if (k == senseActiveIndex) {
1180                        occupiedSense = Sensor.ACTIVE;
1181                    } else {
1182                        occupiedSense = Sensor.INACTIVE;
1183                    }
1184
1185                    if (oldSense != occupiedSense) {
1186                        needsRedraw = true;
1187                    }
1188                    // check if track color changed
1189                    Color oldColor = blockTrackColor;
1190                    blockTrackColor = trackColorChooser.getColor();
1191                    if (oldColor != blockTrackColor) {
1192                        needsRedraw = true;
1193                        JmriColorChooser.addRecentColor(blockTrackColor);
1194                    }
1195                    // check if occupied color changed
1196                    oldColor = blockOccupiedColor;
1197                    blockOccupiedColor = occupiedColorChooser.getColor();
1198                    if (oldColor != blockOccupiedColor) {
1199                        needsRedraw = true;
1200                        JmriColorChooser.addRecentColor(blockOccupiedColor);
1201                    }
1202                    // check if extra color changed
1203                    oldColor = blockExtraColor;
1204                    blockExtraColor = extraColorChooser.getColor();
1205                    if (oldColor != blockExtraColor) {
1206                        needsRedraw = true;
1207                        JmriColorChooser.addRecentColor(blockExtraColor);
1208                    }
1209                    // check if Memory changed
1210                    String newName = memoryComboBox.getSelectedItemDisplayName();
1211                    if (newName == null) {
1212                        newName = "";
1213                    }
1214                    if (!memoryName.equals(newName)) {
1215                        // memory has changed
1216                        setMemory(validateMemory(newName, null), newName);
1217                        if (getMemory() == null) {
1218                            // invalid memory entered
1219                            memoryName = "";
1220                            memoryComboBox.setSelectedItem(null);
1221                            return;
1222                        } else {
1223                            memoryComboBox.setSelectedItem(getMemory());
1224                            needsRedraw = true;
1225                        }
1226                    }
1227
1228                    if (needsRedraw) {
1229                        redrawLayoutBlockPanels();
1230                    }
1231                }
1232            });
1233
1234            layout.setResetItem(new AbstractAction() {
1235                @Override
1236                public void actionPerformed(ActionEvent e) {
1237                    memoryComboBox.setSelectedItem(getMemory());
1238                    trackColorChooser.setColor(blockTrackColor);
1239                    occupiedColorChooser.setColor(blockOccupiedColor);
1240                    extraColorChooser.setColor(blockExtraColor);
1241                    if (occupiedSense == Sensor.ACTIVE) {
1242                        senseBox.setSelectedIndex(senseActiveIndex);
1243                    } else {
1244                        senseBox.setSelectedIndex(senseInactiveIndex);
1245                    }
1246                }
1247            });
1248            bei.add(layout);
1249            return layout;
1250        }
1251
1252        BeanItemPanel blockRoutingDetails() {
1253            BeanItemPanel routing = new BeanItemPanel();
1254            routing.setName("Routing");
1255
1256            routing.addItem(new BeanEditItem(metricField, "Block Metric", "set the cost for going over this block"));
1257
1258            routing.addItem(new BeanEditItem(null, null, "Set the direction of the connection to the neighbouring block"));
1259            neighbourDir = new ArrayList<>(getNumberOfNeighbours());
1260            for (int i = 0; i < getNumberOfNeighbours(); i++) {
1261                JComboBox<String> dir = new JComboBox<>(working);
1262                routing.addItem(new BeanEditItem(dir, getNeighbourAtIndex(i).getDisplayName(), null));
1263                neighbourDir.add(dir);
1264            }
1265
1266            routing.setResetItem(new AbstractAction() {
1267                @Override
1268                public void actionPerformed(ActionEvent e) {
1269                    metricField.setText(Integer.toString(metric));
1270                    for (int i = 0; i < getNumberOfNeighbours(); i++) {
1271                        JComboBox<String> dir = neighbourDir.get(i);
1272                        Block blk = neighbours.get(i).getBlock();
1273                        if (block.isBlockDenied(blk)) {
1274                            dir.setSelectedIndex(2);
1275                        } else if (blk.isBlockDenied(block)) {
1276                            dir.setSelectedIndex(1);
1277                        } else {
1278                            dir.setSelectedIndex(0);
1279                        }
1280                    }
1281                }
1282            });
1283
1284            routing.setSaveItem(new AbstractAction() {
1285                @Override
1286                public void actionPerformed(ActionEvent e) {
1287                    int m = Integer.parseInt(metricField.getText().trim());
1288                    if (m != metric) {
1289                        setBlockMetric(m);
1290                    }
1291                    if (neighbourDir != null) {
1292                        for (int i = 0; i < neighbourDir.size(); i++) {
1293                            int neigh = neighbourDir.get(i).getSelectedIndex();
1294                            neighbours.get(i).getBlock().removeBlockDenyList(block);
1295                            block.removeBlockDenyList(neighbours.get(i).getBlock());
1296                            switch (neigh) {
1297                                case 0: {
1298                                    updateNeighbourPacketFlow(neighbours.get(i), RXTX);
1299                                    break;
1300                                }
1301
1302                                case 1: {
1303                                    neighbours.get(i).getBlock().addBlockDenyList(block.getDisplayName());
1304                                    updateNeighbourPacketFlow(neighbours.get(i), TXONLY);
1305                                    break;
1306                                }
1307
1308                                case 2: {
1309                                    block.addBlockDenyList(neighbours.get(i).getBlock().getDisplayName());
1310                                    updateNeighbourPacketFlow(neighbours.get(i), RXONLY);
1311                                    break;
1312                                }
1313
1314                                default: {
1315                                    break;
1316                                }
1317                            }
1318                            /* switch */
1319                        }
1320                    }
1321                }
1322            });
1323            bei.add(routing);
1324            return routing;
1325        }
1326    }
1327
1328    /**
1329     * Remove this object from display and persistance.
1330     */
1331    void remove() {
1332        // if an occupancy sensor has been activated, deactivate it
1333        deactivateBlock();
1334        // remove from persistance by flagging inactive
1335        active = false;
1336    }
1337
1338    boolean active = true;
1339
1340    /**
1341     * "active" is true if the object is still displayed, and should be stored.
1342     *
1343     * @return active
1344     */
1345    public boolean isActive() {
1346        return active;
1347    }
1348
1349    /*
1350      The code below relates to the layout block routing protocol
1351     */
1352    /**
1353     * Set the block metric based upon the track segment that the block is
1354     * associated with if the (200 if Side, 50 if Main). If the block is
1355     * assigned against multiple track segments all with different types then
1356     * the highest type will be used. In theory no reason why it couldn't be a
1357     * compromise.
1358     */
1359    void setBlockMetric() {
1360        if (!defaultMetric) {
1361            return;
1362        }
1363        updateRouteLog.debug("From '{}' default set block metric called", getDisplayName());
1364        LayoutEditor panel = getMaxConnectedPanel();
1365        if (panel == null) {
1366            updateRouteLog.debug("From '{}' unable to set metric as we are not connected to a panel yet",
1367                getDisplayName());
1368            return;
1369        }
1370        String userName = getUserName();
1371        if (userName == null) {
1372            log.info("From '{}': unable to get user name", this.getDisplayName());
1373            return;
1374        }
1375        List<TrackSegment> ts = panel.getFinder().findTrackSegmentByBlock(userName);
1376        int mainline = 0;
1377        int side = 0;
1378
1379        for (TrackSegment t : ts) {
1380            if (t.isMainline()) {
1381                mainline++;
1382            } else {
1383                side++;
1384            }
1385        }
1386
1387        if (mainline > side) {
1388            metric = 50;
1389        } else if (mainline < side) {
1390            metric = 200;
1391        } else {
1392            // They must both be equal so will set as a mainline.
1393            metric = 50;
1394        }
1395
1396        updateRouteLog.debug("From '{}' metric set to {}", getDisplayName(), metric);
1397
1398        // What we need to do here, is resend our routing packets with the new metric
1399        RoutingPacket update = new RoutingPacket(UPDATE, this.getBlock(), -1, metric, -1, -1, getNextPacketID());
1400        firePropertyChange(PROPERTY_ROUTING, null, update);
1401        advertiseAllRouteChanges();
1402    }
1403
1404    private boolean defaultMetric = true;
1405
1406    public boolean useDefaultMetric() {
1407        return defaultMetric;
1408    }
1409
1410    public void useDefaultMetric(boolean boo) {
1411        if (boo == defaultMetric) {
1412            return;
1413        }
1414        defaultMetric = boo;
1415        if (boo) {
1416            setBlockMetric();
1417        }
1418    }
1419
1420    /**
1421     * Set a metric cost against a block, this is used in the calculation of a
1422     * path between two location on the layout, a lower path cost is always
1423     * preferred For Layout blocks defined as Mainline the default metric is 50.
1424     * For Layout blocks defined as a Siding the default metric is 200.
1425     *
1426     * @param m metric value
1427     */
1428    public void setBlockMetric(int m) {
1429        if (metric == m) {
1430            return;
1431        }
1432        metric = m;
1433        defaultMetric = false;
1434        if (jmri.util.ThreadingUtil.isLayoutThread()) {
1435            sendMetricUpdate();
1436        } else {
1437            // routing tables are only changed on the layout thread
1438            jmri.util.ThreadingUtil.runOnLayoutEventually(this::sendMetricUpdate);
1439        }
1440    }
1441
1442    private void sendMetricUpdate() {
1443        RoutingPacket update = new RoutingPacket(UPDATE, this.getBlock(), -1, metric, -1, -1, getNextPacketID());
1444        firePropertyChange(PROPERTY_ROUTING, null, update);
1445        // every route we advertise includes our metric
1446        advertiseAllRouteChanges();
1447    }
1448
1449    /**
1450     * Get the layout block metric cost
1451     *
1452     * @return metric cost of block
1453     */
1454    public int getBlockMetric() {
1455        return metric;
1456    }
1457
1458    // re work this so that is makes beter us of existing code.
1459    // This is no longer required currently, but might be used at a later date.
1460    public void addAllThroughPaths() {
1461        addRouteLog.debug("Add all ThroughPaths {}", getDisplayName());
1462
1463        if ((block != null) && (!panels.isEmpty())) {
1464            // a block is attached and this LayoutBlock is used
1465            // initialize connectivity as defined in first Layout Editor panel
1466            LayoutEditor panel = panels.get(0);
1467            List<LayoutConnectivity> c = panel.getLEAuxTools().getConnectivityList(this);
1468
1469            // if more than one panel, find panel with the highest connectivity
1470            if (panels.size() > 1) {
1471                for (int i = 1; i < panels.size(); i++) {
1472                    if (c.size() < panels.get(i).getLEAuxTools().
1473                            getConnectivityList(this).size()) {
1474                        panel = panels.get(i);
1475                        c = panel.getLEAuxTools().getConnectivityList(this);
1476                    }
1477                }
1478
1479                // check that this connectivity is compatible with that of other panels.
1480                for (LayoutEditor tPanel : panels) {
1481                    if ((tPanel != panel)
1482                            && InstanceManager.getDefault(LayoutBlockManager.class).
1483                                    warn() && (!compareConnectivity(c, tPanel.getLEAuxTools().getConnectivityList(this)))) {
1484
1485                        // send user an error message
1486                        int response = JmriJOptionPane.showOptionDialog(null,
1487                                java.text.MessageFormat.format(Bundle.getMessage("Warn1"),
1488                                        new Object[]{getUserName(), tPanel.getLayoutName(),
1489                                            panel.getLayoutName()}), Bundle.getMessage("WarningTitle"),
1490                                JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE,
1491                                null,
1492                                new Object[]{Bundle.getMessage("ButtonOK"), Bundle.getMessage("ButtonOKPlus")},
1493                                Bundle.getMessage("ButtonOK"));
1494                        if (response == 1) { // array position 1 ButtonOKPlus pressed, user elected to disable messages
1495                            InstanceManager.getDefault(LayoutBlockManager.class).turnOffWarning();
1496                        }
1497                    }
1498                }
1499            }
1500            auxTools = panel.getLEAuxTools();
1501            List<LayoutConnectivity> d = auxTools.getConnectivityList(this);
1502            List<LayoutBlock> attachedBlocks = new ArrayList<>();
1503
1504            for (LayoutConnectivity connectivity : d) {
1505                if (connectivity.getBlock1() != this) {
1506                    attachedBlocks.add(connectivity.getBlock1());
1507                } else {
1508                    attachedBlocks.add(connectivity.getBlock2());
1509                }
1510            }
1511            // Will need to re-look at this to cover both way and single way routes
1512            for (LayoutBlock attachedBlock : attachedBlocks) {
1513                addRouteLog.debug("From {} block is attached {}", getDisplayName(), attachedBlock.getDisplayName());
1514
1515                for (LayoutBlock layoutBlock : attachedBlocks) {
1516                    addThroughPath(attachedBlock.getBlock(), layoutBlock.getBlock(), panel);
1517                }
1518            }
1519        }
1520    }
1521
1522    // TODO: if the block already exists, we still may want to re-work the through paths
1523    // With this bit we need to get our neighbour to send new routes
1524    private void addNeighbour(Block addBlock, int direction, int workingDirection) {
1525        boolean layoutConnectivityBefore = layoutConnectivity;
1526
1527        addRouteLog.debug("From {} asked to add block {} as new neighbour {}", getDisplayName(),
1528                    addBlock.getDisplayName(), decodePacketFlow(workingDirection));
1529
1530        if (getAdjacency(addBlock) != null) {
1531            addRouteLog.debug("Block is already registered");
1532            addThroughPath(getAdjacency(addBlock));
1533        } else {
1534            Adjacencies adj = new Adjacencies(addBlock, direction, workingDirection);
1535            neighbours.add(adj);
1536
1537            // Add the neighbour to our routing table.
1538            LayoutBlock blk = InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlock(addBlock);
1539            LayoutEditor editor = getMaxConnectedPanel();
1540
1541            if ((editor != null) && (connection == null)) {
1542                // We should be able to determine block metric now as the tracksegment should be valid
1543                connection = editor.getConnectivityUtil();
1544            }
1545
1546            // Need to inform our neighbours of our new addition
1547            // We only add an entry into the routing table if we are able to reach the next working block.
1548            // If we only transmit routes to it, then we can not route to it therefore it is not added
1549            Routes route = null;
1550
1551            if ((workingDirection == RXTX) || (workingDirection == RXONLY)) {
1552                if (blk != null) {
1553                    route = new Routes(addBlock, this.getBlock(), 1, direction, blk.getBlockMetric(), addBlock.getLengthMm());
1554                } else {
1555                    route = new Routes(addBlock, this.getBlock(), 1, direction, 0, 0);
1556                }
1557                routes.add(route);
1558            }
1559
1560            if (blk != null) {
1561                boolean mutual = blk.informNeighbourOfAttachment(this, this.getBlock(), workingDirection);
1562
1563                // The propertychange listener will have to be modified depending upon RX or TX selection.
1564                // if we only transmit routes to this neighbour then we do not want to listen to thier broadcast messages
1565                if ((workingDirection == RXTX) || (workingDirection == RXONLY)) {
1566                    blk.addPropertyChangeListener(this);
1567                    // log.info("From {} add property change {}", this.getDisplayName(), blk.getDisplayName());
1568                } else {
1569                    blk.removePropertyChangeListener(this);
1570                }
1571
1572                int neighwork = blk.getAdjacencyPacketFlow(this.getBlock());
1573                addRouteLog.debug("{}.getAdjacencyPacketFlow({}): {}, {}",
1574                    blk.getDisplayName(), getBlock().getDisplayName(),
1575                    ( neighwork==-1 ? "Unset" : decodePacketFlow(neighwork)), neighwork);
1576
1577                if (neighwork != -1) {
1578                    addRouteLog.debug("From {} Updating flow direction to {} for block {} choice of {} {}",
1579                        getDisplayName(),
1580                        decodePacketFlow(determineAdjPacketFlow(workingDirection, neighwork)),
1581                        blk.getDisplayName(), decodePacketFlow(workingDirection), decodePacketFlow(neighwork));
1582
1583                    int newPacketFlow = determineAdjPacketFlow(workingDirection, neighwork);
1584                    adj.setPacketFlow(newPacketFlow);
1585
1586                    if (newPacketFlow == TXONLY) {
1587                        for (int j = routes.size() - 1; j > -1; j--) {
1588                            Routes ro = routes.get(j);
1589                            if ((ro.getDestBlock() == addBlock)
1590                                    && (ro.getNextBlock() == this.getBlock())) {
1591                                adj.removeRouteAdvertisedToNeighbour(ro);
1592                                routes.remove(j).dispose();
1593                            }
1594                        }
1595                        RoutingPacket newUpdate = new RoutingPacket(REMOVAL, addBlock, -1, -1, -1, -1, getNextPacketID());
1596                        neighbours.forEach((adja) -> adja.removeRouteAdvertisedToNeighbour(addBlock));
1597                        firePropertyChange(PROPERTY_ROUTING, null, newUpdate);
1598                    }
1599                } else {
1600                    addRouteLog.debug("From {} neighbour {} working direction is not valid",
1601                        getDisplayName(), addBlock.getDisplayName());
1602                    return;
1603                }
1604                adj.setMutual(mutual);
1605
1606                if (route != null) {
1607                    route.stateChange();
1608                }
1609                addThroughPath(getAdjacency(addBlock));
1610                // We get our new neighbour to send us a list of valid routes that they have.
1611                // This might have to be re-written as a property change event?
1612                // Also only inform our neighbour if they have us down as a mutual, otherwise it will just reject the packet.
1613                if (((workingDirection == RXTX) || (workingDirection == TXONLY)) && mutual) {
1614                    blk.informNeighbourOfValidRoutes(getBlock());
1615                }
1616            } else {
1617                addRouteLog.debug("From {} neighbour {} has no layoutBlock associated, metric set to {}",
1618                    getDisplayName(), addBlock.getDisplayName(), adj.getMetric());
1619            }
1620        }
1621
1622        /* If the connectivity before has not completed and produced an error with
1623           setting up through Paths, we will cycle through them */
1624        addRouteLog.debug("From {} layout connectivity before {}", getDisplayName(), layoutConnectivityBefore);
1625        if (!layoutConnectivityBefore) {
1626            for (Adjacencies neighbour : neighbours) {
1627                addThroughPath(neighbour);
1628            }
1629        }
1630        /* We need to send our new neighbour our copy of the routing table however
1631           we can only send valid routes that would be able to traverse as definded by
1632           through paths table */
1633    }
1634
1635    private boolean informNeighbourOfAttachment(LayoutBlock lBlock, Block block, int workingDirection) {
1636        Adjacencies adj = getAdjacency(block);
1637        if (adj == null) {
1638            addRouteLog.debug("From {} neighbour {} has informed us of its attachment to us, however we do not yet have it registered",
1639                getDisplayName(), lBlock.getDisplayName());
1640            return false;
1641        }
1642
1643        if (!adj.isMutual()) {
1644            addRouteLog.debug("From {} neighbour {} wants us to {}; we have it set as {}",
1645                getDisplayName(), block.getDisplayName(),
1646                decodePacketFlow(workingDirection), decodePacketFlow(adj.getPacketFlow()));
1647
1648            // Simply if both the neighbour and us both want to do the same thing with sending routing information,
1649            // in one direction then no routes will be passed
1650            int newPacketFlow = determineAdjPacketFlow(adj.getPacketFlow(), workingDirection);
1651            addRouteLog.debug("From {} neighbour {} passed {} we have {} this will be updated to {}",
1652                getDisplayName(), block.getDisplayName(), decodePacketFlow(workingDirection),
1653                decodePacketFlow(adj.getPacketFlow()), decodePacketFlow(newPacketFlow));
1654            adj.setPacketFlow(newPacketFlow);
1655
1656            // If we are only set to transmit routing information to the adj, then
1657            // we will not have it appearing in the routing table
1658            if (newPacketFlow != TXONLY) {
1659                Routes neighRoute = getValidRoute(this.getBlock(), adj.getBlock());
1660                // log.info("From " + this.getDisplayName() + " neighbour " + adj.getBlock().getDisplayName() + " valid routes returned as " + neighRoute);
1661                if (neighRoute == null) {
1662                    log.info("Null route so will bomb out");
1663                    return false;
1664                }
1665
1666                if (neighRoute.getMetric() != adj.getMetric()) {
1667                    addRouteLog.debug("From {} The value of the metric we have for this route"
1668                        + " is not correct {}, stored {} v {}",
1669                        getDisplayName(), getBlock().getDisplayName(), neighRoute.getMetric(), adj.getMetric());
1670                    neighRoute.setMetric(adj.getMetric());
1671                    // This update might need to be more selective
1672                    RoutingPacket update = new RoutingPacket(UPDATE, adj.getBlock(), -1, (adj.getMetric() + metric), -1, -1, getNextPacketID());
1673                    firePropertyChange(PROPERTY_ROUTING, null, update);
1674                }
1675
1676                if (!MathUtil.equals(neighRoute.getLength(), adj.getLength())) {
1677                    addRouteLog.debug("From {} The value of the length we have for this route"
1678                        + " is not correct {}, stored {} v {}",
1679                        getDisplayName(), getBlock().getDisplayName(), neighRoute.getLength(), adj.getLength());
1680                    neighRoute.setLength(adj.getLength());
1681                    // This update might need to be more selective
1682                    RoutingPacket update = new RoutingPacket(UPDATE, adj.getBlock(), -1, -1,
1683                            adj.getLength() + block.getLengthMm(), -1, getNextPacketID());
1684                    firePropertyChange(PROPERTY_ROUTING, null, update);
1685                }
1686                // the route straight to the neighbour costs the neighbour's metric
1687                neighRoute.setMetric(lBlock.getBlockMetric());
1688                advertiseRouteChange(block);
1689            }
1690
1691            addRouteLog.debug("From {} We were not a mutual adjacency with {} but now are",
1692                getDisplayName(), lBlock.getDisplayName());
1693
1694            if ((newPacketFlow == RXTX) || (newPacketFlow == RXONLY)) {
1695                lBlock.addPropertyChangeListener(this);
1696            } else {
1697                lBlock.removePropertyChangeListener(this);
1698            }
1699
1700            if (newPacketFlow == TXONLY) {
1701                for (int j = routes.size() - 1; j > -1; j--) {
1702                    Routes ro = routes.get(j);
1703                    if ((ro.getDestBlock() == block) && (ro.getNextBlock() == this.getBlock())) {
1704                        adj.removeRouteAdvertisedToNeighbour(ro);
1705                        routes.remove(j).dispose();
1706                    }
1707                }
1708
1709                for (int j = throughPaths.size() - 1; j > -1; j--) {
1710                    if ((throughPaths.get(j).getDestinationBlock() == block)) {
1711                        addRouteLog.debug("From {} removed throughpath {} {}",
1712                            getDisplayName(), throughPaths.get(j).getSourceBlock().getDisplayName(),
1713                            throughPaths.get(j).getDestinationBlock().getDisplayName());
1714                        throughPaths.remove(j).dispose();
1715                    }
1716                }
1717                RoutingPacket newUpdate = new RoutingPacket(REMOVAL, block, -1, -1, -1, -1, getNextPacketID());
1718                neighbours.forEach((adja) -> adja.removeRouteAdvertisedToNeighbour(block));
1719                firePropertyChange(PROPERTY_ROUTING, null, newUpdate);
1720            }
1721
1722            adj.setMutual(true);
1723            addThroughPath(adj);
1724
1725            // As we are now mutual we will send our neigh a list of valid routes.
1726            if ((newPacketFlow == RXTX) || (newPacketFlow == TXONLY)) {
1727                addRouteLog.debug("From {} inform neighbour of valid routes", getDisplayName());
1728                informNeighbourOfValidRoutes(block);
1729            }
1730        }
1731        return true;
1732    }
1733
1734    private int determineAdjPacketFlow(int our, int neigh) {
1735        // Both are the same
1736        updateRouteLog.debug("From {} values passed our {} neigh {}", getDisplayName(),
1737            decodePacketFlow(our), decodePacketFlow(neigh));
1738        if ((our == RXTX) && (neigh == RXTX)) {
1739            return RXTX;
1740        }
1741
1742        /*First off reverse the neighbour flow, as it will be telling us if it will allow or deny traffic from us.
1743           So if it is set to RX, then we can TX to it.*/
1744        if (neigh == RXONLY) {
1745            neigh = TXONLY;
1746        } else if (neigh == TXONLY) {
1747            neigh = RXONLY;
1748        }
1749
1750        if (our == neigh) {
1751            return our;
1752        }
1753        return NONE;
1754    }
1755
1756    private void informNeighbourOfValidRoutes(Block newblock) {
1757        informNeighbourOfValidRoutes(newblock, null);
1758    }
1759
1760    /**
1761     * Send a neighbour the routes it can use through this block.
1762     *
1763     * @param newblock the neighbour
1764     * @param onlyVia  if not null, only send the routes leaving this block
1765     *                 to this neighbouring block, as when a through path
1766     *                 between the two has just been added
1767     */
1768    private void informNeighbourOfValidRoutes(Block newblock, @CheckForNull Block onlyVia) {
1769        // java.sql.Timestamp t1 = new java.sql.Timestamp(System.nanoTime());
1770        List<Block> validFromPath = new ArrayList<>();
1771        addRouteLog.debug("From {} new block {}", getDisplayName(), newblock.getDisplayName());
1772
1773        for (ThroughPaths tp : throughPaths) {
1774            if ((onlyVia != null) && (tp.getSourceBlock() != onlyVia) && (tp.getDestinationBlock() != onlyVia)) {
1775                continue;
1776            }
1777            addRouteLog.debug("From {} B through routes {} {}",
1778                getDisplayName(), tp.getSourceBlock().getDisplayName(),
1779                tp.getDestinationBlock().getDisplayName());
1780
1781            if (tp.getSourceBlock() == newblock) {
1782                validFromPath.add(tp.getDestinationBlock());
1783            } else if (tp.getDestinationBlock() == newblock) {
1784                validFromPath.add(tp.getSourceBlock());
1785            }
1786        }
1787
1788        addRouteLog.debug("From {} ===== valid from size path {} ====", getDisplayName(), validFromPath.size());
1789        addRouteLog.debug("To {}", newblock.getDisplayName());
1790
1791        // We only send packets on to our neighbour that are registered as being on a valid through path and are mutual.
1792        LayoutBlock lBnewblock = null;
1793        Adjacencies adj = getAdjacency(newblock);
1794        if (adj.isMutual()) {
1795            addRouteLog.debug("From {} adj with {} is mutual", getDisplayName(), newblock.getDisplayName());
1796            lBnewblock = InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlock(newblock);
1797        } else {
1798            addRouteLog.debug("From {} adj with {} is NOT mutual", getDisplayName(), newblock.getDisplayName());
1799        }
1800
1801        if (lBnewblock == null) {
1802            return;
1803        }
1804
1805        for (Routes ro : new ArrayList<>(routes)) {
1806            addRouteLog.debug("next:{} dest:{}", ro.getNextBlock().getDisplayName(),
1807                ro.getDestBlock().getDisplayName());
1808
1809            if (ro.getNextBlock() == getBlock()) {
1810                addRouteLog.debug("From {} ro next block is this", getDisplayName());
1811                if (validFromPath.contains(ro.getDestBlock())) {
1812                    addRouteLog.debug("From {} route to {} we have it with a metric of {} we will add our metric of {} "
1813                        + "this will be sent to {} a",
1814                        getDisplayName(), ro.getDestBlock().getDisplayName(),
1815                        ro.getMetric(), metric, lBnewblock.getDisplayName());
1816                    // we added +1 to hop count and our metric.
1817
1818                    RoutingPacket update = new RoutingPacket(ADDITION, ro.getDestBlock(), ro.getHopCount() + 1, (ro.getMetric() + metric), (ro.getLength() + block.getLengthMm()), -1, getNextPacketID());
1819                    lBnewblock.addRouteFromNeighbour(this, update);
1820                }
1821            } else {
1822                // Don't know if this might need changing so that we only send out our best
1823                // route to the neighbour, rather than cycling through them all.
1824                if (validFromPath.contains(ro.getNextBlock())) {
1825                    addRouteLog.debug("From {} route to {} we have it with a metric of {} we will add our metric of {} this will be sent to {} b", this.getDisplayName(), ro.getDestBlock().getDisplayName(), ro.getMetric(), metric, lBnewblock.getDisplayName());
1826                    // we added +1 to hop count and our metric.
1827                    if (adj.advertiseRouteToNeighbour(ro)) {
1828                        addRouteLog.debug("Told to advertise to neighbour");
1829                        // this should keep track of the routes we sent to our neighbour.
1830                        adj.addRouteAdvertisedToNeighbour(ro);
1831                        RoutingPacket update = new RoutingPacket(ADDITION, ro.getDestBlock(), ro.getHopCount() + 1, (ro.getMetric() + metric), (ro.getLength() + block.getLengthMm()), -1, getNextPacketID());
1832                        lBnewblock.addRouteFromNeighbour(this, update);
1833                    } else {
1834                        addRouteLog.debug("Not advertised to neighbour");
1835                    }
1836                } else {
1837                    addRouteLog.debug("failed valid from path Not advertised/added");
1838                }
1839            }
1840        }
1841    }
1842
1843    static long time = 0;
1844
1845    /**
1846     * Work out our direction of route flow correctly.
1847     */
1848    private void addAdjacency(Path addPath) {
1849        addRouteLog.debug("From {} path to be added {} {}",
1850            getDisplayName(), addPath.getBlock().getDisplayName(),
1851            Path.decodeDirection(addPath.getToBlockDirection()));
1852
1853        Block destBlockToAdd = addPath.getBlock();
1854        int ourWorkingDirection = RXTX;
1855        if (destBlockToAdd == null) {
1856            log.error("Found null destination block for path from {}", this.getDisplayName());
1857            return;
1858        }
1859
1860        if (this.getBlock().isBlockDenied(destBlockToAdd.getDisplayName())) {
1861            ourWorkingDirection = RXONLY;
1862        } else if (destBlockToAdd.isBlockDenied(this.getBlock().getDisplayName())) {
1863            ourWorkingDirection = TXONLY;
1864        }
1865
1866        addRouteLog.debug("From {} to block {} we should therefore be... {}",
1867            getDisplayName(), addPath.getBlock().getDisplayName(), decodePacketFlow(ourWorkingDirection));
1868        addNeighbour(addPath.getBlock(), addPath.getToBlockDirection(), ourWorkingDirection);
1869
1870    }
1871
1872    // Might be possible to refactor the removal to use a bit of common code.
1873    private void removeAdjacency(Path removedPath) {
1874        Block ablock = removedPath.getBlock();
1875        if (ablock != null) {
1876            deleteRouteLog.debug("From {} Adjacency to be removed {} {}",
1877                getDisplayName(), ablock.getDisplayName(), Path.decodeDirection(removedPath.getToBlockDirection()));
1878            LayoutBlock layoutBlock = InstanceManager.getDefault(
1879                    LayoutBlockManager.class).getLayoutBlock(ablock);
1880            if (layoutBlock != null) {
1881                removeAdjacency(layoutBlock);
1882            }
1883        } else {
1884            log.debug("removeAdjacency() removedPath.getBlock() is null");
1885        }
1886    }
1887
1888    private void removeAdjacency(LayoutBlock layoutBlock) {
1889        deleteRouteLog.debug("From {} Adjacency to be removed {}",
1890            getDisplayName(), layoutBlock.getDisplayName());
1891        Block removedBlock = layoutBlock.getBlock();
1892
1893        // Work our way backward through the list of neighbours
1894        // We need to work out which routes to remove first.
1895        // here we simply remove the routes which are advertised from the removed neighbour
1896        List<Routes> tmpBlock = removeRouteReceivedFromNeighbour(removedBlock);
1897
1898        for (int i = neighbours.size() - 1; i > -1; i--) {
1899            // Use to check against direction but don't now.
1900            if ((neighbours.get(i).getBlock() == removedBlock)) {
1901                // Was previously before the for loop.
1902                // Pos move the remove list and remove thoughpath out of this for loop.
1903                layoutBlock.removePropertyChangeListener(this);
1904                deleteRouteLog.debug("From {} block {} found and removed",
1905                    getDisplayName(), removedBlock.getDisplayName());
1906                LayoutBlock layoutBlockToNotify = InstanceManager.getDefault(
1907                        LayoutBlockManager.class).getLayoutBlock(neighbours.get(i).getBlock());
1908                if (layoutBlockToNotify==null){ // move to provides?
1909                    log.error("Unable to notify neighbours for block {}",neighbours.get(i).getBlock());
1910                    continue;
1911                }
1912                getAdjacency(neighbours.get(i).getBlock()).dispose();
1913                neighbours.remove(i);
1914                layoutBlockToNotify.notifiedNeighbourNoLongerMutual(this);
1915            }
1916        }
1917
1918        for (int i = throughPaths.size() - 1; i > -1; i--) {
1919            if (throughPaths.get(i).getSourceBlock() == removedBlock) {
1920                // only mark for removal if the source isn't in the adjcency table
1921                if (getAdjacency(throughPaths.get(i).getSourceBlock()) == null) {
1922                    deleteRouteLog.debug("remove {} to {}",
1923                        throughPaths.get(i).getSourceBlock().getDisplayName(),
1924                        throughPaths.get(i).getDestinationBlock().getDisplayName());
1925                    throughPaths.remove(i).dispose();
1926                }
1927            } else if (throughPaths.get(i).getDestinationBlock() == removedBlock) {
1928                // only mark for removal if the destination isn't in the adjcency table
1929                if (getAdjacency(throughPaths.get(i).getDestinationBlock()) == null) {
1930                    deleteRouteLog.debug("remove {} to {}",
1931                        throughPaths.get(i).getSourceBlock().getDisplayName(),
1932                        throughPaths.get(i).getDestinationBlock().getDisplayName());
1933                    throughPaths.remove(i).dispose();
1934                }
1935            }
1936        }
1937
1938        deleteRouteLog.debug("From {} neighbour has been removed - Number of routes to this neighbour removed{}",
1939            getDisplayName(), tmpBlock.size());
1940        notifyNeighboursOfRemoval(tmpBlock, removedBlock);
1941    }
1942
1943    // This is used when a property event change is triggered for a removed route.
1944    // Not sure that bulk removals will be necessary
1945    private void removeRouteFromNeighbour(LayoutBlock src, RoutingPacket update) {
1946        InstanceManager.getDefault(LayoutBlockManager.class).setLastRoutingChange();
1947        Block srcblk = src.getBlock();
1948        Block destblk = update.getBlock();
1949        String msgPrefix = "From " + this.getDisplayName() + " notify block " + srcblk.getDisplayName() + " ";
1950
1951        deleteRouteLog.debug("{} remove route from neighbour called", msgPrefix);
1952
1953        if (InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlock(srcblk) == this) {
1954            deleteRouteLog.debug("From {} source block is the same as our block! {}",
1955                getDisplayName(), destblk.getDisplayName());
1956            return;
1957        }
1958
1959        deleteRouteLog.debug("{} (Direct Notification) neighbour {} has removed route to {}",
1960            msgPrefix, srcblk.getDisplayName(), destblk.getDisplayName());
1961        deleteRouteLog.debug("{} routes in table {} Remove route from neighbour", msgPrefix, routes.size());
1962        List<Routes> routesToRemove = new ArrayList<>();
1963        for (int i = routes.size() - 1; i > -1; i--) {
1964            Routes ro = routes.get(i);
1965            if ((ro.getNextBlock() == srcblk) && (ro.getDestBlock() == destblk)) {
1966                routesToRemove.add(new Routes(routes.get(i).getDestBlock(), routes.get(i).getNextBlock()));
1967                deleteRouteLog.debug("{} route to {} from block {} to be removed triggered by propertyChange",
1968                    msgPrefix, ro.getDestBlock().getDisplayName(), ro.getNextBlock().getDisplayName());
1969                routes.remove(i).dispose();
1970                // We only fire off routing update the once
1971            }
1972        }
1973        notifyNeighboursOfRemoval(routesToRemove, srcblk);
1974    }
1975
1976    private List<Routes> removeRouteReceivedFromNeighbour(Block removedBlock) {
1977        List<Routes> tmpBlock = new ArrayList<>();
1978
1979        // here we simply remove the routes which are advertised from the removed neighbour
1980        for (int j = routes.size() - 1; j > -1; j--) {
1981            Routes ro = routes.get(j);
1982            deleteRouteLog.debug("From {} route to check {} from Block {}",
1983                getDisplayName(), routes.get(j).getDestBlock().getDisplayName(),
1984                routes.get(j).getNextBlock().getDisplayName());
1985
1986            if (ro.getDestBlock() == removedBlock) {
1987                deleteRouteLog.debug("From {} route to {} from block {} to be removed"
1988                        + " triggered by adjancey removal as dest block has been removed",
1989                    getDisplayName(), routes.get(j).getDestBlock().getDisplayName(),
1990                    routes.get(j).getNextBlock().getDisplayName());
1991
1992                if (!tmpBlock.contains(ro)) {
1993                    tmpBlock.add(ro);
1994                }
1995                routes.remove(j).dispose();
1996                // This will need to be removed fromth directly connected
1997            } else if (ro.getNextBlock() == removedBlock) {
1998                deleteRouteLog.debug("From {} route to {} from block {} to be removed"
1999                    + " triggered by adjancey removal",
2000                    getDisplayName(), routes.get(j).getDestBlock().getDisplayName(),
2001                    routes.get(j).getNextBlock().getDisplayName());
2002
2003                if (!tmpBlock.contains(ro)) {
2004                    tmpBlock.add(ro);
2005                }
2006                routes.remove(j).dispose();
2007                // This will also need to be removed from the directly connected list as well.
2008            }
2009        }
2010        return tmpBlock;
2011    }
2012
2013    private void updateNeighbourPacketFlow(Block neighbour, int flow) {
2014        // Packet flow from neighbour will need to be reversed.
2015        Adjacencies neighAdj = getAdjacency(neighbour);
2016
2017        if (flow == RXONLY) {
2018            flow = TXONLY;
2019        } else if (flow == TXONLY) {
2020            flow = RXONLY;
2021        }
2022
2023        if (neighAdj.getPacketFlow() == flow) {
2024            return;
2025        }
2026        updateNeighbourPacketFlow(neighAdj, flow);
2027    }
2028
2029    protected void updateNeighbourPacketFlow(Adjacencies neighbour, final int flow) {
2030        if (neighbour.getPacketFlow() == flow) {
2031            return;
2032        }
2033
2034        final LayoutBlock neighLBlock = neighbour.getLayoutBlock();
2035        Runnable r = () -> neighLBlock.updateNeighbourPacketFlow(block, flow);
2036
2037        Block neighBlock = neighbour.getBlock();
2038        int oldPacketFlow = neighbour.getPacketFlow();
2039
2040        neighbour.setPacketFlow(flow);
2041
2042        SwingUtilities.invokeLater(r);
2043
2044        if (flow == TXONLY) {
2045            neighBlock.addBlockDenyList(this.block);
2046            neighLBlock.removePropertyChangeListener(this);
2047
2048            // This should remove routes learned from our neighbour
2049            List<Routes> tmpBlock = removeRouteReceivedFromNeighbour(neighBlock);
2050
2051            notifyNeighboursOfRemoval(tmpBlock, neighBlock);
2052
2053            // Need to also remove all through paths to this neighbour
2054            for (int i = throughPaths.size() - 1; i > -1; i--) {
2055                if (throughPaths.get(i).getDestinationBlock() == neighBlock) {
2056                    throughPaths.remove(i).dispose();
2057                    firePropertyChange(PROPERTY_THROUGH_PATH_REMOVED, null, null);
2058                }
2059            }
2060
2061            // We potentially will need to re-advertise routes to this neighbour
2062            if (oldPacketFlow == RXONLY) {
2063                addThroughPath(neighbour);
2064            }
2065        } else if (flow == RXONLY) {
2066            neighLBlock.addPropertyChangeListener(this);
2067            neighBlock.removeBlockDenyList(this.block);
2068            this.block.addBlockDenyList(neighBlock);
2069
2070            for (int i = throughPaths.size() - 1; i > -1; i--) {
2071                if (throughPaths.get(i).getSourceBlock() == neighBlock) {
2072                    throughPaths.remove(i).dispose();
2073                    firePropertyChange(PROPERTY_THROUGH_PATH_REMOVED, null, null);
2074                }
2075            }
2076
2077            // Might need to rebuild through paths.
2078            if (oldPacketFlow == TXONLY) {
2079                routes.add(new Routes(neighBlock, this.getBlock(),
2080                        1, neighbour.getDirection(), neighLBlock.getBlockMetric(), neighBlock.getLengthMm()));
2081                addThroughPath(neighbour);
2082            }
2083            // We would need to withdraw the routes that we advertise to the neighbour
2084        } else if (flow == RXTX) {
2085            neighBlock.removeBlockDenyList(this.block);
2086            this.block.removeBlockDenyList(neighBlock);
2087            neighLBlock.addPropertyChangeListener(this);
2088
2089            // Might need to rebuild through paths.
2090            if (oldPacketFlow == TXONLY) {
2091                routes.add(new Routes(neighBlock, this.getBlock(),
2092                        1, neighbour.getDirection(), neighLBlock.getBlockMetric(), neighBlock.getLengthMm()));
2093            }
2094            addThroughPath(neighbour);
2095        }
2096    }
2097
2098    private void notifyNeighboursOfRemoval(List<Routes> routesToRemove, Block notifyingblk) {
2099        String msgPrefix = "From " + this.getDisplayName() + " notify block " + notifyingblk.getDisplayName() + " ";
2100
2101        deleteRouteLog.debug("{} notifyNeighboursOfRemoval called for routes from {} ===",
2102            msgPrefix, notifyingblk.getDisplayName());
2103        boolean notifyvalid = false;
2104
2105        for (int i = neighbours.size() - 1; i > -1; i--) {
2106            if (neighbours.get(i).getBlock() == notifyingblk) {
2107                notifyvalid = true;
2108            }
2109        }
2110
2111        deleteRouteLog.debug("{} The notifying block is still valid? {}", msgPrefix, notifyvalid);
2112
2113        for (int j = routesToRemove.size() - 1; j > -1; j--) {
2114            boolean stillexist = false;
2115            Block destBlock = routesToRemove.get(j).getDestBlock();
2116            Block sourceBlock = routesToRemove.get(j).getNextBlock();
2117            RoutingPacket newUpdate = new RoutingPacket(REMOVAL, destBlock, -1, -1, -1, -1, getNextPacketID());
2118
2119            deleteRouteLog.debug("From {} notify block {} checking {} from {}",
2120                getDisplayName(), notifyingblk.getDisplayName(),
2121                destBlock.getDisplayName(), sourceBlock.getDisplayName());
2122            List<Routes> validroute = new ArrayList<>();
2123            List<Routes> destRoutes = getDestRoutes(destBlock);
2124            for (Routes r : destRoutes) {
2125                // We now know that we still have a valid route to the dest
2126                if (r.getNextBlock() == this.getBlock()) {
2127                    deleteRouteLog.debug("{} The destBlock {} is our neighbour",
2128                        msgPrefix, destBlock.getDisplayName());
2129                    validroute.add(new Routes(r.getDestBlock(), r.getNextBlock()));
2130                    stillexist = true;
2131                } else {
2132                    // At this stage do we need to check if the valid route comes from a neighbour?
2133                    deleteRouteLog.debug("{} we still have a route to {} via {} in our list",
2134                        msgPrefix, destBlock.getDisplayName(), r.getNextBlock().getDisplayName());
2135                    validroute.add(new Routes(destBlock, r.getNextBlock()));
2136                    stillexist = true;
2137                }
2138            }
2139            // We may need to find out who else we could of sent the route to by checking in the through paths
2140
2141            if (stillexist) {
2142                deleteRouteLog.debug("{}A Route still exists", msgPrefix);
2143                deleteRouteLog.debug("{} the number of routes installed to block {} is {}",
2144                    msgPrefix, destBlock.getDisplayName(), validroute.size());
2145
2146                if (validroute.size() == 1) {
2147                    // Specific routing update.
2148                    Block nextHop = validroute.get(0).getNextBlock();
2149                    LayoutBlock layoutBlock;
2150                    if (validroute.get(0).getNextBlock() != this.getBlock()) {
2151                        layoutBlock = InstanceManager.getDefault(
2152                                LayoutBlockManager.class).getLayoutBlock(nextHop);
2153                        deleteRouteLog.debug("{} We only have a single valid route left to {}"
2154                            + " So will tell {} we no longer have it",
2155                            msgPrefix, destBlock.getDisplayName(),
2156                            layoutBlock == null ? "NULL" : layoutBlock.getDisplayName());
2157
2158                        if (layoutBlock != null) {
2159                            layoutBlock.removeRouteFromNeighbour(this, newUpdate);
2160                        }
2161                        getAdjacency(nextHop).removeRouteAdvertisedToNeighbour(routesToRemove.get(j));
2162                    }
2163
2164                    // At this point we could probably do with checking for other valid paths from the notifyingblock
2165                    // Have a feeling that this is pretty much the same as above!
2166                    List<Block> validNeighboursToNotify = new ArrayList<>();
2167
2168                    // Problem we have here is that although we only have one valid route, one of our neighbours
2169                    // could still hold a valid through path.
2170                    for (int i = neighbours.size() - 1; i > -1; i--) {
2171                        // Need to ignore if the dest block is our neighour in this instance
2172                        if ((neighbours.get(i).getBlock() != destBlock) && (neighbours.get(i).getBlock() != nextHop) 
2173                            && validThroughPath(notifyingblk, neighbours.get(i).getBlock())) {
2174                            Block neighblock = neighbours.get(i).getBlock();
2175
2176                            deleteRouteLog.debug("{} we could of potentially sent the route to {}",
2177                                msgPrefix, neighblock.getDisplayName());
2178
2179                            if (!validThroughPath(nextHop, neighblock)) {
2180                                deleteRouteLog.debug("{} there is no other valid path so will mark for removal",
2181                                    msgPrefix);
2182                                validNeighboursToNotify.add(neighblock);
2183                            } else {
2184                                deleteRouteLog.debug("{} there is another valid path so will NOT mark for removal",
2185                                    msgPrefix);
2186                            }
2187                        }
2188                    }
2189
2190                    deleteRouteLog.debug("{} the next block is our selves so we won't remove!", msgPrefix);
2191                    deleteRouteLog.debug("{} do we need to find out if we could of send the route"
2192                        + " to another neighbour such as?", msgPrefix);
2193
2194                    for (Block value : validNeighboursToNotify) {
2195                        // If the neighbour has a valid through path to the dest
2196                        // we will not notify the neighbour of our loss of route
2197                        if (!validThroughPath(value, destBlock)) {
2198                            layoutBlock = InstanceManager.getDefault(LayoutBlockManager.class).
2199                                    getLayoutBlock(value);
2200                            if (layoutBlock != null) {
2201                                layoutBlock.removeRouteFromNeighbour(this, newUpdate);
2202                            }
2203                            getAdjacency(value).removeRouteAdvertisedToNeighbour(routesToRemove.get(j));
2204                        } else {
2205                            deleteRouteLog.debug("{}{} has a valid path to {}",
2206                                msgPrefix, value.getDisplayName(), destBlock.getDisplayName());
2207                        }
2208                    }
2209                } else {
2210                    // Need to deal with having multiple routes left.
2211                    deleteRouteLog.debug("{} routes left to block {}", msgPrefix, destBlock.getDisplayName());
2212
2213                    for (Routes item : validroute) {
2214                        // We need to see if we have valid routes.
2215                        if (validThroughPath(notifyingblk, item.getNextBlock())) {
2216                            deleteRouteLog.debug("{} to {} Is a valid route",
2217                                msgPrefix, item.getNextBlock().getDisplayName());
2218                            // Will mark the route for potential removal
2219                            item.setMiscFlags(0x02);
2220                        } else {
2221                            deleteRouteLog.debug("{} to {} Is not a valid route",
2222                                msgPrefix, item.getNextBlock().getDisplayName());
2223                            // Mark the route to not be removed.
2224                            item.setMiscFlags(0x01);
2225
2226                            // Given that the route to this is not valid, we do not want to
2227                            // be notifying this next block about the loss of route.
2228                        }
2229                    }
2230
2231                    // We have marked all the routes for either potential notification of route removal, or definate no removal;
2232                    // Now need to get through the list and cross reference each one.
2233                    for (int i = 0; i < validroute.size(); i++) {
2234                        if (validroute.get(i).getMiscFlags() == 0x02) {
2235                            Block nextblk = validroute.get(i).getNextBlock();
2236
2237                            deleteRouteLog.debug("{} route from {} has been flagged for removal",
2238                                msgPrefix, nextblk.getDisplayName());
2239
2240                            // Need to cross reference it with the routes that are left.
2241                            boolean leaveroute = false;
2242                            for (Routes value : validroute) {
2243                                if (value.getMiscFlags() == 0x01) {
2244                                    if (validThroughPath(nextblk, value.getNextBlock())) {
2245                                        deleteRouteLog.debug("{} we have a valid path from {} to {}",
2246                                            msgPrefix, nextblk.getDisplayName(), value.getNextBlock());
2247                                        leaveroute = true;
2248                                    }
2249                                }
2250                            }
2251
2252                            if (!leaveroute) {
2253                                LayoutBlock layoutBlock = InstanceManager.getDefault(
2254                                        LayoutBlockManager.class).getLayoutBlock(nextblk);
2255                                deleteRouteLog.debug("{}############ We need to send notification to {} to remove route ########### haven't found an example of this yet!",
2256                                    msgPrefix, nextblk.getDisplayName());
2257                                if (layoutBlock==null) { // change to provides
2258                                    log.error("Unable to fetch block {}",nextblk);
2259                                    continue;
2260                                }
2261                                layoutBlock.removeRouteFromNeighbour(this, newUpdate);
2262                                getAdjacency(nextblk).removeRouteAdvertisedToNeighbour(routesToRemove.get(j));
2263
2264                            } else {
2265                                deleteRouteLog.debug("{} a valid path through exists {} so we will not remove route.",
2266                                    msgPrefix, nextblk.getDisplayName());
2267                            }
2268                        }
2269                    }
2270                }
2271                // the best route left may cost more than the one removed
2272                advertiseRouteChange(destBlock);
2273            } else {
2274                deleteRouteLog.debug("{} We have no other routes to {} Therefore we will broadast this to our neighbours",
2275                    msgPrefix, destBlock.getDisplayName());
2276
2277                for (Adjacencies adj : neighbours) {
2278                    adj.removeRouteAdvertisedToNeighbour(destBlock);
2279                }
2280                firePropertyChange(PROPERTY_ROUTING, null, newUpdate);
2281            }
2282        }
2283
2284        deleteRouteLog.debug("{} finshed check and notifying of removed routes from {} ===",
2285            msgPrefix, notifyingblk.getDisplayName());
2286    }
2287
2288    private void addThroughPath( @Nonnull Adjacencies adj) {
2289        // Check if this block is a turntable block on ANY panel it belongs to.
2290        // If so, do not create through paths.
2291        boolean isTurntableBlock = false;
2292        for (LayoutEditor p : panels) {
2293            for (LayoutTurntable turntable : p.getLayoutTurntables()) {
2294                if (turntable.getLayoutBlock() == this) {
2295                    isTurntableBlock = true;
2296                    break;
2297                }
2298            }
2299            if (isTurntableBlock) {
2300                break;
2301            }
2302        }
2303
2304        if (isTurntableBlock) {
2305            addRouteLog.debug("Block {} is a turntable block. Skipping through path generation in addThroughPath(Adjacencies).", getDisplayName());
2306            return; // Do not create through paths for a turntable
2307        }
2308
2309        // Check if this block is a traverser block on ANY panel it belongs to.
2310        // If so, do not create through paths.
2311        boolean isTraverserBlock = false;
2312        for (LayoutEditor p : panels) {
2313            for (LayoutTraverser traverser : p.getLayoutTraversers()) {
2314                if (traverser.getLayoutBlock() == this) {
2315                    isTraverserBlock = true;
2316                    break;
2317                }
2318            }
2319            if (isTraverserBlock) {
2320                break;
2321            }
2322        }
2323
2324        if (isTraverserBlock) {
2325            addRouteLog.debug("Block {} is a traverser block. Skipping through path generation in addThroughPath(Adjacencies).", getDisplayName());
2326            return; // Do not create through paths for a traverser
2327        }
2328        
2329        Block newAdj = adj.getBlock();
2330        int packetFlow = adj.getPacketFlow();
2331
2332        addRouteLog.debug("From {} addThroughPathCalled with adj {}",
2333            getDisplayName(), adj.getBlock().getDisplayName());
2334
2335        for (Adjacencies neighbour : neighbours) {
2336            // cycle through all the neighbours
2337            if (neighbour.getBlock() != newAdj) {
2338                int neighPacketFlow = neighbour.getPacketFlow();
2339
2340                addRouteLog.debug("From {} our direction: {}, neighbour direction: {}",
2341                    getDisplayName(), decodePacketFlow(packetFlow), decodePacketFlow(neighPacketFlow));
2342
2343                if ((packetFlow == RXTX) && (neighPacketFlow == RXTX)) {
2344                    // if both are RXTX then add flow in both directions
2345                    addThroughPath(neighbour.getBlock(), newAdj);
2346                    addThroughPath(newAdj, neighbour.getBlock());
2347                } else if ((packetFlow == RXONLY) && (neighPacketFlow == TXONLY)) {
2348                    addThroughPath(neighbour.getBlock(), newAdj);
2349                } else if ((packetFlow == TXONLY) && (neighPacketFlow == RXONLY)) {
2350                    addThroughPath(newAdj, neighbour.getBlock());
2351                } else if ((packetFlow == RXTX) && (neighPacketFlow == TXONLY)) {   // was RX
2352                    addThroughPath(neighbour.getBlock(), newAdj);
2353                } else if ((packetFlow == RXTX) && (neighPacketFlow == RXONLY)) {   // was TX
2354                    addThroughPath(newAdj, neighbour.getBlock());
2355                } else if ((packetFlow == RXONLY) && (neighPacketFlow == RXTX)) {
2356                    addThroughPath(neighbour.getBlock(), newAdj);
2357                } else if ((packetFlow == TXONLY) && (neighPacketFlow == RXTX)) {
2358                    addThroughPath(newAdj, neighbour.getBlock());
2359                } else {
2360                    addRouteLog.debug("Invalid combination {} and {}",
2361                        decodePacketFlow(packetFlow), decodePacketFlow(neighPacketFlow));
2362                }
2363            }
2364        }
2365    }
2366
2367    /**
2368     * Add a path between two blocks, but without spec a panel.
2369     */
2370    private void addThroughPath( @Nonnull Block srcBlock, @Nonnull Block dstBlock) {
2371        addRouteLog.debug("Block {}.addThroughPath(src:{}, dst: {})",
2372            getDisplayName(), srcBlock.getDisplayName(), dstBlock.getDisplayName());
2373
2374        if ((block != null) && (!panels.isEmpty()) && isThroughPath(srcBlock, dstBlock)) {
2375            // nothing to work out for a through path we already have
2376            layoutConnectivity = true;
2377            return;
2378        }
2379        if ((block != null) && (!panels.isEmpty())) {
2380            // a block is attached and this LayoutBlock is used
2381            // initialize connectivity as defined in first Layout Editor panel
2382            LayoutEditor panel = panels.get(0);
2383            List<LayoutConnectivity> c = panel.getLEAuxTools().getConnectivityList(this);
2384
2385            // if more than one panel, find panel with the highest connectivity
2386            if (panels.size() > 1) {
2387                for (int i = 1; i < panels.size(); i++) {
2388                    if (c.size() < panels.get(i).getLEAuxTools().
2389                            getConnectivityList(this).size()) {
2390                        panel = panels.get(i);
2391                        c = panel.getLEAuxTools().getConnectivityList(this);
2392                    }
2393                }
2394
2395                // check that this connectivity is compatible with that of other panels.
2396                for (LayoutEditor tPanel : panels) {
2397                    if ((tPanel != panel) && InstanceManager.getDefault(LayoutBlockManager.class).
2398                            warn() && (!compareConnectivity(c,
2399                                    tPanel.getLEAuxTools().getConnectivityList(this)))) {
2400                        // send user an error message
2401                        int response = JmriJOptionPane.showOptionDialog(null,
2402                                java.text.MessageFormat.format(Bundle.getMessage("Warn1"),
2403                                        new Object[]{getUserName(), tPanel.getLayoutName(),
2404                                            panel.getLayoutName()}), Bundle.getMessage("WarningTitle"),
2405                                JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.QUESTION_MESSAGE,
2406                                null,
2407                                new Object[]{Bundle.getMessage("ButtonOK"), Bundle.getMessage("ButtonOKPlus")},
2408                                Bundle.getMessage("ButtonOK"));
2409                        if (response == 1 ) { // array position 1 ButtonOKPlus pressed, user elected to disable messages
2410                            InstanceManager.getDefault(LayoutBlockManager.class).turnOffWarning();
2411                        }
2412                    }
2413                }
2414            }
2415            // update block Paths to reflect connectivity as needed
2416            addThroughPath(srcBlock, dstBlock, panel);
2417        }
2418    }
2419
2420    private LayoutEditorAuxTools auxTools = null;
2421    private ConnectivityUtil connection = null;
2422    private boolean layoutConnectivity = true;
2423
2424    /**
2425     * Add a through path on this layout block, going from the source block to
2426     * the destination block, using a specific panel. Note: If the reverse path
2427     * is required, then this needs to be added seperately.
2428     */
2429    // Was public
2430    private void addThroughPath(Block srcBlock, Block dstBlock, LayoutEditor panel) {
2431        // Reset connectivity flag.
2432        layoutConnectivity = true;
2433
2434        if (srcBlock == dstBlock) {
2435            // Do not do anything if the blocks are the same!
2436            return;
2437        }
2438
2439        addRouteLog.debug("Block {}.addThroughPath(src:{}, dst: {}, <panel>)",
2440            getDisplayName(), srcBlock.getDisplayName(), dstBlock.getDisplayName());
2441
2442        // Initally check to make sure that the through path doesn't already exist.
2443        // no point in going through the checks if the path already exists.
2444        boolean add = true;
2445        for (ThroughPaths throughPath : throughPaths) {
2446            if (throughPath.getSourceBlock() == srcBlock) {
2447                if (throughPath.getDestinationBlock() == dstBlock) {
2448                    add = false;
2449                }
2450            }
2451        }
2452
2453        if (!add) {
2454            return;
2455        }
2456
2457        addRouteLog.debug("Block {}, src: {}, dst: {}",
2458            block.getDisplayName(), srcBlock.getDisplayName(), dstBlock.getDisplayName());
2459        connection = panel.getConnectivityUtil();
2460        List<LayoutTrackExpectedState<LayoutTurnout>> stod;
2461
2462        try {
2463            MDC.put("loggingDisabled", connection.getClass().getCanonicalName());
2464            stod = connection.getTurnoutList(block, srcBlock, dstBlock, true);
2465            MDC.remove("loggingDisabled");
2466        } catch (java.lang.NullPointerException ex) {
2467            MDC.remove("loggingDisabled");
2468            if (addRouteLog.isDebugEnabled()) {
2469                log.error("Exception ({}) caught while trying to discover turnout connectivity"
2470                    + "\nBlock: {}, srcBlock ({}) to dstBlock ({})", ex.getMessage(),
2471                    block.getDisplayName(), srcBlock.getDisplayName(), dstBlock.getDisplayName());
2472                if (ex.getStackTrace().length > 1) {
2473                    log.error("@ Line # {}", ex.getStackTrace()[1].getLineNumber());
2474                }
2475            }
2476            return;
2477        }
2478
2479        if (!connection.isTurnoutConnectivityComplete()) {
2480            layoutConnectivity = false;
2481        }
2482        List<LayoutTrackExpectedState<LayoutTurnout>> tmpdtos;
2483
2484        try {
2485            MDC.put("loggingDisabled", connection.getClass().getName());
2486            tmpdtos = connection.getTurnoutList(block, dstBlock, srcBlock, true);
2487            MDC.remove("loggingDisabled");
2488        } catch (java.lang.NullPointerException ex) {
2489            MDC.remove("loggingDisabled");
2490            addRouteLog.debug("Exception ({}) caught while trying to discover turnout connectivity"
2491                + "\nBlock: {}, dstBlock ({}) to  srcBlock ({})", ex.getMessage(),
2492                block.getDisplayName(), dstBlock.getDisplayName(), srcBlock.getDisplayName());
2493            // a frequently thrown exception may have no stack trace
2494            if (addRouteLog.isDebugEnabled() && (ex.getStackTrace().length > 1)) {
2495                addRouteLog.debug("@ Line # {}", ex.getStackTrace()[1].getLineNumber());
2496            }
2497            return;
2498        }
2499
2500        if (!connection.isTurnoutConnectivityComplete()) {
2501            layoutConnectivity = false;
2502        }
2503
2504        if (stod.size() == tmpdtos.size()) {
2505            // Need to reorder the tmplist (dst-src) to be the same order as src-dst
2506            List<LayoutTrackExpectedState<LayoutTurnout>> dtos = new ArrayList<>();
2507            for (int i = tmpdtos.size(); i > 0; i--) {
2508                dtos.add(tmpdtos.get(i - 1));
2509            }
2510
2511            // check to make sure that we pass through the same turnouts
2512            addRouteLog.debug("From {} destination size {} v source size {}",
2513                getDisplayName(), dtos.size(), stod.size());
2514
2515            for (int i = 0; i < dtos.size(); i++) {
2516                if (dtos.get(i).getObject() != stod.get(i).getObject()) {
2517                    addRouteLog.debug("{} != {}: will quit", dtos.get(i).getObject(), stod.get(i).getObject());
2518                    return;
2519                }
2520            }
2521
2522            for (int i = 0; i < dtos.size(); i++) {
2523                int x = stod.get(i).getExpectedState();
2524                int y = dtos.get(i).getExpectedState();
2525
2526                if (x != y) {
2527                    addRouteLog.debug("{} not on setting equal will quit {}, {}", block.getDisplayName(), x, y);
2528                    return;
2529                } else if (x == Turnout.UNKNOWN) {
2530                    addRouteLog.debug("{} turnout state returned as UNKNOWN", block.getDisplayName());
2531                    return;
2532                }
2533            }
2534            Set<LayoutTurnout> set = new HashSet<>();
2535
2536            for (LayoutTrackExpectedState<LayoutTurnout> layoutTurnoutLayoutTrackExpectedState : stod) {
2537                boolean val = set.add(layoutTurnoutLayoutTrackExpectedState.getObject());
2538                if ( !val ) {
2539                    // Duplicate found. will not add
2540                    return;
2541                }
2542            }
2543            // for (LayoutTurnout turn : stod) {
2544            //    if (turn.type == LayoutTurnout.DOUBLE_XOVER) {
2545            //        // Further checks might be required.
2546            //    }
2547            //}
2548            addThroughPathPostChecks(srcBlock, dstBlock, stod);
2549        } else {
2550            // We know that a path that contains a double cross-over, is not reported correctly,
2551            // therefore we shall do some additional checks and add it.
2552            addRouteLog.debug("sizes are not the same therefore, we will do some further checks");
2553            List<LayoutTrackExpectedState<LayoutTurnout>> maxt;
2554            if (stod.size() >= tmpdtos.size()) {
2555                maxt = stod;
2556            } else {
2557                maxt = tmpdtos;
2558            }
2559
2560            Set<LayoutTrackExpectedState<LayoutTurnout>> set = new HashSet<>(maxt);
2561
2562            if (set.size() == maxt.size()) {
2563                addRouteLog.debug("All turnouts are unique so potentially a valid path");
2564                boolean allowAddition = false;
2565                for (LayoutTrackExpectedState<LayoutTurnout> layoutTurnoutLayoutTrackExpectedState : maxt) {
2566                    LayoutTurnout turn = layoutTurnoutLayoutTrackExpectedState.getObject();
2567                    if (turn.type == LayoutTurnout.TurnoutType.DOUBLE_XOVER) {
2568                        allowAddition = true;
2569                        // The double crossover gets reported in the opposite setting.
2570                        if (layoutTurnoutLayoutTrackExpectedState.getExpectedState() == 2) {
2571                            layoutTurnoutLayoutTrackExpectedState.setExpectedState(4);
2572                        } else {
2573                            layoutTurnoutLayoutTrackExpectedState.setExpectedState(2);
2574                        }
2575                    }
2576                }
2577
2578                if (allowAddition) {
2579                    addRouteLog.debug("addition allowed");
2580                    addThroughPathPostChecks(srcBlock, dstBlock, maxt);
2581                } else {
2582                    addRouteLog.debug("No double cross-over so not a valid path");
2583                }
2584            }
2585        }
2586    }   // addThroughPath
2587
2588    private void addThroughPathPostChecks(Block srcBlock,
2589            Block dstBlock, List<LayoutTrackExpectedState<LayoutTurnout>> stod) {
2590        List<Path> paths = block.getPaths();
2591        Path srcPath = null;
2592
2593        for (Path item : paths) {
2594            if (item.getBlock() == srcBlock) {
2595                srcPath = item;
2596            }
2597        }
2598        Path dstPath = null;
2599
2600        for (Path value : paths) {
2601            if (value.getBlock() == dstBlock) {
2602                dstPath = value;
2603            }
2604        }
2605        ThroughPaths path = new ThroughPaths(srcBlock, srcPath, dstBlock, dstPath);
2606        path.setTurnoutList(stod);
2607
2608        addRouteLog.debug("From {} added Throughpath {} {}",
2609            getDisplayName(), path.getSourceBlock().getDisplayName(), path.getDestinationBlock().getDisplayName());
2610        throughPaths.add(path);
2611        firePropertyChange(PROPERTY_THROUGH_PATH_ADDED, null, null);
2612
2613        // update our neighbours of the new valid paths; the routes using our
2614        // other through paths have already been sent
2615        informNeighbourOfValidRoutes(srcBlock, dstBlock);
2616        informNeighbourOfValidRoutes(dstBlock, srcBlock);
2617
2618        // routes via the destination block may now be the best ones for the
2619        // source block, even where it already holds a route to the same place
2620        Adjacencies srcAdj = getAdjacency(srcBlock);
2621        if (srcAdj != null) {
2622            for (Routes ro : new ArrayList<>(routes)) {
2623                if ((ro.getNextBlock() == dstBlock)
2624                        || ((ro.getNextBlock() == getBlock()) && (ro.getDestBlock() == dstBlock))) {
2625                    advertiseRouteChange(srcAdj, ro.getDestBlock());
2626                }
2627            }
2628        }
2629    }
2630
2631    void notifiedNeighbourNoLongerMutual(LayoutBlock srcBlock) {
2632        deleteRouteLog.debug("From {}Notification from neighbour that it is no longer our friend {}",
2633            getDisplayName(), srcBlock.getDisplayName());
2634        Block blk = srcBlock.getBlock();
2635
2636        for (int i = neighbours.size() - 1; i > -1; i--) {
2637            // Need to check if the block we are being informed about has already been removed or not
2638            if (neighbours.get(i).getBlock() == blk) {
2639                removeAdjacency(srcBlock);
2640                break;
2641            }
2642        }
2643    }
2644
2645    public static final int RESERVED = 0x08;
2646
2647    void stateUpdate() {
2648        // Need to find a way to fire off updates to the various tables
2649        updateRouteLog.trace("From {} A block state change ({}) has occurred", getDisplayName(), getBlockStatusString());
2650        RoutingPacket update = new RoutingPacket(UPDATE, this.getBlock(), -1, -1, -1, getBlockStatus(), getNextPacketID());
2651        firePropertyChange(PROPERTY_ROUTING, null, update);
2652    }
2653
2654    int getBlockStatus() {
2655        if (getOccupancy() == OCCUPIED) {
2656            // Our section of track is occupied
2657            return OCCUPIED;
2658        } else if (useExtraColor) {
2659            return RESERVED;
2660        } else if (getOccupancy() == EMPTY) {
2661            return EMPTY;
2662        } else {
2663            return UNKNOWN;
2664        }
2665    }
2666
2667    String getBlockStatusString() {
2668        String result = "UNKNOWN";
2669        if (getOccupancy() == OCCUPIED) {
2670            result = "OCCUPIED";
2671        } else if (useExtraColor) {
2672            result = "RESERVED";
2673        } else if (getOccupancy() == EMPTY) {
2674            result = "EMPTY";
2675        }
2676        return result;
2677    }
2678
2679    Integer getNextPacketID() {
2680        return lastPacketID.incrementAndGet() & Integer.MAX_VALUE;
2681    }
2682
2683    public List<Block> getActiveNextBlocks(Block source) {
2684        List<Block> currentPath = new ArrayList<>();
2685
2686        for (ThroughPaths path : throughPaths) {
2687            if ((path.getSourceBlock() == source) && (path.isPathActive())) {
2688                currentPath.add(path.getDestinationBlock());
2689            }
2690        }
2691        return currentPath;
2692    }
2693
2694    public Path getThroughPathSourcePathAtIndex(int i) {
2695        return throughPaths.get(i).getSourcePath();
2696    }
2697
2698    public Path getThroughPathDestinationPathAtIndex(int i) {
2699        return throughPaths.get(i).getDestinationPath();
2700    }
2701
2702    public boolean validThroughPath(Block sourceBlock, Block destinationBlock) {
2703        for (ThroughPaths throughPath : throughPaths) {
2704            if ((throughPath.getSourceBlock() == sourceBlock) && (throughPath.getDestinationBlock() == destinationBlock)) {
2705                return true;
2706            } else if ((throughPath.getSourceBlock() == destinationBlock) && (throughPath.getDestinationBlock() == sourceBlock)) {
2707                return true;
2708            }
2709        }
2710        return false;
2711    }
2712
2713    public int getThroughPathIndex(Block sourceBlock, Block destinationBlock) {
2714        for (int i = 0; i < throughPaths.size(); i++) {
2715            if ((throughPaths.get(i).getSourceBlock() == sourceBlock)
2716                    && (throughPaths.get(i).getDestinationBlock() == destinationBlock)) {
2717                return i;
2718            } else if ((throughPaths.get(i).getSourceBlock() == destinationBlock)
2719                    && (throughPaths.get(i).getDestinationBlock() == sourceBlock)) {
2720                return i;
2721            }
2722        }
2723        return -1;
2724    }
2725
2726    private final List<Adjacencies> neighbours = new ArrayList<>();
2727
2728    private final List<ThroughPaths> throughPaths = new ArrayList<>();
2729
2730    // A sub class that holds valid routes through the block.
2731    // Possibly want to store the path direction in here as well.
2732    // or we store the ref to the path, so we can get the directions.
2733    private final List<Routes> routes = new ArrayList<>();
2734
2735    String decodePacketFlow(int value) {
2736        switch (value) {
2737            case RXTX: {
2738                return "Bi-Direction Operation";
2739            }
2740
2741            case RXONLY: {
2742                return "Uni-Directional - Trains can only exit to this block (RX) ";
2743            }
2744
2745            case TXONLY: {
2746                return "Uni-Directional - Trains can not be sent down this block (TX) ";
2747            }
2748
2749            case NONE: {
2750                return "None routing updates will be passed";
2751            }
2752            default:
2753                log.warn("Unhandled packet flow value: {}", value);
2754                break;
2755        }
2756        return "Unknown";
2757    }
2758
2759    /**
2760     * Provide an output to the console of all the valid paths through this
2761     * block.
2762     */
2763    public void printValidThroughPaths() {
2764        log.info("Through paths for block {}", this.getDisplayName());
2765        log.info("Current Block, From Block, To Block");
2766        for (ThroughPaths tp : throughPaths) {
2767            String activeStr = "";
2768            if (tp.isPathActive()) {
2769                activeStr = ", *";
2770            }
2771            log.info("From {}, {}, {}{}", this.getDisplayName(),
2772                (tp.getSourceBlock()).getDisplayName(), (tp.getDestinationBlock()).getDisplayName(), activeStr);
2773        }
2774    }
2775
2776    /**
2777     * Provide an output to the console of all our neighbouring blocks.
2778     */
2779    public void printAdjacencies() {
2780        log.info("Adjacencies for block {}", this.getDisplayName());
2781        log.info("Neighbour, Direction, mutual, relationship, metric");
2782        for (Adjacencies neighbour : neighbours) {
2783            log.info(" neighbor: {}, {}, {}, {}, {}", neighbour.getBlock().getDisplayName(),
2784                Path.decodeDirection(neighbour.getDirection()), neighbour.isMutual(),
2785                decodePacketFlow(neighbour.getPacketFlow()), neighbour.getMetric());
2786        }
2787    }
2788
2789    /**
2790     * Provide an output to the console of all the remote blocks reachable from
2791     * our block.
2792     */
2793    public void printRoutes() {
2794        log.info("Routes for block {}", this.getDisplayName());
2795        log.info("Destination, Next Block, Hop Count, Direction, State, Metric");
2796        for (Routes r : routes) {
2797            String nexthop = r.getNextBlock().getDisplayName();
2798
2799            if (r.getNextBlock() == this.getBlock()) {
2800                nexthop = "Directly Connected";
2801            }
2802            String activeString = "";
2803            if (r.isRouteCurrentlyValid()) {
2804                activeString = ", *";
2805            }
2806
2807            log.info(" neighbor: {}, {}, {}, {}, {}, {}{}", r.getDestBlock().getDisplayName(),
2808                nexthop, r.getHopCount(), Path.decodeDirection(r.getDirection()),
2809                r.getState(), r.getMetric(), activeString);
2810        }
2811    }
2812
2813    /**
2814     * Provide an output to the console of how to reach a specific block from
2815     * our block.
2816     *
2817     * @param inBlockName to find in route
2818     */
2819    public void printRoutes(String inBlockName) {
2820        log.info("Routes for block {}", this.getDisplayName());
2821        log.info("Our Block, Destination, Next Block, Hop Count, Direction, Metric");
2822        for (Routes route : routes) {
2823            if (route.getDestBlock().getDisplayName().equals(inBlockName)) {
2824                log.info("From {}, {}, {}, {}, {}, {}",
2825                    getDisplayName(), (route.getDestBlock()).getDisplayName(),
2826                    route.getNextBlock().getDisplayName(), route.getHopCount(),
2827                    Path.decodeDirection(route.getDirection()), route.getMetric());
2828            }
2829        }
2830    }
2831
2832    /**
2833     * @param destBlock is the destination of the block we are following
2834     * @param direction is the direction of travel from the previous block
2835     * @return next block
2836     */
2837    public Block getNextBlock(Block destBlock, int direction) {
2838        int bestMetric = 965000;
2839        Block bestBlock = null;
2840
2841        for (Routes r : routes) {
2842            if ((r.getDestBlock() == destBlock) && (r.getDirection() == direction)) {
2843                if (r.getMetric() < bestMetric) {
2844                    bestMetric = r.getMetric();
2845                    bestBlock = r.getNextBlock();
2846                    // bestBlock=r.getDestBlock();
2847                }
2848            }
2849        }
2850        return bestBlock;
2851    }
2852
2853    /**
2854     * Used if we already know the block prior to our block, and the destination
2855     * block. direction, is optional and is used where the previousBlock is
2856     * equal to our block.
2857     *
2858     * @param previousBlock start block
2859     * @param destBlock     finish block
2860     * @return next block
2861     */
2862    @CheckForNull
2863    public Block getNextBlock(Block previousBlock, Block destBlock) {
2864        int bestMetric = 965000;
2865        Block bestBlock = null;
2866
2867        for (Routes r : routes) {
2868            if (r.getDestBlock() == destBlock) {
2869                // Check that the route through from the previous block, to the next hop is valid
2870                if (validThroughPath(previousBlock, r.getNextBlock())) {
2871                    if (r.getMetric() < bestMetric) {
2872                        bestMetric = r.getMetric();
2873                        // bestBlock=r.getDestBlock();
2874                        bestBlock = r.getNextBlock();
2875                    }
2876                }
2877            }
2878        }
2879        return bestBlock;
2880    }
2881
2882    public int getConnectedBlockRouteIndex(Block destBlock, int direction) {
2883        for (int i = 0; i < routes.size(); i++) {
2884            if (routes.get(i).getNextBlock() == this.getBlock()) {
2885                log.info("Found a block that is directly connected");
2886
2887                if ((routes.get(i).getDestBlock() == destBlock)) {
2888                    log.info("In getConnectedBlockRouteIndex,  {}",
2889                        Integer.toString(routes.get(i).getDirection() & direction));
2890                    if ((routes.get(i).getDirection() & direction) != 0) {
2891                        return i;
2892                    }
2893                }
2894            }
2895
2896            if (log.isDebugEnabled()) {
2897                log.debug("From {}, {}, nexthop {}, {}, {}, {}", getDisplayName(),
2898                    routes.get(i).getDestBlock().getDisplayName(),
2899                    routes.get(i).getHopCount(),
2900                    Path.decodeDirection(routes.get(i).getDirection()),
2901                    routes.get(i).getState(), routes.get(i).getMetric());
2902            }
2903        }
2904        return -1;
2905    }
2906
2907    // Need to work on this to deal with the method of routing
2908    public int getNextBlockByIndex(Block destBlock, int direction, int offSet) {
2909        for (int i = offSet; i < routes.size(); i++) {
2910            Routes ro = routes.get(i);
2911            if ((ro.getDestBlock() == destBlock)) {
2912                log.info("getNextBlockByIndex {}", Integer.toString(ro.getDirection() & direction));
2913                if ((ro.getDirection() & direction) != 0) {
2914                    return i;
2915                }
2916            }
2917        }
2918        return -1;
2919    }
2920
2921    // Need to work on this to deal with the method of routing
2922    /*
2923     *
2924     */
2925    public int getNextBlockByIndex(Block previousBlock, Block destBlock, int offSet) {
2926        for (int i = offSet; i < routes.size(); i++) {
2927            Routes ro = routes.get(i);
2928            // log.info(r.getDestBlock().getDisplayName() + " vs " + destBlock.getDisplayName());
2929            if (ro.getDestBlock() == destBlock) {
2930                // Check that the route through from the previous block, to the next hop is valid
2931                if (validThroughPath(previousBlock, ro.getNextBlock())) {
2932                    log.debug("valid through path");
2933                    return i;
2934                }
2935
2936                if (ro.getNextBlock() == this.getBlock()) {
2937                    log.debug("getNextBlock is this block therefore directly connected");
2938                    return i;
2939                }
2940            }
2941        }
2942        return -1;
2943    }
2944
2945    /**
2946     * last index - the index of the last block we returned ie we last returned
2947     * index 10, so we don't want to return it again. The block returned will
2948     * have a hopcount or metric equal to or greater than the one of the last
2949     * block returned. if the exclude block list is empty this is the first
2950     * time, it has been used. The parameters for the best last block are based
2951     * upon the last entry in the excludedBlock list.
2952     *
2953     * @param previousBlock starting block
2954     * @param destBlock     finish block
2955     * @param excludeBlock  blocks to skip
2956     * @param routingMethod value to match metric
2957     * @return next block
2958     */
2959    public int getNextBestBlock(Block previousBlock, Block destBlock, List<Integer> excludeBlock, LayoutBlockConnectivityTools.Metric routingMethod) {
2960        searchRouteLog.debug("From {} find best route from {} to {} index {} routingMethod {}",
2961            getDisplayName(), previousBlock.getDisplayName(), destBlock.getDisplayName(), excludeBlock, routingMethod);
2962
2963        int bestCount = 965255; // set stupidly high
2964        int bestIndex = -1;
2965        int lastValue = 0;
2966        List<Block> nextBlocks = new ArrayList<>(5);
2967        if (!excludeBlock.isEmpty() && (excludeBlock.get(excludeBlock.size() - 1) < routes.size())) {
2968            if (routingMethod == LayoutBlockConnectivityTools.Metric.METRIC) {
2969                lastValue = routes.get(excludeBlock.get(excludeBlock.size() - 1)).getMetric();
2970            } else /* if (routingMethod==LayoutBlockManager.HOPCOUNT)*/ {
2971                lastValue = routes.get(excludeBlock.get(excludeBlock.size() - 1)).getHopCount();
2972            }
2973
2974            for (int i : excludeBlock) {
2975                nextBlocks.add(routes.get(i).getNextBlock());
2976            }
2977
2978            searchRouteLog.debug("last index is {} {}", excludeBlock.get(excludeBlock.size() - 1),
2979                routes.get(excludeBlock.get(excludeBlock.size() - 1)).getDestBlock().getDisplayName());
2980        }
2981
2982        for (int i = 0; i < routes.size(); i++) {
2983            if (!excludeBlock.contains(i)) {
2984                Routes ro = routes.get(i);
2985                if (!nextBlocks.contains(ro.getNextBlock())) {
2986                    // if(ro.getNextBlock()!=nextBlock){
2987                    int currentValue;
2988                    if (routingMethod == LayoutBlockConnectivityTools.Metric.METRIC) {
2989                        currentValue = routes.get(i).getMetric();
2990                    } else /*if (routingMethod==InstanceManager.getDefault(
2991                        LayoutBlockManager.class).HOPCOUNT)*/ {
2992                        currentValue = routes.get(i).getHopCount();  // was lastindex changed to i
2993                    }
2994
2995                    if (currentValue >= lastValue) {
2996                        if (ro.getDestBlock() == destBlock) {
2997                            searchRouteLog.debug("Match on dest blocks");
2998                            // Check that the route through from the previous block, to the next hop is valid
2999                            searchRouteLog.debug("Is valid through path previous block {} to {}",
3000                                previousBlock.getDisplayName(), ro.getNextBlock().getDisplayName());
3001
3002                            if (validThroughPath(previousBlock, ro.getNextBlock())) {
3003                                searchRouteLog.debug("valid through path");
3004
3005                                if (routingMethod == LayoutBlockConnectivityTools.Metric.METRIC) {
3006                                    if ((ro.getMetric() < bestCount) || ((ro.getMetric() == bestCount)
3007                                            && isPreferredOnTie(ro, bestIndex, routingMethod))) {
3008                                        bestIndex = i;
3009                                        bestCount = ro.getMetric();
3010                                    }
3011                                } else /*if (routingMethod==InstanceManager.getDefault(
3012                                    LayoutBlockManager.class).HOPCOUNT)*/ {
3013                                    if ((ro.getHopCount() < bestCount) || ((ro.getHopCount() == bestCount)
3014                                            && isPreferredOnTie(ro, bestIndex, routingMethod))) {
3015                                        bestIndex = i;
3016                                        bestCount = ro.getHopCount();
3017                                    }
3018                                }
3019                            }
3020
3021                            if (ro.getNextBlock() == this.getBlock()) {
3022                                searchRouteLog.debug("getNextBlock is this block therefore directly connected");
3023                                return i;
3024                            }
3025                        }
3026                    }
3027                }
3028            }
3029        }
3030
3031        searchRouteLog.debug("returning {} best count {}", bestIndex, bestCount);
3032        return bestIndex;
3033    }
3034
3035    /**
3036     * Choose between two routes of equal cost without depending on the order
3037     * in which the routes were learnt: prefer fewer hops (or the lower metric
3038     * when routing by hop count), then the next block with the lower system
3039     * name.
3040     *
3041     * @param ro            the route being considered
3042     * @param bestIndex     index of the best route found so far, or -1
3043     * @param routingMethod the cost being compared
3044     * @return true if ro should replace the best route found so far
3045     */
3046    private boolean isPreferredOnTie(Routes ro, int bestIndex, LayoutBlockConnectivityTools.Metric routingMethod) {
3047        if (bestIndex == -1) {
3048            return true;
3049        }
3050        Routes best = routes.get(bestIndex);
3051        int roSecond;
3052        int bestSecond;
3053        if (routingMethod == LayoutBlockConnectivityTools.Metric.METRIC) {
3054            roSecond = ro.getHopCount();
3055            bestSecond = best.getHopCount();
3056        } else {
3057            roSecond = ro.getMetric();
3058            bestSecond = best.getMetric();
3059        }
3060        if (roSecond != bestSecond) {
3061            return roSecond < bestSecond;
3062        }
3063        return ro.getNextBlock().getSystemName().compareTo(best.getNextBlock().getSystemName()) < 0;
3064    }
3065
3066    @CheckForNull
3067    Routes getRouteByDestBlock(Block blk) {
3068        for (int i = routes.size() - 1; i > -1; i--) {
3069            if (routes.get(i).getDestBlock() == blk) {
3070                return routes.get(i);
3071            }
3072        }
3073        return null;
3074    }
3075
3076    @Nonnull
3077    List<Routes> getRouteByNeighbour(Block blk) {
3078        List<Routes> rtr = new ArrayList<>();
3079        for (Routes route : routes) {
3080            if (route.getNextBlock() == blk) {
3081                rtr.add(route);
3082            }
3083        }
3084        return rtr;
3085    }
3086
3087    int getAdjacencyPacketFlow(Block blk) {
3088        for (Adjacencies neighbour : neighbours) {
3089            if (neighbour.getBlock() == blk) {
3090                return neighbour.getPacketFlow();
3091            }
3092        }
3093        return -1;
3094    }
3095
3096    boolean isValidNeighbour(Block blk) {
3097        for (Adjacencies neighbour : neighbours) {
3098            if (neighbour.getBlock() == blk) {
3099                return true;
3100            }
3101        }
3102        return false;
3103    }
3104
3105    @Override
3106    public synchronized void addPropertyChangeListener(PropertyChangeListener listener) {
3107        if (listener == this) {
3108            log.debug("adding ourselves as a listener for some strange reason! Skipping");
3109            return;
3110        }
3111        super.addPropertyChangeListener(listener);
3112    }
3113
3114    // TODO - check "NewRoute" - only appears in Bundle strings
3115    @Override
3116    public void propertyChange(PropertyChangeEvent e) {
3117
3118        switch (e.getPropertyName()) {
3119            case "NewRoute": {
3120                updateRouteLog.debug("==Event type {} New {}",
3121                    e.getPropertyName(), ((LayoutBlock) e.getNewValue()).getDisplayName());
3122                break;
3123            }
3124            case PROPERTY_THROUGH_PATH_ADDED: {
3125                updateRouteLog.debug("neighbour has new through path");
3126                break;
3127            }
3128            case PROPERTY_THROUGH_PATH_REMOVED: {
3129                updateRouteLog.debug("neighbour has through removed");
3130                break;
3131            }
3132            case PROPERTY_ROUTING: {
3133                if (e.getSource() instanceof LayoutBlock) {
3134                    LayoutBlock sourceLayoutBlock = (LayoutBlock) e.getSource();
3135                    updateRouteLog.debug("From {} we have a routing packet update from neighbour {}",
3136                        getDisplayName(), sourceLayoutBlock.getDisplayName());
3137                    RoutingPacket update = (RoutingPacket) e.getNewValue();
3138                    int updateType = update.getPacketType();
3139                    switch (updateType) {
3140                        case ADDITION: {
3141                            updateRouteLog.debug("\t    updateType: Addition");
3142                            // InstanceManager.getDefault(
3143                            // LayoutBlockManager.class).setLastRoutingChange();
3144                            addRouteFromNeighbour(sourceLayoutBlock, update);
3145                            break;
3146                        }
3147                        case UPDATE: {
3148                            updateRouteLog.debug("\t    updateType: Update");
3149                            updateRoutingInfo(sourceLayoutBlock, update);
3150                            break;
3151                        }
3152                        case REMOVAL: {
3153                            updateRouteLog.debug("\t    updateType: Removal");
3154                            InstanceManager.getDefault(LayoutBlockManager.class).setLastRoutingChange();
3155                            removeRouteFromNeighbour(sourceLayoutBlock, update);
3156                            break;
3157                        }
3158                        default: {
3159                            break;
3160                        }
3161                    }   // switch (updateType)
3162                }   // if (e.getSource() instanceof LayoutBlock)
3163                break;
3164            }
3165            default: {
3166                log.debug("Unhandled propertyChange({}): ", e);
3167                break;
3168            }
3169        }   // switch (e.getPropertyName())
3170    }   // propertyChange
3171
3172    /**
3173     * Get valid Routes, based upon the next block and destination block
3174     *
3175     * @param nxtBlock next block
3176     * @param dstBlock final block
3177     * @return routes that fit, or null
3178     */
3179    @CheckForNull
3180    Routes getValidRoute(Block nxtBlock, Block dstBlock) {
3181        if ( nxtBlock != null && dstBlock != null ) {
3182            List<Routes> rtr = getRouteByNeighbour(nxtBlock);
3183
3184            if (rtr.isEmpty()) {
3185                log.debug("From {}, no routes returned for getRouteByNeighbour({})",
3186                        this.getDisplayName(),
3187                        nxtBlock.getDisplayName());
3188                return null;
3189            }
3190
3191            for (Routes rt : rtr) {
3192                if (rt.getDestBlock() == dstBlock) {
3193                    log.debug("From {}, found dest {}.", this.getDisplayName(), dstBlock.getDisplayName());
3194                    return rt;
3195                }
3196            }
3197            log.debug("From {}, no routes to {}.", this.getDisplayName(), nxtBlock.getDisplayName());
3198        } else {
3199            log.warn("getValidRoute({}, {}",
3200                    (nxtBlock != null) ? nxtBlock.getDisplayName() : "<null>",
3201                    (dstBlock != null) ? dstBlock.getDisplayName() : "<null>");
3202        }
3203        return null;
3204    }
3205
3206    /**
3207     * Is the route to the destination block, going via our neighbouring block
3208     * valid. ie Does the block have a route registered via neighbour
3209     * "protecting" to the destination block.
3210     *
3211     * @param protecting  neighbour block that might protect
3212     * @param destination block
3213     * @return true if we have valid path to block
3214     */
3215    public boolean isRouteToDestValid(Block protecting, Block destination) {
3216        if (protecting == destination) {
3217            log.debug("protecting and destination blocks are the same "
3218                + "therefore we need to check if we have a valid neighbour");
3219
3220            // We are testing for a directly connected block.
3221            if (getAdjacency(protecting) != null) {
3222                return true;
3223            }
3224        } else if (getValidRoute(protecting, destination) != null) {
3225            return true;
3226        }
3227        return false;
3228    }
3229
3230    /**
3231     * Get a list of valid Routes to our destination block
3232     *
3233     * @param dstBlock target to find
3234     * @return routes between this and dstBlock
3235     */
3236    List<Routes> getDestRoutes(Block dstBlock) {
3237        List<Routes> rtr = new ArrayList<>();
3238        var tempRouteList = new ArrayList<>(routes);
3239        for (Routes route : tempRouteList) {
3240            if (route.getDestBlock() == dstBlock) {
3241                rtr.add(route);
3242            }
3243        }
3244        return rtr;
3245    }
3246
3247    /**
3248     * Get a list of valid Routes via our next block
3249     *
3250     * @param nxtBlock target block
3251     * @return list of routes to target block
3252     */
3253    List<Routes> getNextRoutes(Block nxtBlock) {
3254        List<Routes> rtr = new ArrayList<>();
3255        for (Routes route : routes) {
3256            if (route.getNextBlock() == nxtBlock) {
3257                rtr.add(route);
3258            }
3259        }
3260        return rtr;
3261    }
3262
3263    void updateRoutingInfo(Routes route) {
3264        if (route.getHopCount() >= 254) {
3265            return;
3266        }
3267        Block destBlock = route.getDestBlock();
3268
3269        RoutingPacket update = new RoutingPacket(UPDATE, destBlock, getBestRouteByHop(destBlock).getHopCount() + 1,
3270                ((getBestRouteByMetric(destBlock).getMetric()) + metric),
3271                ((getBestRouteByMetric(destBlock).getMetric())
3272                + block.getLengthMm()), -1,
3273                getNextPacketID());
3274        firePropertyChange(PROPERTY_ROUTING, null, update);
3275    }
3276
3277    /**
3278     * Act on a routing update from a neighbour.
3279     * <p>
3280     * The update only says that something has changed. The metric, hop count
3281     * and length of our route via the neighbour are taken from what the
3282     * neighbour advertises to us now, so an update that arrives late, twice or
3283     * out of order cannot leave the route wrong. If the route changes, the
3284     * routes we advertise to our own neighbours are brought up to date.
3285     *
3286     * @param src    the neighbour that sent the update
3287     * @param update the routing packet
3288     */
3289    void updateRoutingInfo( @Nonnull LayoutBlock src, @Nonnull RoutingPacket update) {
3290        updateRouteLog.debug("From {} src: {}, block: {}, hopCount: {}, metric: {}, status: {}, packetID: {}",
3291            getDisplayName(), src.getDisplayName(), update.getBlock().getDisplayName(),
3292            update.getHopCount(), update.getMetric(), update.getBlockState(), update.getPacketId());
3293        Block srcblk = src.getBlock();
3294        Adjacencies adj = getAdjacency(srcblk);
3295
3296        if (adj == null) {
3297            updateRouteLog.debug("From {} packet is from a src that is not registered {}",
3298                getDisplayName(), srcblk.getDisplayName());
3299            // If the packet is from a src that is not registered as a neighbour
3300            // Then we will simply reject it.
3301            return;
3302        }
3303
3304        updateRouteLog.debug("From {} an Update packet from neighbour {}", getDisplayName(), src.getDisplayName());
3305
3306        Block updateBlock = update.getBlock();
3307
3308        if (updateBlock == this.getBlock()) {
3309            updateRouteLog.debug("Reject packet update as it is a route advertised by our selves");
3310            return;
3311        }
3312
3313        Routes ro;
3314        boolean neighbour = false;
3315        if (updateBlock == srcblk) {
3316            // Very likely that this update is from a neighbour about its own status.
3317            ro = getValidRoute(this.getBlock(), updateBlock);
3318            neighbour = true;
3319        } else {
3320            ro = getValidRoute(srcblk, updateBlock);
3321        }
3322
3323        if (ro == null) {
3324            updateRouteLog.debug("From {} update packet is for a block that we do not have route registered for {}",
3325                getDisplayName(), updateBlock.getDisplayName());
3326            // If the packet is for a dest that is not in the routing table
3327            // Then we will simply reject it.
3328            return;
3329        }
3330
3331        if ((update.getHopCount() != -1) || (update.getMetric() != -1) || ((int) update.getLength() != -1)) {
3332            if (neighbour) {
3333                // the cost of a route straight to the neighbour is the neighbour's own
3334                int oldMetric = ro.getMetric();
3335                float oldLength = ro.getLength();
3336                ro.setMetric(src.getBlockMetric());
3337                ro.setLength(srcblk.getLengthMm());
3338                if (oldMetric != ro.getMetric()) {
3339                    adj.setMetric(ro.getMetric());
3340                }
3341                if (!MathUtil.equals(oldLength, ro.getLength())) {
3342                    adj.setLength(ro.getLength());
3343                }
3344                if ((oldMetric != ro.getMetric()) || !MathUtil.equals(oldLength, ro.getLength())) {
3345                    routeValuesChanged(updateBlock);
3346                }
3347            } else {
3348                RouteCost cost = src.getAdvertisedRoute(this.getBlock(), updateBlock);
3349                if (cost == UNREACHABLE) {
3350                    // the route only goes round a loop now, so withdraw it
3351                    removeRouteFromNeighbour(src, new RoutingPacket(REMOVAL, updateBlock, -1, -1, -1, -1, getNextPacketID()));
3352                    return;
3353                } else if (cost != null) {
3354                    setRouteCost(ro, cost);
3355                }
3356            }
3357        }
3358
3359        int blockstate = update.getBlockState();
3360        if (blockstate != -1) {
3361            // We will update all the destination blocks with the new state, it
3362            // saves re-firing off new updates block status
3363            boolean stateUpdated = false;
3364            List<Routes> rtr = getDestRoutes(updateBlock);
3365
3366            for (Routes rt : rtr) {
3367                if (rt.getState() != blockstate) {
3368                    stateUpdated = true;
3369                    rt.stateChange();
3370                }
3371            }
3372
3373            if (stateUpdated) {
3374                RoutingPacket newUpdate = new RoutingPacket(UPDATE, updateBlock, -1, -1, -1, blockstate, getNextPacketID());
3375                firePropertyChange(PROPERTY_ROUTING, null, newUpdate);
3376            }
3377        }
3378    }
3379
3380    /**
3381     * The metric, hop count and length of a route as advertised to a
3382     * neighbour.
3383     */
3384    private static final class RouteCost {
3385
3386        final int metric;
3387        final int hopCount;
3388        final float length;
3389
3390        RouteCost(int metric, int hopCount, float length) {
3391            this.metric = metric;
3392            this.hopCount = hopCount;
3393            this.length = length;
3394        }
3395    }
3396
3397    // Routes round a reversing loop or a wye can keep each other alive after
3398    // the way to the destination has gone, their hop counts rising each time
3399    // round. Such a route is withdrawn once it reaches the hop count limit.
3400    private static final int MAX_HOP_COUNT = 254;
3401    private static final RouteCost UNREACHABLE = new RouteCost(965000, MAX_HOP_COUNT + 1, Float.MAX_VALUE);
3402
3403    /**
3404     * Get the route to a destination that we advertise to a neighbour: the
3405     * lowest metric, hop count and length of our routes to the destination
3406     * that traffic arriving from the neighbour can take through this block,
3407     * each including this block.
3408     * <p>
3409     * The metric, hop count and length are each the lowest of the usable
3410     * routes, so they may come from different routes, as each is used on its
3411     * own when choosing a route.
3412     *
3413     * @param neighbour the neighbouring block the route is advertised to
3414     * @param dest      the destination block
3415     * @return the advertised route, UNREACHABLE if every usable route has
3416     *         reached the hop count limit and should be withdrawn, or null if
3417     *         traffic from the
3418     *         neighbour can not reach the destination through this block
3419     */
3420    @CheckForNull
3421    private RouteCost getAdvertisedRoute(Block neighbour, Block dest) {
3422        int bestMetric = Integer.MAX_VALUE;
3423        int bestHopCount = Integer.MAX_VALUE;
3424        float bestLength = Float.MAX_VALUE;
3425        boolean found = false;
3426        boolean tooFar = false;
3427        for (Routes ro : routes) {
3428            if (ro.getDestBlock() != dest) {
3429                continue;
3430            }
3431            // a route straight to a neighbour has this block as its next block
3432            Block exit = (ro.getNextBlock() == getBlock()) ? dest : ro.getNextBlock();
3433            if (exit == neighbour || !isThroughPath(neighbour, exit)) {
3434                continue;
3435            }
3436            if (ro.getHopCount() >= MAX_HOP_COUNT) {
3437                // only a route going round a loop gets this long
3438                tooFar = true;
3439                continue;
3440            }
3441            found = true;
3442            bestMetric = Math.min(bestMetric, ro.getMetric());
3443            bestHopCount = Math.min(bestHopCount, ro.getHopCount());
3444            bestLength = Math.min(bestLength, ro.getLength());
3445        }
3446        if (!found) {
3447            return tooFar ? UNREACHABLE : null;
3448        }
3449        return new RouteCost(bestMetric + metric, bestHopCount + 1, bestLength + block.getLengthMm());
3450    }
3451
3452    private boolean isThroughPath(Block sourceBlock, Block destinationBlock) {
3453        for (ThroughPaths throughPath : throughPaths) {
3454            if ((throughPath.getSourceBlock() == sourceBlock) && (throughPath.getDestinationBlock() == destinationBlock)) {
3455                return true;
3456            }
3457        }
3458        return false;
3459    }
3460
3461    /**
3462     * Set the metric, hop count and length of one of our routes and, if any
3463     * changed, bring the routes we advertise to our neighbours up to date.
3464     */
3465    private void setRouteCost(Routes ro, RouteCost cost) {
3466        if ((ro.getMetric() == cost.metric) && (ro.getHopCount() == cost.hopCount)
3467                && MathUtil.equals(ro.getLength(), cost.length)) {
3468            return;
3469        }
3470        updateRouteLog.debug("From {} route to {} via {} changed to metric {}, hops {}, length {}",
3471            getDisplayName(), ro.getDestBlock().getDisplayName(), ro.getNextBlock().getDisplayName(),
3472            cost.metric, cost.hopCount, cost.length);
3473        ro.setMetric(cost.metric);
3474        ro.setHopCount(cost.hopCount);
3475        ro.setLength(cost.length);
3476        routeValuesChanged(ro.getDestBlock());
3477    }
3478
3479    private void routeValuesChanged(Block dest) {
3480        InstanceManager.getDefault(LayoutBlockManager.class).setLastRoutingChange();
3481        advertiseRouteChange(dest);
3482    }
3483
3484    /**
3485     * Bring the route to a destination held by each of our neighbours via
3486     * this block up to date with what we now advertise to that neighbour.
3487     * Only a neighbour whose route changes passes the change on, so this
3488     * stops once every table agrees.
3489     *
3490     * @param dest the destination block
3491     */
3492    void advertiseRouteChange(Block dest) {
3493        for (Adjacencies adj : new ArrayList<>(neighbours)) {
3494            advertiseRouteChange(adj, dest);
3495        }
3496    }
3497
3498    private void advertiseRouteChange(Adjacencies adj, Block dest) {
3499        if (!adj.isMutual()) {
3500            return;
3501        }
3502        LayoutBlock neighbour = adj.getLayoutBlock();
3503        if (neighbour == null || dest == adj.getBlock()) {
3504            return;
3505        }
3506        Routes theirs = neighbour.getValidRoute(getBlock(), dest);
3507        RouteCost cost = getAdvertisedRoute(adj.getBlock(), dest);
3508        if (theirs == null) {
3509            return;
3510        }
3511        if (cost == UNREACHABLE) {
3512            // the route only goes round a loop now, so withdraw it
3513            adj.removeRouteAdvertisedToNeighbour(dest);
3514            neighbour.removeRouteFromNeighbour(this, new RoutingPacket(REMOVAL, dest, -1, -1, -1, -1, getNextPacketID()));
3515        } else if (cost != null) {
3516            neighbour.setRouteCost(theirs, cost);
3517        }
3518    }
3519
3520    /**
3521     * Bring every route our neighbours hold via this block up to date, for
3522     * example after the metric of this block has changed.
3523     */
3524    private void advertiseAllRouteChanges() {
3525        Set<Block> destinations = new LinkedHashSet<>();
3526        for (Routes ro : routes) {
3527            destinations.add(ro.getDestBlock());
3528        }
3529        destinations.forEach(this::advertiseRouteChange);
3530    }
3531
3532    void updateRoutesToNeighbours(List<Block> messageRecipients, Routes ro, RoutingPacket update) {
3533        for (Block messageRecipient : messageRecipients) {
3534            Adjacencies adj = getAdjacency(messageRecipient);
3535            if (adj.advertiseRouteToNeighbour(ro)) {
3536                adj.addRouteAdvertisedToNeighbour(ro);
3537                LayoutBlock recipient = InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlock(messageRecipient);
3538                if (recipient != null) {
3539                    recipient.updateRoutingInfo(this, update);
3540                }
3541            }
3542        }
3543    }
3544
3545    Routes getBestRouteByMetric(Block dest) {
3546        // int bestHopCount = 255;
3547        int bestMetric = 965000;
3548        int bestIndex = -1;
3549
3550        List<Routes> destRoutes = getDestRoutes(dest);
3551        for (int i = 0; i < destRoutes.size(); i++) {
3552            if (destRoutes.get(i).getMetric() < bestMetric) {
3553                bestMetric = destRoutes.get(i).getMetric();
3554                bestIndex = i;
3555            }
3556        }
3557
3558        if (bestIndex == -1) {
3559            return null;
3560        }
3561        return destRoutes.get(bestIndex);
3562    }
3563
3564    Routes getBestRouteByHop(Block dest) {
3565        int bestHopCount = 255;
3566        // int bestMetric = 965000;
3567        int bestIndex = -1;
3568
3569        List<Routes> destRoutes = getDestRoutes(dest);
3570        for (int i = 0; i < destRoutes.size(); i++) {
3571            if (destRoutes.get(i).getHopCount() < bestHopCount) {
3572                bestHopCount = destRoutes.get(i).getHopCount();
3573                bestIndex = i;
3574            }
3575        }
3576
3577        if (bestIndex == -1) {
3578            return null;
3579        }
3580        return destRoutes.get(bestIndex);
3581    }
3582
3583    Routes getBestRouteByLength(Block dest) {
3584        // int bestHopCount = 255;
3585        // int bestMetric = 965000;
3586        // long bestLength = 999999999;
3587        int bestIndex = -1;
3588        List<Routes> destRoutes = getDestRoutes(dest);
3589        float bestLength = Float.MAX_VALUE;
3590
3591        for (int i = 0; i < destRoutes.size(); i++) {
3592            if ((bestIndex == -1) || (destRoutes.get(i).getLength() < bestLength)) {
3593                bestLength = destRoutes.get(i).getLength();
3594                bestIndex = i;
3595            }
3596        }
3597
3598        if (bestIndex == -1) {
3599            return null;
3600        }
3601        return destRoutes.get(bestIndex);
3602    }
3603
3604    void addRouteToNeighbours(Routes ro) {
3605        addRouteLog.debug("From {} Add route to neighbour", getDisplayName());
3606        Block nextHop = ro.getNextBlock();
3607        List<LayoutBlock> validFromPath = new ArrayList<>();
3608
3609        addRouteLog.debug("From {} new block {}", getDisplayName(), nextHop.getDisplayName());
3610
3611        for (int i = 0; i < throughPaths.size(); i++) {
3612            LayoutBlock validBlock = null;
3613
3614            addRouteLog.debug("Through routes index {}", i);
3615            addRouteLog.debug("From {} A through routes {} {}", getDisplayName(),
3616                throughPaths.get(i).getSourceBlock().getDisplayName(),
3617                throughPaths.get(i).getDestinationBlock().getDisplayName());
3618
3619            /*As the through paths include each possible path, ie 2 > 3 and 3 > 2
3620               as seperate entries then we only need to forward the new route to those
3621               source blocks that have a desination of the next hop*/
3622            if (throughPaths.get(i).getDestinationBlock() == nextHop) {
3623                if (getAdjacency(throughPaths.get(i).getSourceBlock()).isMutual()) {
3624                    validBlock = InstanceManager.getDefault(
3625                            LayoutBlockManager.class).
3626                            getLayoutBlock(throughPaths.get(i).getSourceBlock());
3627                }
3628            }
3629
3630            // only need to add it the once.  Not sure if the contains is required.
3631            if ((validBlock != null) && (!validFromPath.contains(validBlock))) {
3632                validFromPath.add(validBlock);
3633            }
3634        }
3635
3636        if ( addRouteLog.isDebugEnabled() ) {
3637            addRouteLog.debug("From {} ===== valid from size path {} ==== (addroutetoneigh)", this.getDisplayName(), validFromPath.size());
3638
3639            validFromPath.forEach( valid -> addRouteLog.debug("fromPath: {}", valid.getDisplayName()));
3640            addRouteLog.debug("Next Hop {}", nextHop.getDisplayName());
3641        }
3642        RoutingPacket update = new RoutingPacket(ADDITION, ro.getDestBlock(), ro.getHopCount() + 1,
3643                ro.getMetric() + metric,
3644                (ro.getLength() + getBlock().getLengthMm()), -1, getNextPacketID());
3645
3646        for (LayoutBlock layoutBlock : validFromPath) {
3647            Adjacencies adj = getAdjacency(layoutBlock.getBlock());
3648            if (adj.advertiseRouteToNeighbour(ro)) {
3649                // getBestRouteByHop(destBlock).getHopCount()+1, ((getBestRouteByMetric(destBlock).getMetric())+metric),
3650                //((getBestRouteByMetric(destBlock).getMetric())+block.getLengthMm())
3651                addRouteLog.debug("From {} Sending update to {} As this has a better hop count or metric",
3652                    getDisplayName(), layoutBlock.getDisplayName());
3653                adj.addRouteAdvertisedToNeighbour(ro);
3654                layoutBlock.addRouteFromNeighbour(this, update);
3655            }
3656        }
3657    }
3658
3659    void addRouteFromNeighbour(LayoutBlock src, RoutingPacket update) {
3660            // log.info("From " + this.getDisplayName() + " packet to be added from neighbour " + src.getDisplayName());
3661            addRouteLog.debug("From {} src: {}, block: {}, hopCount: {}, metric: {}, status: {}, packetID: {}",
3662                getDisplayName(), src.getDisplayName(), update.getBlock().getDisplayName(),
3663                update.getHopCount(), update.getMetric(), update.getBlockState(), update.getPacketId());
3664        InstanceManager.getDefault(LayoutBlockManager.class).setLastRoutingChange();
3665        Block destBlock = update.getBlock();
3666        Block srcblk = src.getBlock();
3667
3668        if (destBlock == this.getBlock()) {
3669            addRouteLog.debug("Reject packet update as it is to a route advertised by our selves");
3670            return;
3671        }
3672
3673        Adjacencies adj = getAdjacency(srcblk);
3674        if (adj == null) {
3675            addRouteLog.debug("From {} packet is from a src that is not registered {}",
3676                getDisplayName(), srcblk.getDisplayName());
3677            // If the packet is from a src that is not registered as a neighbour
3678            // Then we will simply reject it.
3679            return;
3680        } else if (adj.getPacketFlow() == TXONLY) {
3681            addRouteLog.debug("From {} packet is from a src {} that is registered as one that we should be transmitting to only",
3682                getDisplayName(), src.getDisplayName());
3683            // we should only be transmitting routes to this neighbour not receiving them
3684            return;
3685        }
3686        int hopCount = update.getHopCount();
3687        int updatemetric = update.getMetric();
3688        float length = update.getLength();
3689
3690        if (hopCount > 255) {
3691            addRouteLog.debug("From {} hop count exceeded {}", getDisplayName(), destBlock.getDisplayName());
3692            return;
3693        }
3694
3695        for (Routes ro : routes) {
3696            if ((ro.getNextBlock() == srcblk) && (ro.getDestBlock() == destBlock)) {
3697                addRouteLog.debug("From {} Route to {} is already configured",
3698                    getDisplayName(), destBlock.getDisplayName());
3699                addRouteLog.debug("{} v {}", ro.getHopCount(), hopCount);
3700                addRouteLog.debug("{} v {}", ro.getMetric(), updatemetric);
3701                updateRoutingInfo(src, update);
3702                return;
3703            }
3704        }
3705
3706        addRouteLog.debug("From {} We should be adding route {}", getDisplayName(), destBlock.getDisplayName());
3707
3708        // take the cost from what the neighbour advertises to us now, which is
3709        // its best route, rather than from the route this packet was sent for
3710        RouteCost cost = src.getAdvertisedRoute(this.getBlock(), destBlock);
3711        if (cost == UNREACHABLE) {
3712            addRouteLog.debug("From {} route to {} is beyond the hop count limit",
3713                getDisplayName(), destBlock.getDisplayName());
3714            return;
3715        }
3716        if (cost != null) {
3717            hopCount = cost.hopCount;
3718            updatemetric = cost.metric;
3719            length = cost.length;
3720        }
3721
3722        // We need to propergate out the routes that we have added to our neighbour
3723        int direction = adj.getDirection();
3724        Routes route = new Routes(destBlock, srcblk, hopCount, direction, updatemetric, length);
3725        routes.add(route);
3726
3727        // Need to propergate the route down to our neighbours
3728        addRouteToNeighbours(route);
3729        // and update neighbours that already hold a route to the destination via us
3730        advertiseRouteChange(destBlock);
3731    }
3732
3733    /* this should look after removal of a specific next hop from our neighbour*/
3734    /**
3735     * Get the direction of travel to our neighbouring block.
3736     *
3737     * @param neigh neighbor block
3738     * @return direction to get to neighbor block
3739     */
3740    public int getNeighbourDirection(LayoutBlock neigh) {
3741        if (neigh == null) {
3742            return Path.NONE;
3743        }
3744        Block neighbourBlock = neigh.getBlock();
3745        return getNeighbourDirection(neighbourBlock);
3746    }
3747
3748    public int getNeighbourDirection(Block neighbourBlock) {
3749        for (Adjacencies neighbour : neighbours) {
3750            if (neighbour.getBlock() == neighbourBlock) {
3751                return neighbour.getDirection();
3752            }
3753        }
3754        return Path.NONE;
3755    }
3756
3757    Adjacencies getAdjacency(Block blk) {
3758        for (Adjacencies neighbour : neighbours) {
3759            if (neighbour.getBlock() == blk) {
3760                return neighbour;
3761            }
3762        }
3763        return null;
3764    }
3765
3766    static final int ADDITION = 0x00;
3767    static final int UPDATE = 0x02;
3768    static final int REMOVAL = 0x04;
3769
3770    static final int RXTX = 0x00;
3771    static final int RXONLY = 0x02;
3772    static final int TXONLY = 0x04;
3773    static final int NONE = 0x08;
3774    int metric = 100;
3775
3776    private static class RoutingPacket {
3777
3778        int packetType;
3779        Block block;
3780        int hopCount = -1;
3781        int packetMetric = -1;
3782        int blockstate = -1;
3783        float length = -1;
3784        Integer packetRef = -1;
3785
3786        RoutingPacket(int packetType, Block blk, int hopCount, int packetMetric,
3787                float length, int blockstate, Integer packetRef) {
3788            this.packetType = packetType;
3789            this.block = blk;
3790            this.hopCount = hopCount;
3791            this.packetMetric = packetMetric;
3792            this.blockstate = blockstate;
3793            this.packetRef = packetRef;
3794            this.length = length;
3795        }
3796
3797        int getPacketType() {
3798            return packetType;
3799        }
3800
3801        Block getBlock() {
3802            return block;
3803        }
3804
3805        int getHopCount() {
3806            return hopCount;
3807        }
3808
3809        int getMetric() {
3810            return packetMetric;
3811        }
3812
3813        int getBlockState() {
3814            return blockstate;
3815        }
3816
3817        float getLength() {
3818            return length;
3819        }
3820
3821        Integer getPacketId() {
3822            return packetRef;
3823        }
3824    }
3825
3826    /**
3827     * Get the number of neighbor blocks attached to this block.
3828     *
3829     * @return count of neighbor
3830     */
3831    public int getNumberOfNeighbours() {
3832        return neighbours.size();
3833    }
3834
3835    /**
3836     * Get the neighboring block at index i.
3837     *
3838     * @param i index to neighbor
3839     * @return neighbor block
3840     */
3841    public Block getNeighbourAtIndex(int i) {
3842        return neighbours.get(i).getBlock();
3843    }
3844
3845    /**
3846     * Get the direction of travel to neighbouring block at index i.
3847     *
3848     * @param i index in neighbors
3849     * @return neighbor block
3850     */
3851    public int getNeighbourDirection(int i) {
3852        return neighbours.get(i).getDirection();
3853    }
3854
3855    /**
3856     * Get the metric/cost to neighbouring block at index i.
3857     *
3858     * @param i index in neighbors
3859     * @return metric of neighbor
3860     */
3861    public int getNeighbourMetric(int i) {
3862        return neighbours.get(i).getMetric();
3863    }
3864
3865    /**
3866     * Get the flow of traffic to and from neighbouring block at index i RXTX -
3867     * Means Traffic can flow both ways between the blocks RXONLY - Means we can
3868     * only receive traffic from our neighbour, we can not send traffic to it
3869     * TXONLY - Means we do not receive traffic from our neighbour, but can send
3870     * traffic to it.
3871     *
3872     * @param i index in neighbors
3873     * @return direction of traffic
3874     */
3875    public String getNeighbourPacketFlowAsString(int i) {
3876        return decodePacketFlow(neighbours.get(i).getPacketFlow());
3877    }
3878
3879    /**
3880     * Is our neighbouring block at index i a mutual neighbour, ie both blocks
3881     * have each other registered as neighbours and are exchanging information.
3882     *
3883     * @param i index of neighbor
3884     * @return true if both are mutual neighbors
3885     */
3886    public boolean isNeighbourMutual(int i) {
3887        return neighbours.get(i).isMutual();
3888    }
3889
3890    int getNeighbourIndex(Adjacencies adj) {
3891        for (int i = 0; i < neighbours.size(); i++) {
3892            if (neighbours.get(i) == adj) {
3893                return i;
3894            }
3895        }
3896        return -1;
3897    }
3898
3899    private class Adjacencies {
3900
3901        Block adjBlock;
3902        LayoutBlock adjLayoutBlock;
3903        int direction;
3904        int packetFlow = RXTX;
3905        boolean mutualAdjacency = false;
3906
3907        HashMap<Block, Routes> adjDestRoutes = new HashMap<>();
3908
3909        Adjacencies(Block block, int dir, int packetFlow) {
3910            adjBlock = block;
3911            direction = dir;
3912            this.packetFlow = packetFlow;
3913        }
3914
3915        Block getBlock() {
3916            return adjBlock;
3917        }
3918
3919        LayoutBlock getLayoutBlock() {
3920            return adjLayoutBlock;
3921        }
3922
3923        int getDirection() {
3924            return direction;
3925        }
3926
3927        // If a set true on mutual, then we could go through the list of what to send out to neighbour
3928        void setMutual(boolean mut) {
3929            if (mut == mutualAdjacency) {   // No change will exit
3930                return;
3931            }
3932            mutualAdjacency = mut;
3933            if (mutualAdjacency) {
3934                adjLayoutBlock = InstanceManager.getDefault(
3935                        LayoutBlockManager.class).getLayoutBlock(adjBlock);
3936            }
3937        }
3938
3939        boolean isMutual() {
3940            return mutualAdjacency;
3941        }
3942
3943        int getPacketFlow() {
3944            return packetFlow;
3945        }
3946
3947        void setPacketFlow(int flow) {
3948            if (flow != packetFlow) {
3949                int oldFlow = packetFlow;
3950                packetFlow = flow;
3951                firePropertyChange(PROPERTY_NEIGHBOUR_PACKET_FLOW, oldFlow, packetFlow);
3952            }
3953        }
3954
3955        // The metric could just be read directly from the neighbour as we have no
3956        // need to specifically keep a copy of it here this is here just to fire off the change
3957        void setMetric(int met) {
3958            firePropertyChange(PROPERTY_NEIGHBOUR_METRIC, null, getNeighbourIndex(this));
3959        }
3960
3961        int getMetric() {
3962            if (adjLayoutBlock != null) {
3963                return adjLayoutBlock.getBlockMetric();
3964            }
3965            adjLayoutBlock = InstanceManager.getDefault(
3966                    LayoutBlockManager.class).getLayoutBlock(adjBlock);
3967            if (adjLayoutBlock != null) {
3968                return adjLayoutBlock.getBlockMetric();
3969            }
3970
3971            if (log.isDebugEnabled()) {
3972                log.debug("Layout Block {} returned as null", adjBlock.getDisplayName());
3973            }
3974            return -1;
3975        }
3976
3977        void setLength(float len) {
3978            firePropertyChange(PROPERTY_NEIGHBOUR_LENGTH, null, getNeighbourIndex(this));
3979        }
3980
3981        float getLength() {
3982            if (adjLayoutBlock != null) {
3983                return adjLayoutBlock.getBlock().getLengthMm();
3984            }
3985            adjLayoutBlock = InstanceManager.getDefault(
3986                    LayoutBlockManager.class).getLayoutBlock(adjBlock);
3987            if (adjLayoutBlock != null) {
3988                return adjLayoutBlock.getBlock().getLengthMm();
3989            }
3990
3991            if (log.isDebugEnabled()) {
3992                log.debug("Layout Block {} returned as null", adjBlock.getDisplayName());
3993            }
3994            return -1;
3995        }
3996
3997        void removeRouteAdvertisedToNeighbour(Routes removeRoute) {
3998            Block dest = removeRoute.getDestBlock();
3999
4000            if (adjDestRoutes.get(dest) == removeRoute) {
4001                adjDestRoutes.remove(dest);
4002            }
4003        }
4004
4005        void removeRouteAdvertisedToNeighbour(Block block) {
4006            adjDestRoutes.remove(block);
4007        }
4008
4009        void addRouteAdvertisedToNeighbour(Routes addedRoute) {
4010            adjDestRoutes.put(addedRoute.getDestBlock(), addedRoute);
4011        }
4012
4013        boolean advertiseRouteToNeighbour(Routes routeToAdd) {
4014            if (!isMutual()) {
4015                log.debug("In block {}: Neighbour is not mutual so will not advertise it (Routes {})",
4016                    getDisplayName(), routeToAdd);
4017                return false;
4018            }
4019
4020            // Just wonder if this should forward on the new packet to the neighbour?
4021            Block dest = routeToAdd.getDestBlock();
4022            if (!adjDestRoutes.containsKey(dest)) {
4023                log.debug("In block {}: We are not currently advertising a route to the destination to neighbour: {}",
4024                    getDisplayName(), dest.getDisplayName());
4025                return true;
4026            }
4027
4028            if (routeToAdd.getHopCount() > 255) {
4029                log.debug("Hop count is gereater than 255 we will therefore do nothing with this route");
4030                return false;
4031            }
4032            Routes existingRoute = adjDestRoutes.get(dest);
4033            if (existingRoute.getMetric() > routeToAdd.getMetric()) {
4034                return true;
4035            }
4036            if (existingRoute.getHopCount() > routeToAdd.getHopCount()) {
4037                return true;
4038            }
4039
4040            // A change to the metric, hop count or length of a route that the
4041            // neighbour already holds is sent by advertiseRouteChange().
4042            return false;
4043        }
4044
4045        void dispose() {
4046            adjBlock = null;
4047            adjLayoutBlock = null;
4048            mutualAdjacency = false;
4049            adjDestRoutes = null;
4050        }
4051    }
4052
4053    /**
4054     * Get the number of routes that the block has registered.
4055     *
4056     * @return count of routes
4057     */
4058    public int getNumberOfRoutes() {
4059        return routes.size();
4060    }
4061
4062    /**
4063     * Get the direction of route i.
4064     *
4065     * @param i index in routes
4066     * @return direction
4067     */
4068    public int getRouteDirectionAtIndex(int i) {
4069        return routes.get(i).getDirection();
4070    }
4071
4072    /**
4073     * Get the destination block at route i
4074     *
4075     * @param i index in routes
4076     * @return dest block from route
4077     */
4078    public Block getRouteDestBlockAtIndex(int i) {
4079        return routes.get(i).getDestBlock();
4080    }
4081
4082    /**
4083     * Get the next block at route i
4084     *
4085     * @param i index in routes
4086     * @return next block from route
4087     */
4088    public Block getRouteNextBlockAtIndex(int i) {
4089        return routes.get(i).getNextBlock();
4090    }
4091
4092    /**
4093     * Get the hop count of route i.<br>
4094     * The Hop count is the number of other blocks that we traverse to get to
4095     * the destination
4096     *
4097     * @param i index in routes
4098     * @return hop count
4099     */
4100    public int getRouteHopCountAtIndex(int i) {
4101        return routes.get(i).getHopCount();
4102    }
4103
4104    /**
4105     * Get the length of route i.<br>
4106     * The length is the combined length of all the blocks that we traverse to
4107     * get to the destination
4108     *
4109     * @param i index in routes
4110     * @return length of block in route
4111     */
4112    public float getRouteLengthAtIndex(int i) {
4113        return routes.get(i).getLength();
4114    }
4115
4116    /**
4117     * Get the metric/cost at route i
4118     *
4119     * @param i index in routes
4120     * @return metric
4121     */
4122    public int getRouteMetric(int i) {
4123        return routes.get(i).getMetric();
4124    }
4125
4126    /**
4127     * Get the state (Occupied, unoccupied) of the destination layout block at
4128     * index i
4129     *
4130     * @param i index in routes
4131     * @return state of block
4132     */
4133    public int getRouteState(int i) {
4134        return routes.get(i).getState();
4135    }
4136
4137    /**
4138     * Is the route to the destination potentially valid from our block.
4139     *
4140     * @param i index in route
4141     * @return true if route is valid
4142     */
4143    // TODO: Java standard pattern for boolean getters is "isRouteValid()"
4144    public boolean getRouteValid(int i) {
4145        return routes.get(i).isRouteCurrentlyValid();
4146    }
4147
4148    /**
4149     * Get the state of the destination layout block at index i as a string.
4150     *
4151     * @param i index in routes
4152     * @return dest status
4153     */
4154    public String getRouteStateAsString(int i) {
4155        int state = routes.get(i).getState();
4156        switch (state) {
4157            case OCCUPIED: {
4158                return Bundle.getMessage("TrackOccupied"); // i18n using NamedBeanBundle.properties TODO remove duplicate keys
4159            }
4160
4161            case RESERVED: {
4162                return Bundle.getMessage("StateReserved"); // "Reserved"
4163            }
4164
4165            case EMPTY: {
4166                return Bundle.getMessage("StateFree");  // "Free"
4167            }
4168
4169            default: {
4170                return Bundle.getMessage("BeanStateUnknown"); // "Unknown"
4171            }
4172        }
4173    }
4174
4175    int getRouteIndex(Routes r) {
4176        for (int i = 0; i < routes.size(); i++) {
4177            if (routes.get(i) == r) {
4178                return i;
4179            }
4180        }
4181        return -1;
4182    }
4183
4184    /**
4185     * Get the number of layout blocks to our destintation block going from the
4186     * next directly connected block. If the destination block and nextblock are
4187     * the same and the block is also registered as a neighbour then 1 is
4188     * returned. If no valid route to the destination block can be found via the
4189     * next block then -1 is returned. If more than one route exists to the
4190     * destination then the route with the lowest count is returned.
4191     *
4192     * @param destination final block
4193     * @param nextBlock   adjcent block
4194     * @return hop count to final, -1 if not available
4195     */
4196    public int getBlockHopCount(Block destination, Block nextBlock) {
4197        if ((destination == nextBlock) && (isValidNeighbour(nextBlock))) {
4198            return 1;
4199        }
4200
4201        for (Routes route : routes) {
4202            if (route.getDestBlock() == destination) {
4203                if (route.getNextBlock() == nextBlock) {
4204                    return route.getHopCount();
4205                }
4206            }
4207        }
4208        return -1;
4209    }
4210
4211    /**
4212     * Get the metric to our desintation block going from the next directly
4213     * connected block. If the destination block and nextblock are the same and
4214     * the block is also registered as a neighbour then 1 is returned. If no
4215     * valid route to the destination block can be found via the next block then
4216     * -1 is returned. If more than one route exists to the destination then the
4217     * route with the lowest count is returned.
4218     *
4219     * @param destination final block
4220     * @param nextBlock   adjcent block
4221     * @return metric to final block, -1 if not available
4222     */
4223    public int getBlockMetric(Block destination, Block nextBlock) {
4224        if ((destination == nextBlock) && (isValidNeighbour(nextBlock))) {
4225            return 1;
4226        }
4227
4228        for (Routes route : routes) {
4229            if (route.getDestBlock() == destination) {
4230                if (route.getNextBlock() == nextBlock) {
4231                    return route.getMetric();
4232                }
4233            }
4234        }
4235        return -1;
4236    }
4237
4238    /**
4239     * Get the distance to our desintation block going from the next directly
4240     * connected block. If the destination block and nextblock are the same and
4241     * the block is also registered as a neighbour then 1 is returned. If no
4242     * valid route to the destination block can be found via the next block then
4243     * -1 is returned. If more than one route exists to the destination then the
4244     * route with the lowest count is returned.
4245     *
4246     * @param destination final block
4247     * @param nextBlock   adjcent block
4248     * @return length to final, -1 if not viable
4249     */
4250    public float getBlockLength(Block destination, Block nextBlock) {
4251        if ((destination == nextBlock) && (isValidNeighbour(nextBlock))) {
4252            return 1;
4253        }
4254
4255        for (Routes route : routes) {
4256            if (route.getDestBlock() == destination) {
4257                if (route.getNextBlock() == nextBlock) {
4258                    return route.getLength();
4259                }
4260            }
4261        }
4262        return -1;
4263    }
4264
4265    // TODO This needs a propertychange listener adding
4266    private class Routes implements PropertyChangeListener {
4267
4268        int direction;
4269        Block destBlock;
4270        Block nextBlock;
4271        int hopCount;
4272        int routeMetric;
4273        float length;
4274
4275        // int state =-1;
4276        int miscflags = 0x00;
4277        boolean validCurrentRoute = false;
4278
4279        Routes(Block dstBlock, Block nxtBlock, int hop, int dir, int met, float len) {
4280            destBlock = dstBlock;
4281            nextBlock = nxtBlock;
4282            hopCount = hop;
4283            direction = dir;
4284            routeMetric = met;
4285            length = len;
4286            init();
4287        }
4288
4289        /**
4290         * A copy holding only the destination and next block, used in
4291         * temporary lists. It does not listen to the destination block.
4292         */
4293        Routes(Block dstBlock, Block nxtBlock) {
4294            destBlock = dstBlock;
4295            nextBlock = nxtBlock;
4296        }
4297
4298        final void init() {
4299            validCurrentRoute = checkIsRouteOnValidThroughPath(this);
4300            firePropertyChange(PROPERTY_LENGTH, null, null);
4301            destBlock.addPropertyChangeListener(this);
4302        }
4303
4304        /**
4305         * Stop listening to the destination block once the route is removed.
4306         */
4307        void dispose() {
4308            destBlock.removePropertyChangeListener(this);
4309        }
4310
4311        @Override
4312        public String toString() {
4313            return "Routes(dst:" + destBlock + ", nxt:" + nextBlock
4314                    + ", hop:" + hopCount + ", dir:" + direction
4315                    + ", met:" + routeMetric + ", len: " + length + ")";
4316        }
4317
4318        @Override
4319        public void propertyChange(PropertyChangeEvent e) {
4320            if ( Block.PROPERTY_STATE.equals(e.getPropertyName())) {
4321                stateChange();
4322            }
4323        }
4324
4325        public Block getDestBlock() {
4326            return destBlock;
4327        }
4328
4329        public Block getNextBlock() {
4330            return nextBlock;
4331        }
4332
4333        public int getHopCount() {
4334            return hopCount;
4335        }
4336
4337        public int getDirection() {
4338            return direction;
4339        }
4340
4341        public int getMetric() {
4342            return routeMetric;
4343        }
4344
4345        public float getLength() {
4346            return length;
4347        }
4348
4349        public void setMetric(int met) {
4350            if (met == routeMetric) {
4351                return;
4352            }
4353            routeMetric = met;
4354            firePropertyChange(PROPERTY_METRIC, null, getRouteIndex(this));
4355        }
4356
4357        public void setHopCount(int hop) {
4358            if (hopCount == hop) {
4359                return;
4360            }
4361            hopCount = hop;
4362            firePropertyChange(PROPERTY_HOP, null, getRouteIndex(this));
4363        }
4364
4365        public void setLength(float len) {
4366            if (Float.compare(len, length) == 0) {
4367                return;
4368            }
4369            length = len;
4370            firePropertyChange(PROPERTY_LENGTH, null, getRouteIndex(this));
4371        }
4372
4373        // This state change is only here for the routing table view
4374        void stateChange() {
4375            firePropertyChange(PROPERTY_STATE, null, getRouteIndex(this));
4376        }
4377
4378        int getState() {
4379            LayoutBlock destLBlock = InstanceManager.getDefault(
4380                    LayoutBlockManager.class).getLayoutBlock(destBlock);
4381            if (destLBlock != null) {
4382                return destLBlock.getBlockStatus();
4383            }
4384
4385            log.debug("Layout Block {} returned as null", destBlock.getDisplayName());
4386            return -1;
4387        }
4388
4389        void setValidCurrentRoute(boolean boo) {
4390            if (validCurrentRoute == boo) {
4391                return;
4392            }
4393            validCurrentRoute = boo;
4394            firePropertyChange(PROPERTY_VALID, null, getRouteIndex(this));
4395        }
4396
4397        boolean isRouteCurrentlyValid() {
4398            return validCurrentRoute;
4399        }
4400
4401        // Misc flags is not used in general routing, but is used for determining route removals
4402        void setMiscFlags(int f) {
4403            miscflags = f;
4404        }
4405
4406        int getMiscFlags() {
4407            return miscflags;
4408        }
4409    }
4410
4411    /**
4412     * Get the number of valid through paths on this block.
4413     *
4414     * @return count of paths through this block
4415     */
4416    public int getNumberOfThroughPaths() {
4417        return throughPaths.size();
4418    }
4419
4420    /**
4421     * Get the source block at index i
4422     *
4423     * @param i index in throughPaths
4424     * @return source block
4425     */
4426    public Block getThroughPathSource(int i) {
4427        return throughPaths.get(i).getSourceBlock();
4428    }
4429
4430    /**
4431     * Get the destination block at index i
4432     *
4433     * @param i index in throughPaths
4434     * @return final block
4435     */
4436    public Block getThroughPathDestination(int i) {
4437        return throughPaths.get(i).getDestinationBlock();
4438    }
4439
4440    /**
4441     * Is the through path at index i active?
4442     *
4443     * @param i index in path
4444     * @return active or not
4445     */
4446    public Boolean isThroughPathActive(int i) {
4447        return throughPaths.get(i).isPathActive();
4448    }
4449
4450    // We keep a track of what paths through this block are active, only so that
4451    // we can easily mark which routes are also potentially valid
4452    private List<ThroughPaths> activePaths;
4453
4454    private class ThroughPaths implements PropertyChangeListener {
4455
4456        Block sourceBlock;
4457        Block destinationBlock;
4458        Path sourcePath;
4459        Path destinationPath;
4460
4461        boolean pathActive = false;
4462
4463        HashMap<Turnout, Integer> _turnouts = new HashMap<>();
4464
4465        ThroughPaths(Block srcBlock, Path srcPath, Block destBlock, Path dstPath) {
4466            sourceBlock = srcBlock;
4467            destinationBlock = destBlock;
4468            sourcePath = srcPath;
4469            destinationPath = dstPath;
4470        }
4471
4472        Block getSourceBlock() {
4473            return sourceBlock;
4474        }
4475
4476        Block getDestinationBlock() {
4477            return destinationBlock;
4478        }
4479
4480        Path getSourcePath() {
4481            return sourcePath;
4482        }
4483
4484        Path getDestinationPath() {
4485            return destinationPath;
4486        }
4487
4488        boolean isPathActive() {
4489            return pathActive;
4490        }
4491
4492        void setTurnoutList(List<LayoutTrackExpectedState<LayoutTurnout>> turnouts) {
4493            if (!_turnouts.isEmpty()) {
4494                Set<Turnout> en = _turnouts.keySet();
4495                en.forEach( listTurnout -> listTurnout.removePropertyChangeListener(this));
4496            }
4497
4498            // If we have no turnouts in this path, then this path is always active
4499            if (turnouts.isEmpty()) {
4500                pathActive = true;
4501                updateActiveThroughPaths(this, true);
4502                return;
4503            }
4504            _turnouts = new HashMap<>(turnouts.size());
4505            for (LayoutTrackExpectedState<LayoutTurnout> turnout : turnouts) {
4506                if (turnout.getObject() instanceof LayoutSlip) {
4507                    int slipState = turnout.getExpectedState();
4508                    LayoutSlip ls = (LayoutSlip) turnout.getObject();
4509                    int taState = ls.getTurnoutState(slipState);
4510                    _turnouts.put(ls.getTurnout(), taState);
4511                    ls.getTurnout().addPropertyChangeListener(this, ls.getTurnoutName(), "Layout Block Routing");
4512
4513                    int tbState = ls.getTurnoutBState(slipState);
4514                    _turnouts.put(ls.getTurnoutB(), tbState);
4515                    ls.getTurnoutB().addPropertyChangeListener(this, ls.getTurnoutBName(), "Layout Block Routing");
4516                } else {
4517                    LayoutTurnout lt = turnout.getObject();
4518                    if (lt.getTurnout() != null) {
4519                        _turnouts.put(lt.getTurnout(), turnout.getExpectedState());
4520                        lt.getTurnout().addPropertyChangeListener(this, lt.getTurnoutName(), "Layout Block Routing");
4521                    } else {
4522                        log.error("{} has no physical turnout allocated, block = {}", lt, block.getDisplayName());
4523                    }
4524                }
4525            }
4526
4527            // The turnouts may already be set, in which case no event will tell us
4528            boolean allset = !_turnouts.isEmpty();
4529            for (Map.Entry<Turnout, Integer> entry : _turnouts.entrySet()) {
4530                if (entry.getKey().getKnownState() != entry.getValue()) {
4531                    allset = false;
4532                    break;
4533                }
4534            }
4535            if (allset != pathActive) {
4536                updateActiveThroughPaths(this, allset);
4537                pathActive = allset;
4538            }
4539        }
4540
4541        /**
4542         * Stop listening to the turnouts once the through path is removed.
4543         */
4544        void dispose() {
4545            _turnouts.keySet().forEach( listTurnout -> listTurnout.removePropertyChangeListener(this));
4546            if (pathActive) {
4547                updateActiveThroughPaths(this, false);
4548                pathActive = false;
4549            } else if (activePaths != null) {
4550                activePaths.remove(this);
4551            }
4552        }
4553
4554        @Override
4555        public void propertyChange(PropertyChangeEvent e) {
4556            if ( Turnout.PROPERTY_KNOWN_STATE.equals(e.getPropertyName())) {
4557                Turnout srcTurnout = (Turnout) e.getSource();
4558                int newVal = (Integer) e.getNewValue();
4559                int values = _turnouts.get(srcTurnout);
4560                boolean allset = false;
4561                pathActive = false;
4562
4563                if (newVal == values) {
4564                    allset = true;
4565
4566                    if (_turnouts.size() > 1) {
4567                        for (Map.Entry<Turnout, Integer> entry : _turnouts.entrySet()) {
4568                            if (srcTurnout != entry.getKey()) {
4569                                int state = entry.getKey().getKnownState();
4570                                if (state != entry.getValue()) {
4571                                    allset = false;
4572                                    break;
4573                                }
4574                            }
4575                        }
4576                    }
4577                }
4578                updateActiveThroughPaths(this, allset);
4579                pathActive = allset;
4580            }
4581        }
4582
4583        private void updateActiveThroughPaths(ThroughPaths tp, boolean active) {
4584            updateRouteLog.debug("We have been notified that a through path has changed state");
4585
4586            if (activePaths == null) {
4587                activePaths = new ArrayList<>();
4588            }
4589
4590            if (active) {
4591                if (!activePaths.contains(tp)) {
4592                    activePaths.add(tp);
4593                }
4594                setRoutesValid(tp.getSourceBlock(), active);
4595                setRoutesValid(tp.getDestinationBlock(), active);
4596            } else {
4597                // We need to check if either our source or des is in use by another path.
4598                activePaths.remove(tp);
4599                boolean sourceInUse = false;
4600                boolean destinationInUse = false;
4601
4602                List<ThroughPaths> copyOfPaths = activePaths;
4603                for (ThroughPaths activePath : copyOfPaths) {
4604                    Block testSour = activePath.getSourceBlock();
4605                    Block testDest = activePath.getDestinationBlock();
4606                    if ((testSour == tp.getSourceBlock()) || (testDest == tp.getSourceBlock())) {
4607                        sourceInUse = true;
4608                    }
4609                    if ((testSour == tp.getDestinationBlock()) || (testDest == tp.getDestinationBlock())) {
4610                        destinationInUse = true;
4611                    }
4612                }
4613
4614                if (!sourceInUse) {
4615                    setRoutesValid(tp.getSourceBlock(), active);
4616                }
4617
4618                if (!destinationInUse) {
4619                    setRoutesValid(tp.getDestinationBlock(), active);
4620                }
4621            }
4622
4623            for (int i = 0; i < throughPaths.size(); i++) {
4624                // This is processed simply for the throughpath table.
4625                if (tp == throughPaths.get(i)) {
4626                    firePropertyChange(PROPERTY_PATH, null, i);
4627                }
4628            }
4629        }
4630
4631    }
4632
4633    @Nonnull
4634    List<Block> getThroughPathSourceByDestination(Block dest) {
4635        List<Block> a = new ArrayList<>();
4636
4637        for (ThroughPaths throughPath : throughPaths) {
4638            if (throughPath.getDestinationBlock() == dest) {
4639                a.add(throughPath.getSourceBlock());
4640            }
4641        }
4642        return a;
4643    }
4644
4645    @Nonnull
4646    List<Block> getThroughPathDestinationBySource(Block source) {
4647        List<Block> a = new ArrayList<>();
4648
4649        for (ThroughPaths throughPath : throughPaths) {
4650            if (throughPath.getSourceBlock() == source) {
4651                a.add(throughPath.getDestinationBlock());
4652            }
4653        }
4654        return a;
4655    }
4656
4657    /**
4658     * When a route is created, check to see if the through path that this route
4659     * relates to is active.
4660     * @param r The route to check
4661     * @return true if that route is active
4662     */
4663    boolean checkIsRouteOnValidThroughPath(Routes r) {
4664        for (ThroughPaths t : throughPaths) {
4665            if (t.isPathActive()) {
4666                if (t.getDestinationBlock() == r.getNextBlock()) {
4667                    return true;
4668                }
4669                if (t.getSourceBlock() == r.getNextBlock()) {
4670                    return true;
4671                }
4672            }
4673        }
4674        return false;
4675    }
4676
4677    /**
4678     * Go through all the routes and refresh the valid flag.
4679     */
4680    public void refreshValidRoutes() {
4681        for (int i = 0; i < throughPaths.size(); i++) {
4682            ThroughPaths t = throughPaths.get(i);
4683            setRoutesValid(t.getDestinationBlock(), t.isPathActive());
4684            setRoutesValid(t.getSourceBlock(), t.isPathActive());
4685            firePropertyChange(PROPERTY_PATH, null, i);
4686        }
4687    }
4688
4689    /**
4690     * Set the valid flag for routes that are on a valid through path.
4691     * @param nxtHopActive the start of the route
4692     * @param state the state to set into the valid flag
4693     */
4694    void setRoutesValid(Block nxtHopActive, boolean state) {
4695        List<Routes> rtr = getRouteByNeighbour(nxtHopActive);
4696        rtr.forEach( rt -> rt.setValidCurrentRoute(state));
4697    }
4698
4699    @Override
4700    public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException {
4701        if (Manager.PROPERTY_CAN_DELETE.equals(evt.getPropertyName())) {
4702            if (evt.getOldValue() instanceof Sensor) {
4703                if (evt.getOldValue().equals(getOccupancySensor())) {
4704                    throw new PropertyVetoException(getDisplayName(), evt);
4705                }
4706            }
4707
4708            if (evt.getOldValue() instanceof Memory) {
4709                if (evt.getOldValue().equals(getMemory())) {
4710                    throw new PropertyVetoException(getDisplayName(), evt);
4711                }
4712            }
4713        } else if (Manager.PROPERTY_DO_DELETE.equals(evt.getPropertyName())) {
4714            // Do nothing at this stage
4715            if (evt.getOldValue() instanceof Sensor) {
4716                if (evt.getOldValue().equals(getOccupancySensor())) {
4717                    setOccupancySensorName(null);
4718                }
4719            }
4720
4721            if (evt.getOldValue() instanceof Memory) {
4722                if (evt.getOldValue().equals(getMemory())) {
4723                    setMemoryName(null);
4724                }
4725            }
4726        }
4727    }
4728
4729    @Override
4730    public List<NamedBeanUsageReport> getUsageReport(NamedBean bean) {
4731        List<NamedBeanUsageReport> report = new ArrayList<>();
4732        if (bean != null) {
4733            if (bean.equals(getBlock())) {
4734                report.add(new NamedBeanUsageReport("LayoutBlockBlock"));  // NOI18N
4735            }
4736            if (bean.equals(getMemory())) {
4737                report.add(new NamedBeanUsageReport("LayoutBlockMemory"));  // NOI18N
4738            }
4739            if (bean.equals(getOccupancySensor())) {
4740                report.add(new NamedBeanUsageReport("LayoutBlockSensor"));  // NOI18N
4741            }
4742            for (int i = 0; i < getNumberOfNeighbours(); i++) {
4743                if (bean.equals(getNeighbourAtIndex(i))) {
4744                    report.add(new NamedBeanUsageReport("LayoutBlockNeighbor", "Neighbor"));  // NOI18N
4745                }
4746            }
4747        }
4748        return report;
4749    }
4750
4751    @Override
4752    public String getBeanType() {
4753        return Bundle.getMessage("BeanNameLayoutBlock");
4754    }
4755
4756    private static final Logger log = LoggerFactory.getLogger(LayoutBlock.class);
4757    private static final Logger searchRouteLog = LoggerFactory.getLogger(LayoutBlock.class.getName()+".SearchRouteLogging");
4758    private static final Logger updateRouteLog = LoggerFactory.getLogger(LayoutBlock.class.getName()+".UpdateRouteLogging");
4759    private static final Logger addRouteLog = LoggerFactory.getLogger(LayoutBlock.class.getName()+".AddRouteLogging");
4760    private static final Logger deleteRouteLog = LoggerFactory.getLogger(LayoutBlock.class.getName()+".DeleteRouteLogging");
4761
4762}