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