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