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        synchronized (updateReferences) {
2600            if (updateReferences.isEmpty()) {
2601                lastID = 0;
2602            } else {
2603                int lastIDPos = updateReferences.size() - 1;
2604                lastID = updateReferences.get(lastIDPos) + 1;
2605            }
2606    
2607            if (lastID > 2000) {
2608                lastID = 0;
2609            }
2610            updateReferences.add(lastID);
2611        }
2612
2613        /*As we are originating a packet, we will added to the acted upion list
2614         thus making sure if the packet gets back to us we do knowing with it.*/
2615        actedUponUpdates.add(lastID);
2616
2617        synchronized (updateReferences) {
2618            if (updateReferences.size() > 500) {
2619                // log.info("flush update references");
2620                updateReferences.subList(0, 250).clear();
2621            }
2622        }
2623
2624        if (actedUponUpdates.size() > 500) {
2625            actedUponUpdates.subList(0, 250).clear();
2626        }
2627        return lastID;
2628    }
2629
2630    boolean updatePacketActedUpon(Integer packetID) {
2631        return actedUponUpdates.contains(packetID);
2632    }
2633
2634    public List<Block> getActiveNextBlocks(Block source) {
2635        List<Block> currentPath = new ArrayList<>();
2636
2637        for (ThroughPaths path : throughPaths) {
2638            if ((path.getSourceBlock() == source) && (path.isPathActive())) {
2639                currentPath.add(path.getDestinationBlock());
2640            }
2641        }
2642        return currentPath;
2643    }
2644
2645    public Path getThroughPathSourcePathAtIndex(int i) {
2646        return throughPaths.get(i).getSourcePath();
2647    }
2648
2649    public Path getThroughPathDestinationPathAtIndex(int i) {
2650        return throughPaths.get(i).getDestinationPath();
2651    }
2652
2653    public boolean validThroughPath(Block sourceBlock, Block destinationBlock) {
2654        for (ThroughPaths throughPath : throughPaths) {
2655            if ((throughPath.getSourceBlock() == sourceBlock) && (throughPath.getDestinationBlock() == destinationBlock)) {
2656                return true;
2657            } else if ((throughPath.getSourceBlock() == destinationBlock) && (throughPath.getDestinationBlock() == sourceBlock)) {
2658                return true;
2659            }
2660        }
2661        return false;
2662    }
2663
2664    public int getThroughPathIndex(Block sourceBlock, Block destinationBlock) {
2665        for (int i = 0; i < throughPaths.size(); i++) {
2666            if ((throughPaths.get(i).getSourceBlock() == sourceBlock)
2667                    && (throughPaths.get(i).getDestinationBlock() == destinationBlock)) {
2668                return i;
2669            } else if ((throughPaths.get(i).getSourceBlock() == destinationBlock)
2670                    && (throughPaths.get(i).getDestinationBlock() == sourceBlock)) {
2671                return i;
2672            }
2673        }
2674        return -1;
2675    }
2676
2677    private final List<Adjacencies> neighbours = new ArrayList<>();
2678
2679    private final List<ThroughPaths> throughPaths = new ArrayList<>();
2680
2681    // A sub class that holds valid routes through the block.
2682    // Possibly want to store the path direction in here as well.
2683    // or we store the ref to the path, so we can get the directions.
2684    private final List<Routes> routes = new ArrayList<>();
2685
2686    String decodePacketFlow(int value) {
2687        switch (value) {
2688            case RXTX: {
2689                return "Bi-Direction Operation";
2690            }
2691
2692            case RXONLY: {
2693                return "Uni-Directional - Trains can only exit to this block (RX) ";
2694            }
2695
2696            case TXONLY: {
2697                return "Uni-Directional - Trains can not be sent down this block (TX) ";
2698            }
2699
2700            case NONE: {
2701                return "None routing updates will be passed";
2702            }
2703            default:
2704                log.warn("Unhandled packet flow value: {}", value);
2705                break;
2706        }
2707        return "Unknown";
2708    }
2709
2710    /**
2711     * Provide an output to the console of all the valid paths through this
2712     * block.
2713     */
2714    public void printValidThroughPaths() {
2715        log.info("Through paths for block {}", this.getDisplayName());
2716        log.info("Current Block, From Block, To Block");
2717        for (ThroughPaths tp : throughPaths) {
2718            String activeStr = "";
2719            if (tp.isPathActive()) {
2720                activeStr = ", *";
2721            }
2722            log.info("From {}, {}, {}{}", this.getDisplayName(),
2723                (tp.getSourceBlock()).getDisplayName(), (tp.getDestinationBlock()).getDisplayName(), activeStr);
2724        }
2725    }
2726
2727    /**
2728     * Provide an output to the console of all our neighbouring blocks.
2729     */
2730    public void printAdjacencies() {
2731        log.info("Adjacencies for block {}", this.getDisplayName());
2732        log.info("Neighbour, Direction, mutual, relationship, metric");
2733        for (Adjacencies neighbour : neighbours) {
2734            log.info(" neighbor: {}, {}, {}, {}, {}", neighbour.getBlock().getDisplayName(),
2735                Path.decodeDirection(neighbour.getDirection()), neighbour.isMutual(),
2736                decodePacketFlow(neighbour.getPacketFlow()), neighbour.getMetric());
2737        }
2738    }
2739
2740    /**
2741     * Provide an output to the console of all the remote blocks reachable from
2742     * our block.
2743     */
2744    public void printRoutes() {
2745        log.info("Routes for block {}", this.getDisplayName());
2746        log.info("Destination, Next Block, Hop Count, Direction, State, Metric");
2747        for (Routes r : routes) {
2748            String nexthop = r.getNextBlock().getDisplayName();
2749
2750            if (r.getNextBlock() == this.getBlock()) {
2751                nexthop = "Directly Connected";
2752            }
2753            String activeString = "";
2754            if (r.isRouteCurrentlyValid()) {
2755                activeString = ", *";
2756            }
2757
2758            log.info(" neighbor: {}, {}, {}, {}, {}, {}{}", r.getDestBlock().getDisplayName(),
2759                nexthop, r.getHopCount(), Path.decodeDirection(r.getDirection()),
2760                r.getState(), r.getMetric(), activeString);
2761        }
2762    }
2763
2764    /**
2765     * Provide an output to the console of how to reach a specific block from
2766     * our block.
2767     *
2768     * @param inBlockName to find in route
2769     */
2770    public void printRoutes(String inBlockName) {
2771        log.info("Routes for block {}", this.getDisplayName());
2772        log.info("Our Block, Destination, Next Block, Hop Count, Direction, Metric");
2773        for (Routes route : routes) {
2774            if (route.getDestBlock().getDisplayName().equals(inBlockName)) {
2775                log.info("From {}, {}, {}, {}, {}, {}",
2776                    getDisplayName(), (route.getDestBlock()).getDisplayName(),
2777                    route.getNextBlock().getDisplayName(), route.getHopCount(),
2778                    Path.decodeDirection(route.getDirection()), route.getMetric());
2779            }
2780        }
2781    }
2782
2783    /**
2784     * @param destBlock is the destination of the block we are following
2785     * @param direction is the direction of travel from the previous block
2786     * @return next block
2787     */
2788    public Block getNextBlock(Block destBlock, int direction) {
2789        int bestMetric = 965000;
2790        Block bestBlock = null;
2791
2792        for (Routes r : routes) {
2793            if ((r.getDestBlock() == destBlock) && (r.getDirection() == direction)) {
2794                if (r.getMetric() < bestMetric) {
2795                    bestMetric = r.getMetric();
2796                    bestBlock = r.getNextBlock();
2797                    // bestBlock=r.getDestBlock();
2798                }
2799            }
2800        }
2801        return bestBlock;
2802    }
2803
2804    /**
2805     * Used if we already know the block prior to our block, and the destination
2806     * block. direction, is optional and is used where the previousBlock is
2807     * equal to our block.
2808     *
2809     * @param previousBlock start block
2810     * @param destBlock     finish block
2811     * @return next block
2812     */
2813    @CheckForNull
2814    public Block getNextBlock(Block previousBlock, Block destBlock) {
2815        int bestMetric = 965000;
2816        Block bestBlock = null;
2817
2818        for (Routes r : routes) {
2819            if (r.getDestBlock() == destBlock) {
2820                // Check that the route through from the previous block, to the next hop is valid
2821                if (validThroughPath(previousBlock, r.getNextBlock())) {
2822                    if (r.getMetric() < bestMetric) {
2823                        bestMetric = r.getMetric();
2824                        // bestBlock=r.getDestBlock();
2825                        bestBlock = r.getNextBlock();
2826                    }
2827                }
2828            }
2829        }
2830        return bestBlock;
2831    }
2832
2833    public int getConnectedBlockRouteIndex(Block destBlock, int direction) {
2834        for (int i = 0; i < routes.size(); i++) {
2835            if (routes.get(i).getNextBlock() == this.getBlock()) {
2836                log.info("Found a block that is directly connected");
2837
2838                if ((routes.get(i).getDestBlock() == destBlock)) {
2839                    log.info("In getConnectedBlockRouteIndex,  {}",
2840                        Integer.toString(routes.get(i).getDirection() & direction));
2841                    if ((routes.get(i).getDirection() & direction) != 0) {
2842                        return i;
2843                    }
2844                }
2845            }
2846
2847            if (log.isDebugEnabled()) {
2848                log.debug("From {}, {}, nexthop {}, {}, {}, {}", getDisplayName(),
2849                    routes.get(i).getDestBlock().getDisplayName(),
2850                    routes.get(i).getHopCount(),
2851                    Path.decodeDirection(routes.get(i).getDirection()),
2852                    routes.get(i).getState(), routes.get(i).getMetric());
2853            }
2854        }
2855        return -1;
2856    }
2857
2858    // Need to work on this to deal with the method of routing
2859    public int getNextBlockByIndex(Block destBlock, int direction, int offSet) {
2860        for (int i = offSet; i < routes.size(); i++) {
2861            Routes ro = routes.get(i);
2862            if ((ro.getDestBlock() == destBlock)) {
2863                log.info("getNextBlockByIndex {}", Integer.toString(ro.getDirection() & direction));
2864                if ((ro.getDirection() & direction) != 0) {
2865                    return i;
2866                }
2867            }
2868        }
2869        return -1;
2870    }
2871
2872    // Need to work on this to deal with the method of routing
2873    /*
2874     *
2875     */
2876    public int getNextBlockByIndex(Block previousBlock, Block destBlock, int offSet) {
2877        for (int i = offSet; i < routes.size(); i++) {
2878            Routes ro = routes.get(i);
2879            // log.info(r.getDestBlock().getDisplayName() + " vs " + destBlock.getDisplayName());
2880            if (ro.getDestBlock() == destBlock) {
2881                // Check that the route through from the previous block, to the next hop is valid
2882                if (validThroughPath(previousBlock, ro.getNextBlock())) {
2883                    log.debug("valid through path");
2884                    return i;
2885                }
2886
2887                if (ro.getNextBlock() == this.getBlock()) {
2888                    log.debug("getNextBlock is this block therefore directly connected");
2889                    return i;
2890                }
2891            }
2892        }
2893        return -1;
2894    }
2895
2896    /**
2897     * last index - the index of the last block we returned ie we last returned
2898     * index 10, so we don't want to return it again. The block returned will
2899     * have a hopcount or metric equal to or greater than the one of the last
2900     * block returned. if the exclude block list is empty this is the first
2901     * time, it has been used. The parameters for the best last block are based
2902     * upon the last entry in the excludedBlock list.
2903     *
2904     * @param previousBlock starting block
2905     * @param destBlock     finish block
2906     * @param excludeBlock  blocks to skip
2907     * @param routingMethod value to match metric
2908     * @return next block
2909     */
2910    public int getNextBestBlock(Block previousBlock, Block destBlock, List<Integer> excludeBlock, LayoutBlockConnectivityTools.Metric routingMethod) {
2911        searchRouteLog.debug("From {} find best route from {} to {} index {} routingMethod {}",
2912            getDisplayName(), previousBlock.getDisplayName(), destBlock.getDisplayName(), excludeBlock, routingMethod);
2913
2914        int bestCount = 965255; // set stupidly high
2915        int bestIndex = -1;
2916        int lastValue = 0;
2917        List<Block> nextBlocks = new ArrayList<>(5);
2918        if (!excludeBlock.isEmpty() && (excludeBlock.get(excludeBlock.size() - 1) < routes.size())) {
2919            if (routingMethod == LayoutBlockConnectivityTools.Metric.METRIC) {
2920                lastValue = routes.get(excludeBlock.get(excludeBlock.size() - 1)).getMetric();
2921            } else /* if (routingMethod==LayoutBlockManager.HOPCOUNT)*/ {
2922                lastValue = routes.get(excludeBlock.get(excludeBlock.size() - 1)).getHopCount();
2923            }
2924
2925            for (int i : excludeBlock) {
2926                nextBlocks.add(routes.get(i).getNextBlock());
2927            }
2928
2929            searchRouteLog.debug("last index is {} {}", excludeBlock.get(excludeBlock.size() - 1),
2930                routes.get(excludeBlock.get(excludeBlock.size() - 1)).getDestBlock().getDisplayName());
2931        }
2932
2933        for (int i = 0; i < routes.size(); i++) {
2934            if (!excludeBlock.contains(i)) {
2935                Routes ro = routes.get(i);
2936                if (!nextBlocks.contains(ro.getNextBlock())) {
2937                    // if(ro.getNextBlock()!=nextBlock){
2938                    int currentValue;
2939                    if (routingMethod == LayoutBlockConnectivityTools.Metric.METRIC) {
2940                        currentValue = routes.get(i).getMetric();
2941                    } else /*if (routingMethod==InstanceManager.getDefault(
2942                        LayoutBlockManager.class).HOPCOUNT)*/ {
2943                        currentValue = routes.get(i).getHopCount();  // was lastindex changed to i
2944                    }
2945
2946                    if (currentValue >= lastValue) {
2947                        if (ro.getDestBlock() == destBlock) {
2948                            searchRouteLog.debug("Match on dest blocks");
2949                            // Check that the route through from the previous block, to the next hop is valid
2950                            searchRouteLog.debug("Is valid through path previous block {} to {}",
2951                                previousBlock.getDisplayName(), ro.getNextBlock().getDisplayName());
2952
2953                            if (validThroughPath(previousBlock, ro.getNextBlock())) {
2954                                searchRouteLog.debug("valid through path");
2955
2956                                if (routingMethod == LayoutBlockConnectivityTools.Metric.METRIC) {
2957                                    if (ro.getMetric() < bestCount) {
2958                                        bestIndex = i;
2959                                        bestCount = ro.getMetric();
2960                                    }
2961                                } else /*if (routingMethod==InstanceManager.getDefault(
2962                                    LayoutBlockManager.class).HOPCOUNT)*/ {
2963                                    if (ro.getHopCount() < bestCount) {
2964                                        bestIndex = i;
2965                                        bestCount = ro.getHopCount();
2966                                    }
2967                                }
2968                            }
2969
2970                            if (ro.getNextBlock() == this.getBlock()) {
2971                                searchRouteLog.debug("getNextBlock is this block therefore directly connected");
2972                                return i;
2973                            }
2974                        }
2975                    }
2976                }
2977            }
2978        }
2979
2980        searchRouteLog.debug("returning {} best count {}", bestIndex, bestCount);
2981        return bestIndex;
2982    }
2983
2984    @CheckForNull
2985    Routes getRouteByDestBlock(Block blk) {
2986        for (int i = routes.size() - 1; i > -1; i--) {
2987            if (routes.get(i).getDestBlock() == blk) {
2988                return routes.get(i);
2989            }
2990        }
2991        return null;
2992    }
2993
2994    @Nonnull
2995    List<Routes> getRouteByNeighbour(Block blk) {
2996        List<Routes> rtr = new ArrayList<>();
2997        for (Routes route : routes) {
2998            if (route.getNextBlock() == blk) {
2999                rtr.add(route);
3000            }
3001        }
3002        return rtr;
3003    }
3004
3005    int getAdjacencyPacketFlow(Block blk) {
3006        for (Adjacencies neighbour : neighbours) {
3007            if (neighbour.getBlock() == blk) {
3008                return neighbour.getPacketFlow();
3009            }
3010        }
3011        return -1;
3012    }
3013
3014    boolean isValidNeighbour(Block blk) {
3015        for (Adjacencies neighbour : neighbours) {
3016            if (neighbour.getBlock() == blk) {
3017                return true;
3018            }
3019        }
3020        return false;
3021    }
3022
3023    @Override
3024    public synchronized void addPropertyChangeListener(PropertyChangeListener listener) {
3025        if (listener == this) {
3026            log.debug("adding ourselves as a listener for some strange reason! Skipping");
3027            return;
3028        }
3029        super.addPropertyChangeListener(listener);
3030    }
3031
3032    // TODO - check "NewRoute" - only appears in Bundle strings
3033    @Override
3034    public void propertyChange(PropertyChangeEvent e) {
3035
3036        switch (e.getPropertyName()) {
3037            case "NewRoute": {
3038                updateRouteLog.debug("==Event type {} New {}",
3039                    e.getPropertyName(), ((LayoutBlock) e.getNewValue()).getDisplayName());
3040                break;
3041            }
3042            case PROPERTY_THROUGH_PATH_ADDED: {
3043                updateRouteLog.debug("neighbour has new through path");
3044                break;
3045            }
3046            case PROPERTY_THROUGH_PATH_REMOVED: {
3047                updateRouteLog.debug("neighbour has through removed");
3048                break;
3049            }
3050            case PROPERTY_ROUTING: {
3051                if (e.getSource() instanceof LayoutBlock) {
3052                    LayoutBlock sourceLayoutBlock = (LayoutBlock) e.getSource();
3053                    updateRouteLog.debug("From {} we have a routing packet update from neighbour {}",
3054                        getDisplayName(), sourceLayoutBlock.getDisplayName());
3055                    RoutingPacket update = (RoutingPacket) e.getNewValue();
3056                    int updateType = update.getPacketType();
3057                    switch (updateType) {
3058                        case ADDITION: {
3059                            updateRouteLog.debug("\t    updateType: Addition");
3060                            // InstanceManager.getDefault(
3061                            // LayoutBlockManager.class).setLastRoutingChange();
3062                            addRouteFromNeighbour(sourceLayoutBlock, update);
3063                            break;
3064                        }
3065                        case UPDATE: {
3066                            updateRouteLog.debug("\t    updateType: Update");
3067                            updateRoutingInfo(sourceLayoutBlock, update);
3068                            break;
3069                        }
3070                        case REMOVAL: {
3071                            updateRouteLog.debug("\t    updateType: Removal");
3072                            InstanceManager.getDefault(LayoutBlockManager.class).setLastRoutingChange();
3073                            removeRouteFromNeighbour(sourceLayoutBlock, update);
3074                            break;
3075                        }
3076                        default: {
3077                            break;
3078                        }
3079                    }   // switch (updateType)
3080                }   // if (e.getSource() instanceof LayoutBlock)
3081                break;
3082            }
3083            default: {
3084                log.debug("Unhandled propertyChange({}): ", e);
3085                break;
3086            }
3087        }   // switch (e.getPropertyName())
3088    }   // propertyChange
3089
3090    /**
3091     * Get valid Routes, based upon the next block and destination block
3092     *
3093     * @param nxtBlock next block
3094     * @param dstBlock final block
3095     * @return routes that fit, or null
3096     */
3097    @CheckForNull
3098    Routes getValidRoute(Block nxtBlock, Block dstBlock) {
3099        if ( nxtBlock != null && dstBlock != null ) {
3100            List<Routes> rtr = getRouteByNeighbour(nxtBlock);
3101
3102            if (rtr.isEmpty()) {
3103                log.debug("From {}, no routes returned for getRouteByNeighbour({})",
3104                        this.getDisplayName(),
3105                        nxtBlock.getDisplayName());
3106                return null;
3107            }
3108
3109            for (Routes rt : rtr) {
3110                if (rt.getDestBlock() == dstBlock) {
3111                    log.debug("From {}, found dest {}.", this.getDisplayName(), dstBlock.getDisplayName());
3112                    return rt;
3113                }
3114            }
3115            log.debug("From {}, no routes to {}.", this.getDisplayName(), nxtBlock.getDisplayName());
3116        } else {
3117            log.warn("getValidRoute({}, {}",
3118                    (nxtBlock != null) ? nxtBlock.getDisplayName() : "<null>",
3119                    (dstBlock != null) ? dstBlock.getDisplayName() : "<null>");
3120        }
3121        return null;
3122    }
3123
3124    /**
3125     * Is the route to the destination block, going via our neighbouring block
3126     * valid. ie Does the block have a route registered via neighbour
3127     * "protecting" to the destination block.
3128     *
3129     * @param protecting  neighbour block that might protect
3130     * @param destination block
3131     * @return true if we have valid path to block
3132     */
3133    public boolean isRouteToDestValid(Block protecting, Block destination) {
3134        if (protecting == destination) {
3135            log.debug("protecting and destination blocks are the same "
3136                + "therefore we need to check if we have a valid neighbour");
3137
3138            // We are testing for a directly connected block.
3139            if (getAdjacency(protecting) != null) {
3140                return true;
3141            }
3142        } else if (getValidRoute(protecting, destination) != null) {
3143            return true;
3144        }
3145        return false;
3146    }
3147
3148    /**
3149     * Get a list of valid Routes to our destination block
3150     *
3151     * @param dstBlock target to find
3152     * @return routes between this and dstBlock
3153     */
3154    List<Routes> getDestRoutes(Block dstBlock) {
3155        List<Routes> rtr = new ArrayList<>();
3156        var tempRouteList = new ArrayList<>(routes);
3157        for (Routes route : tempRouteList) {
3158            if (route.getDestBlock() == dstBlock) {
3159                rtr.add(route);
3160            }
3161        }
3162        return rtr;
3163    }
3164
3165    /**
3166     * Get a list of valid Routes via our next block
3167     *
3168     * @param nxtBlock target block
3169     * @return list of routes to target block
3170     */
3171    List<Routes> getNextRoutes(Block nxtBlock) {
3172        List<Routes> rtr = new ArrayList<>();
3173        for (Routes route : routes) {
3174            if (route.getNextBlock() == nxtBlock) {
3175                rtr.add(route);
3176            }
3177        }
3178        return rtr;
3179    }
3180
3181    void updateRoutingInfo(Routes route) {
3182        if (route.getHopCount() >= 254) {
3183            return;
3184        }
3185        Block destBlock = route.getDestBlock();
3186
3187        RoutingPacket update = new RoutingPacket(UPDATE, destBlock, getBestRouteByHop(destBlock).getHopCount() + 1,
3188                ((getBestRouteByMetric(destBlock).getMetric()) + metric),
3189                ((getBestRouteByMetric(destBlock).getMetric())
3190                + block.getLengthMm()), -1,
3191                getNextPacketID());
3192        firePropertyChange(PROPERTY_ROUTING, null, update);
3193    }
3194
3195    // This lot might need changing to only forward on the best route details.
3196    void updateRoutingInfo( @Nonnull LayoutBlock src, @Nonnull RoutingPacket update) {
3197        updateRouteLog.debug("From {} src: {}, block: {}, hopCount: {}, metric: {}, status: {}, packetID: {}",
3198            getDisplayName(), src.getDisplayName(), update.getBlock().getDisplayName(),
3199            update.getHopCount(), update.getMetric(), update.getBlockState(), update.getPacketId());
3200        Block srcblk = src.getBlock();
3201        Adjacencies adj = getAdjacency(srcblk);
3202
3203        if (adj == null) {
3204            updateRouteLog.debug("From {} packet is from a src that is not registered {}",
3205                getDisplayName(), srcblk.getDisplayName());
3206            // If the packet is from a src that is not registered as a neighbour
3207            // Then we will simply reject it.
3208            return;
3209        }
3210
3211        if (updatePacketActedUpon(update.getPacketId())) {
3212            if (adj.updatePacketActedUpon(update.getPacketId())) {
3213                updateRouteLog.debug("Reject packet update as we have already acted up on it from this neighbour");
3214                return;
3215            }
3216        }
3217
3218        updateRouteLog.debug("From {} an Update packet from neighbour {}", getDisplayName(), src.getDisplayName());
3219
3220        Block updateBlock = update.getBlock();
3221
3222        // Block srcblk = src.getBlock();
3223        // Need to add in a check to make sure that we have a route registered from the source neighbour
3224        // for the block that they are referring too.
3225        if (updateBlock == this.getBlock()) {
3226            updateRouteLog.debug("Reject packet update as it is a route advertised by our selves");
3227            return;
3228        }
3229
3230        Routes ro;
3231        boolean neighbour = false;
3232        if (updateBlock == srcblk) {
3233            // Very likely that this update is from a neighbour about its own status.
3234            ro = getValidRoute(this.getBlock(), updateBlock);
3235            neighbour = true;
3236        } else {
3237            ro = getValidRoute(srcblk, updateBlock);
3238        }
3239
3240        if (ro == null) {
3241            updateRouteLog.debug("From {} update is from a source that we do not have listed as a route to the destination", getDisplayName());
3242            updateRouteLog.debug("From {} update packet is for a block that we do not have route registered for {}", getDisplayName(), updateBlock.getDisplayName());
3243            // If the packet is for a dest that is not in the routing table
3244            // Then we will simply reject it.
3245            return;
3246        }
3247        /*This prevents us from entering into an update loop.
3248           We only add it to our list once it has passed through as being a valid
3249           packet, otherwise we may get the same packet id back, but from a valid source
3250           which would end up be rejected*/
3251
3252        actedUponUpdates.add(update.getPacketId());
3253        adj.addPacketReceivedFromNeighbour(update.getPacketId());
3254
3255        int hopCount = update.getHopCount();
3256        int packetmetric = update.getMetric();
3257        int blockstate = update.getBlockState();
3258        float length = update.getLength();
3259
3260        // Need to add in a check for a block that is directly connected.
3261        if (hopCount != -1) {
3262            // Was increase hop count before setting it
3263            // int oldHop = ro.getHopCount();
3264            if (ro.getHopCount() != hopCount) {
3265                updateRouteLog.debug("{} Hop counts to {} not the same so will change from {} to {}", getDisplayName(), ro.getDestBlock().getDisplayName(), ro.getHopCount(), hopCount);
3266                ro.setHopCount(hopCount);
3267                hopCount++;
3268            } else {
3269                // No point in forwarding on the update if the hopcount hasn't changed
3270                hopCount = -1;
3271            }
3272        }
3273
3274        // bad to use values as errors, but it's pre-existing code, and code wins
3275        if ((int) length != -1) {
3276            // Length is added at source
3277            float oldLength = ro.getLength();
3278            if (!MathUtil.equals(oldLength, length)) {
3279                ro.setLength(length);
3280                boolean forwardUpdate = true;
3281
3282                if (ro != getBestRouteByLength(update.getBlock())) {
3283                    forwardUpdate = false;
3284                }
3285
3286                updateRouteLog.debug("From {} updating length from {} to {}", getDisplayName(), oldLength, length);
3287
3288                if (neighbour) {
3289                    length = srcblk.getLengthMm();
3290                    adj.setLength(length);
3291
3292                    // ro.setLength(length);
3293                    // Also if neighbour we need to update the cost of the routes via it to reflect the new metric 02/20/2011
3294                    if (forwardUpdate) {
3295                        List<Routes> neighbourRoute = getNextRoutes(srcblk);
3296
3297                        // neighbourRoutes, contains all the routes that have been advertised by the neighbour
3298                        // that will need to have their metric updated to reflect the change.
3299                        for (Routes nRo : neighbourRoute) {
3300                            // Need to remove old metric to the neigbour, then add the new one on
3301                            float updateLength = nRo.getLength();
3302                            updateLength = (updateLength - oldLength) + length;
3303
3304                            updateRouteLog.debug("From {} update metric for route {} from {} to {}",
3305                                getDisplayName(), nRo.getDestBlock().getDisplayName(), nRo.getLength(), updateLength);
3306                            nRo.setLength(updateLength);
3307                            List<Block> messageRecipients = getThroughPathDestinationBySource(srcblk);
3308                            RoutingPacket newUpdate = new RoutingPacket(UPDATE, nRo.getDestBlock(), -1, -1, updateLength + block.getLengthMm(), -1, getNextPacketID());
3309                            updateRoutesToNeighbours(messageRecipients, nRo, newUpdate);
3310                        }
3311                    }
3312                } else if (forwardUpdate) {
3313                    // This can cause a loop, if the layout is in a loop, so we send out the same packetID.
3314                    List<Block> messageRecipients = getThroughPathSourceByDestination(srcblk);
3315                    RoutingPacket newUpdate = new RoutingPacket(UPDATE, updateBlock, -1, -1,
3316                            length + block.getLengthMm(), -1, update.getPacketId());
3317                    updateRoutesToNeighbours(messageRecipients, ro, newUpdate);
3318                }
3319                length += metric;
3320            } else {
3321                length = -1;
3322            }
3323        }
3324
3325        if (packetmetric != -1) {
3326            // Metric is added at source
3327            // Keep a reference of the old metric.
3328            int oldmetric = ro.getMetric();
3329            if (oldmetric != packetmetric) {
3330                ro.setMetric(packetmetric);
3331
3332                updateRouteLog.debug("From {} updating metric from {} to {}", getDisplayName(), oldmetric, packetmetric);
3333                boolean forwardUpdate = true;
3334
3335                if (ro != getBestRouteByMetric(update.getBlock())) {
3336                    forwardUpdate = false;
3337                }
3338
3339                // if the metric update is for a neighbour then we will go directly to the neighbour for the value,
3340                // rather than trust what is in the message at this stage.
3341                if (neighbour) {
3342                    packetmetric = src.getBlockMetric();
3343                    adj.setMetric(packetmetric);
3344
3345                    if (forwardUpdate) {
3346                        // ro.setMetric(packetmetric);
3347                        // Also if neighbour we need to update the cost of the routes via it to
3348                        // reflect the new metric 02/20/2011
3349                        List<Routes> neighbourRoute = getNextRoutes(srcblk);
3350
3351                        // neighbourRoutes, contains all the routes that have been advertised by the neighbour that
3352                        // will need to have their metric updated to reflect the change.
3353                        for (Routes nRo : neighbourRoute) {
3354                            // Need to remove old metric to the neigbour, then add the new one on
3355                            int updatemet = nRo.getMetric();
3356                            updatemet = (updatemet - oldmetric) + packetmetric;
3357
3358                            updateRouteLog.debug("From {} update metric for route {} from {} to {}", getDisplayName(), nRo.getDestBlock().getDisplayName(), nRo.getMetric(), updatemet);
3359                            nRo.setMetric(updatemet);
3360                            List<Block> messageRecipients = getThroughPathDestinationBySource(srcblk);
3361                            RoutingPacket newUpdate = new RoutingPacket(UPDATE, nRo.getDestBlock(), hopCount, updatemet + metric, -1, -1, getNextPacketID());
3362                            updateRoutesToNeighbours(messageRecipients, nRo, newUpdate);
3363                        }
3364                    }
3365                } else if (forwardUpdate) {
3366                    // This can cause a loop, if the layout is in a loop, so we send out the same packetID.
3367                    List<Block> messageRecipients = getThroughPathSourceByDestination(srcblk);
3368                    RoutingPacket newUpdate = new RoutingPacket(UPDATE, updateBlock, hopCount,
3369                            packetmetric + metric, -1, -1, update.getPacketId());
3370                    updateRoutesToNeighbours(messageRecipients, ro, newUpdate);
3371                }
3372                packetmetric += metric;
3373                // Think we need a list of routes that originate from this source neighbour
3374            } else {
3375                // No point in forwarding on the update if the metric hasn't changed
3376                packetmetric = -1;
3377                // Potentially when we do this we need to update all the routes that go via this block, not just this route.
3378            }
3379        }
3380
3381        if (blockstate != -1) {
3382            // We will update all the destination blocks with the new state, it
3383            // saves re-firing off new updates block status
3384            boolean stateUpdated = false;
3385            List<Routes> rtr = getDestRoutes(updateBlock);
3386
3387            for (Routes rt : rtr) {
3388                if (rt.getState() != blockstate) {
3389                    stateUpdated = true;
3390                    rt.stateChange();
3391                }
3392            }
3393
3394            if (stateUpdated) {
3395                RoutingPacket newUpdate = new RoutingPacket(UPDATE, updateBlock, -1, -1, -1, blockstate, getNextPacketID());
3396                firePropertyChange(PROPERTY_ROUTING, null, newUpdate);
3397            }
3398        }
3399
3400        // We need to expand on this so that any update to routing metric is propergated correctly
3401        if ((packetmetric != -1) || (hopCount != -1) || (length != -1)) {
3402            // We only want to send the update on to neighbours that we have advertised the route to.
3403            List<Block> messageRecipients = getThroughPathSourceByDestination(srcblk);
3404            RoutingPacket newUpdate = new RoutingPacket(UPDATE, updateBlock, hopCount, packetmetric,
3405                    length, blockstate, update.getPacketId());
3406            updateRoutesToNeighbours(messageRecipients, ro, newUpdate);
3407        }
3408        // Was just pass on hop count
3409    }
3410
3411    void updateRoutesToNeighbours(List<Block> messageRecipients, Routes ro, RoutingPacket update) {
3412        for (Block messageRecipient : messageRecipients) {
3413            Adjacencies adj = getAdjacency(messageRecipient);
3414            if (adj.advertiseRouteToNeighbour(ro)) {
3415                adj.addRouteAdvertisedToNeighbour(ro);
3416                LayoutBlock recipient = InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlock(messageRecipient);
3417                if (recipient != null) {
3418                    recipient.updateRoutingInfo(this, update);
3419                }
3420            }
3421        }
3422    }
3423
3424    Routes getBestRouteByMetric(Block dest) {
3425        // int bestHopCount = 255;
3426        int bestMetric = 965000;
3427        int bestIndex = -1;
3428
3429        List<Routes> destRoutes = getDestRoutes(dest);
3430        for (int i = 0; i < destRoutes.size(); i++) {
3431            if (destRoutes.get(i).getMetric() < bestMetric) {
3432                bestMetric = destRoutes.get(i).getMetric();
3433                bestIndex = i;
3434            }
3435        }
3436
3437        if (bestIndex == -1) {
3438            return null;
3439        }
3440        return destRoutes.get(bestIndex);
3441    }
3442
3443    Routes getBestRouteByHop(Block dest) {
3444        int bestHopCount = 255;
3445        // int bestMetric = 965000;
3446        int bestIndex = -1;
3447
3448        List<Routes> destRoutes = getDestRoutes(dest);
3449        for (int i = 0; i < destRoutes.size(); i++) {
3450            if (destRoutes.get(i).getHopCount() < bestHopCount) {
3451                bestHopCount = destRoutes.get(i).getHopCount();
3452                bestIndex = i;
3453            }
3454        }
3455
3456        if (bestIndex == -1) {
3457            return null;
3458        }
3459        return destRoutes.get(bestIndex);
3460    }
3461
3462    Routes getBestRouteByLength(Block dest) {
3463        // int bestHopCount = 255;
3464        // int bestMetric = 965000;
3465        // long bestLength = 999999999;
3466        int bestIndex = -1;
3467        List<Routes> destRoutes = getDestRoutes(dest);
3468        float bestLength = destRoutes.get(0).getLength();
3469
3470        for (int i = 0; i < destRoutes.size(); i++) {
3471            if (destRoutes.get(i).getLength() < bestLength) {
3472                bestLength = destRoutes.get(i).getLength();
3473                bestIndex = i;
3474            }
3475        }
3476
3477        if (bestIndex == -1) {
3478            return null;
3479        }
3480        return destRoutes.get(bestIndex);
3481    }
3482
3483    void addRouteToNeighbours(Routes ro) {
3484        addRouteLog.debug("From {} Add route to neighbour", getDisplayName());
3485        Block nextHop = ro.getNextBlock();
3486        List<LayoutBlock> validFromPath = new ArrayList<>();
3487
3488        addRouteLog.debug("From {} new block {}", getDisplayName(), nextHop.getDisplayName());
3489
3490        for (int i = 0; i < throughPaths.size(); i++) {
3491            LayoutBlock validBlock = null;
3492
3493            addRouteLog.debug("Through routes index {}", i);
3494            addRouteLog.debug("From {} A through routes {} {}", getDisplayName(),
3495                throughPaths.get(i).getSourceBlock().getDisplayName(),
3496                throughPaths.get(i).getDestinationBlock().getDisplayName());
3497
3498            /*As the through paths include each possible path, ie 2 > 3 and 3 > 2
3499               as seperate entries then we only need to forward the new route to those
3500               source blocks that have a desination of the next hop*/
3501            if (throughPaths.get(i).getDestinationBlock() == nextHop) {
3502                if (getAdjacency(throughPaths.get(i).getSourceBlock()).isMutual()) {
3503                    validBlock = InstanceManager.getDefault(
3504                            LayoutBlockManager.class).
3505                            getLayoutBlock(throughPaths.get(i).getSourceBlock());
3506                }
3507            }
3508
3509            // only need to add it the once.  Not sure if the contains is required.
3510            if ((validBlock != null) && (!validFromPath.contains(validBlock))) {
3511                validFromPath.add(validBlock);
3512            }
3513        }
3514
3515        if ( addRouteLog.isDebugEnabled() ) {
3516            addRouteLog.debug("From {} ===== valid from size path {} ==== (addroutetoneigh)", this.getDisplayName(), validFromPath.size());
3517
3518            validFromPath.forEach( valid -> addRouteLog.debug("fromPath: {}", valid.getDisplayName()));
3519            addRouteLog.debug("Next Hop {}", nextHop.getDisplayName());
3520        }
3521        RoutingPacket update = new RoutingPacket(ADDITION, ro.getDestBlock(), ro.getHopCount() + 1,
3522                ro.getMetric() + metric,
3523                (ro.getLength() + getBlock().getLengthMm()), -1, getNextPacketID());
3524
3525        for (LayoutBlock layoutBlock : validFromPath) {
3526            Adjacencies adj = getAdjacency(layoutBlock.getBlock());
3527            if (adj.advertiseRouteToNeighbour(ro)) {
3528                // getBestRouteByHop(destBlock).getHopCount()+1, ((getBestRouteByMetric(destBlock).getMetric())+metric),
3529                //((getBestRouteByMetric(destBlock).getMetric())+block.getLengthMm())
3530                addRouteLog.debug("From {} Sending update to {} As this has a better hop count or metric",
3531                    getDisplayName(), layoutBlock.getDisplayName());
3532                adj.addRouteAdvertisedToNeighbour(ro);
3533                layoutBlock.addRouteFromNeighbour(this, update);
3534            }
3535        }
3536    }
3537
3538    void addRouteFromNeighbour(LayoutBlock src, RoutingPacket update) {
3539            // log.info("From " + this.getDisplayName() + " packet to be added from neighbour " + src.getDisplayName());
3540            addRouteLog.debug("From {} src: {}, block: {}, hopCount: {}, metric: {}, status: {}, packetID: {}",
3541                getDisplayName(), src.getDisplayName(), update.getBlock().getDisplayName(),
3542                update.getHopCount(), update.getMetric(), update.getBlockState(), update.getPacketId());
3543        InstanceManager.getDefault(LayoutBlockManager.class).setLastRoutingChange();
3544        Block destBlock = update.getBlock();
3545        Block srcblk = src.getBlock();
3546
3547        if (destBlock == this.getBlock()) {
3548            addRouteLog.debug("Reject packet update as it is to a route advertised by our selves");
3549            return;
3550        }
3551
3552        Adjacencies adj = getAdjacency(srcblk);
3553        if (adj == null) {
3554            addRouteLog.debug("From {} packet is from a src that is not registered {}",
3555                getDisplayName(), srcblk.getDisplayName());
3556            // If the packet is from a src that is not registered as a neighbour
3557            // Then we will simply reject it.
3558            return;
3559        } else if (adj.getPacketFlow() == TXONLY) {
3560            addRouteLog.debug("From {} packet is from a src {} that is registered as one that we should be transmitting to only",
3561                getDisplayName(), src.getDisplayName());
3562            // we should only be transmitting routes to this neighbour not receiving them
3563            return;
3564        }
3565        int hopCount = update.getHopCount();
3566        int updatemetric = update.getMetric();
3567        float length = update.getLength();
3568
3569        if (hopCount > 255) {
3570            addRouteLog.debug("From {} hop count exceeded {}", getDisplayName(), destBlock.getDisplayName());
3571            return;
3572        }
3573
3574        for (Routes ro : routes) {
3575            if ((ro.getNextBlock() == srcblk) && (ro.getDestBlock() == destBlock)) {
3576                addRouteLog.debug("From {} Route to {} is already configured",
3577                    getDisplayName(), destBlock.getDisplayName());
3578                addRouteLog.debug("{} v {}", ro.getHopCount(), hopCount);
3579                addRouteLog.debug("{} v {}", ro.getMetric(), updatemetric);
3580                updateRoutingInfo(src, update);
3581                return;
3582            }
3583        }
3584
3585        addRouteLog.debug("From {} We should be adding route {}", getDisplayName(), destBlock.getDisplayName());
3586
3587        // We need to propergate out the routes that we have added to our neighbour
3588        int direction = adj.getDirection();
3589        Routes route = new Routes(destBlock, srcblk, hopCount, direction, updatemetric, length);
3590        routes.add(route);
3591
3592        // Need to propergate the route down to our neighbours
3593        addRouteToNeighbours(route);
3594    }
3595
3596    /* this should look after removal of a specific next hop from our neighbour*/
3597    /**
3598     * Get the direction of travel to our neighbouring block.
3599     *
3600     * @param neigh neighbor block
3601     * @return direction to get to neighbor block
3602     */
3603    public int getNeighbourDirection(LayoutBlock neigh) {
3604        if (neigh == null) {
3605            return Path.NONE;
3606        }
3607        Block neighbourBlock = neigh.getBlock();
3608        return getNeighbourDirection(neighbourBlock);
3609    }
3610
3611    public int getNeighbourDirection(Block neighbourBlock) {
3612        for (Adjacencies neighbour : neighbours) {
3613            if (neighbour.getBlock() == neighbourBlock) {
3614                return neighbour.getDirection();
3615            }
3616        }
3617        return Path.NONE;
3618    }
3619
3620    Adjacencies getAdjacency(Block blk) {
3621        for (Adjacencies neighbour : neighbours) {
3622            if (neighbour.getBlock() == blk) {
3623                return neighbour;
3624            }
3625        }
3626        return null;
3627    }
3628
3629    static final int ADDITION = 0x00;
3630    static final int UPDATE = 0x02;
3631    static final int REMOVAL = 0x04;
3632
3633    static final int RXTX = 0x00;
3634    static final int RXONLY = 0x02;
3635    static final int TXONLY = 0x04;
3636    static final int NONE = 0x08;
3637    int metric = 100;
3638
3639    private static class RoutingPacket {
3640
3641        int packetType;
3642        Block block;
3643        int hopCount = -1;
3644        int packetMetric = -1;
3645        int blockstate = -1;
3646        float length = -1;
3647        Integer packetRef = -1;
3648
3649        RoutingPacket(int packetType, Block blk, int hopCount, int packetMetric,
3650                float length, int blockstate, Integer packetRef) {
3651            this.packetType = packetType;
3652            this.block = blk;
3653            this.hopCount = hopCount;
3654            this.packetMetric = packetMetric;
3655            this.blockstate = blockstate;
3656            this.packetRef = packetRef;
3657            this.length = length;
3658        }
3659
3660        int getPacketType() {
3661            return packetType;
3662        }
3663
3664        Block getBlock() {
3665            return block;
3666        }
3667
3668        int getHopCount() {
3669            return hopCount;
3670        }
3671
3672        int getMetric() {
3673            return packetMetric;
3674        }
3675
3676        int getBlockState() {
3677            return blockstate;
3678        }
3679
3680        float getLength() {
3681            return length;
3682        }
3683
3684        Integer getPacketId() {
3685            return packetRef;
3686        }
3687    }
3688
3689    /**
3690     * Get the number of neighbor blocks attached to this block.
3691     *
3692     * @return count of neighbor
3693     */
3694    public int getNumberOfNeighbours() {
3695        return neighbours.size();
3696    }
3697
3698    /**
3699     * Get the neighboring block at index i.
3700     *
3701     * @param i index to neighbor
3702     * @return neighbor block
3703     */
3704    public Block getNeighbourAtIndex(int i) {
3705        return neighbours.get(i).getBlock();
3706    }
3707
3708    /**
3709     * Get the direction of travel to neighbouring block at index i.
3710     *
3711     * @param i index in neighbors
3712     * @return neighbor block
3713     */
3714    public int getNeighbourDirection(int i) {
3715        return neighbours.get(i).getDirection();
3716    }
3717
3718    /**
3719     * Get the metric/cost to neighbouring block at index i.
3720     *
3721     * @param i index in neighbors
3722     * @return metric of neighbor
3723     */
3724    public int getNeighbourMetric(int i) {
3725        return neighbours.get(i).getMetric();
3726    }
3727
3728    /**
3729     * Get the flow of traffic to and from neighbouring block at index i RXTX -
3730     * Means Traffic can flow both ways between the blocks RXONLY - Means we can
3731     * only receive traffic from our neighbour, we can not send traffic to it
3732     * TXONLY - Means we do not receive traffic from our neighbour, but can send
3733     * traffic to it.
3734     *
3735     * @param i index in neighbors
3736     * @return direction of traffic
3737     */
3738    public String getNeighbourPacketFlowAsString(int i) {
3739        return decodePacketFlow(neighbours.get(i).getPacketFlow());
3740    }
3741
3742    /**
3743     * Is our neighbouring block at index i a mutual neighbour, ie both blocks
3744     * have each other registered as neighbours and are exchanging information.
3745     *
3746     * @param i index of neighbor
3747     * @return true if both are mutual neighbors
3748     */
3749    public boolean isNeighbourMutual(int i) {
3750        return neighbours.get(i).isMutual();
3751    }
3752
3753    int getNeighbourIndex(Adjacencies adj) {
3754        for (int i = 0; i < neighbours.size(); i++) {
3755            if (neighbours.get(i) == adj) {
3756                return i;
3757            }
3758        }
3759        return -1;
3760    }
3761
3762    private class Adjacencies {
3763
3764        Block adjBlock;
3765        LayoutBlock adjLayoutBlock;
3766        int direction;
3767        int packetFlow = RXTX;
3768        boolean mutualAdjacency = false;
3769
3770        HashMap<Block, Routes> adjDestRoutes = new HashMap<>();
3771        List<Integer> actedUponUpdates = new ArrayList<>(501);
3772
3773        Adjacencies(Block block, int dir, int packetFlow) {
3774            adjBlock = block;
3775            direction = dir;
3776            this.packetFlow = packetFlow;
3777        }
3778
3779        Block getBlock() {
3780            return adjBlock;
3781        }
3782
3783        LayoutBlock getLayoutBlock() {
3784            return adjLayoutBlock;
3785        }
3786
3787        int getDirection() {
3788            return direction;
3789        }
3790
3791        // If a set true on mutual, then we could go through the list of what to send out to neighbour
3792        void setMutual(boolean mut) {
3793            if (mut == mutualAdjacency) {   // No change will exit
3794                return;
3795            }
3796            mutualAdjacency = mut;
3797            if (mutualAdjacency) {
3798                adjLayoutBlock = InstanceManager.getDefault(
3799                        LayoutBlockManager.class).getLayoutBlock(adjBlock);
3800            }
3801        }
3802
3803        boolean isMutual() {
3804            return mutualAdjacency;
3805        }
3806
3807        int getPacketFlow() {
3808            return packetFlow;
3809        }
3810
3811        void setPacketFlow(int flow) {
3812            if (flow != packetFlow) {
3813                int oldFlow = packetFlow;
3814                packetFlow = flow;
3815                firePropertyChange(PROPERTY_NEIGHBOUR_PACKET_FLOW, oldFlow, packetFlow);
3816            }
3817        }
3818
3819        // The metric could just be read directly from the neighbour as we have no
3820        // need to specifically keep a copy of it here this is here just to fire off the change
3821        void setMetric(int met) {
3822            firePropertyChange(PROPERTY_NEIGHBOUR_METRIC, null, getNeighbourIndex(this));
3823        }
3824
3825        int getMetric() {
3826            if (adjLayoutBlock != null) {
3827                return adjLayoutBlock.getBlockMetric();
3828            }
3829            adjLayoutBlock = InstanceManager.getDefault(
3830                    LayoutBlockManager.class).getLayoutBlock(adjBlock);
3831            if (adjLayoutBlock != null) {
3832                return adjLayoutBlock.getBlockMetric();
3833            }
3834
3835            if (log.isDebugEnabled()) {
3836                log.debug("Layout Block {} returned as null", adjBlock.getDisplayName());
3837            }
3838            return -1;
3839        }
3840
3841        void setLength(float len) {
3842            firePropertyChange(PROPERTY_NEIGHBOUR_LENGTH, null, getNeighbourIndex(this));
3843        }
3844
3845        float getLength() {
3846            if (adjLayoutBlock != null) {
3847                return adjLayoutBlock.getBlock().getLengthMm();
3848            }
3849            adjLayoutBlock = InstanceManager.getDefault(
3850                    LayoutBlockManager.class).getLayoutBlock(adjBlock);
3851            if (adjLayoutBlock != null) {
3852                return adjLayoutBlock.getBlock().getLengthMm();
3853            }
3854
3855            if (log.isDebugEnabled()) {
3856                log.debug("Layout Block {} returned as null", adjBlock.getDisplayName());
3857            }
3858            return -1;
3859        }
3860
3861        void removeRouteAdvertisedToNeighbour(Routes removeRoute) {
3862            Block dest = removeRoute.getDestBlock();
3863
3864            if (adjDestRoutes.get(dest) == removeRoute) {
3865                adjDestRoutes.remove(dest);
3866            }
3867        }
3868
3869        void removeRouteAdvertisedToNeighbour(Block block) {
3870            adjDestRoutes.remove(block);
3871        }
3872
3873        void addRouteAdvertisedToNeighbour(Routes addedRoute) {
3874            adjDestRoutes.put(addedRoute.getDestBlock(), addedRoute);
3875        }
3876
3877        boolean advertiseRouteToNeighbour(Routes routeToAdd) {
3878            if (!isMutual()) {
3879                log.debug("In block {}: Neighbour is not mutual so will not advertise it (Routes {})",
3880                    getDisplayName(), routeToAdd);
3881                return false;
3882            }
3883
3884            // Just wonder if this should forward on the new packet to the neighbour?
3885            Block dest = routeToAdd.getDestBlock();
3886            if (!adjDestRoutes.containsKey(dest)) {
3887                log.debug("In block {}: We are not currently advertising a route to the destination to neighbour: {}",
3888                    getDisplayName(), dest.getDisplayName());
3889                return true;
3890            }
3891
3892            if (routeToAdd.getHopCount() > 255) {
3893                log.debug("Hop count is gereater than 255 we will therefore do nothing with this route");
3894                return false;
3895            }
3896            Routes existingRoute = adjDestRoutes.get(dest);
3897            if (existingRoute.getMetric() > routeToAdd.getMetric()) {
3898                return true;
3899            }
3900            if (existingRoute.getHopCount() > routeToAdd.getHopCount()) {
3901                return true;
3902            }
3903
3904            if (existingRoute == routeToAdd) {
3905                // We return true as the metric might have changed
3906                return false;
3907            }
3908            return false;
3909        }
3910
3911        boolean updatePacketActedUpon(Integer packetID) {
3912            return actedUponUpdates.contains(packetID);
3913        }
3914
3915        void addPacketReceivedFromNeighbour(Integer packetID) {
3916            actedUponUpdates.add(packetID);
3917            if (actedUponUpdates.size() > 500) {
3918                actedUponUpdates.subList(0, 250).clear();
3919            }
3920        }
3921
3922        void dispose() {
3923            adjBlock = null;
3924            adjLayoutBlock = null;
3925            mutualAdjacency = false;
3926            adjDestRoutes = null;
3927            actedUponUpdates = null;
3928        }
3929    }
3930
3931    /**
3932     * Get the number of routes that the block has registered.
3933     *
3934     * @return count of routes
3935     */
3936    public int getNumberOfRoutes() {
3937        return routes.size();
3938    }
3939
3940    /**
3941     * Get the direction of route i.
3942     *
3943     * @param i index in routes
3944     * @return direction
3945     */
3946    public int getRouteDirectionAtIndex(int i) {
3947        return routes.get(i).getDirection();
3948    }
3949
3950    /**
3951     * Get the destination block at route i
3952     *
3953     * @param i index in routes
3954     * @return dest block from route
3955     */
3956    public Block getRouteDestBlockAtIndex(int i) {
3957        return routes.get(i).getDestBlock();
3958    }
3959
3960    /**
3961     * Get the next block at route i
3962     *
3963     * @param i index in routes
3964     * @return next block from route
3965     */
3966    public Block getRouteNextBlockAtIndex(int i) {
3967        return routes.get(i).getNextBlock();
3968    }
3969
3970    /**
3971     * Get the hop count of route i.<br>
3972     * The Hop count is the number of other blocks that we traverse to get to
3973     * the destination
3974     *
3975     * @param i index in routes
3976     * @return hop count
3977     */
3978    public int getRouteHopCountAtIndex(int i) {
3979        return routes.get(i).getHopCount();
3980    }
3981
3982    /**
3983     * Get the length of route i.<br>
3984     * The length is the combined length of all the blocks that we traverse to
3985     * get to the destination
3986     *
3987     * @param i index in routes
3988     * @return length of block in route
3989     */
3990    public float getRouteLengthAtIndex(int i) {
3991        return routes.get(i).getLength();
3992    }
3993
3994    /**
3995     * Get the metric/cost at route i
3996     *
3997     * @param i index in routes
3998     * @return metric
3999     */
4000    public int getRouteMetric(int i) {
4001        return routes.get(i).getMetric();
4002    }
4003
4004    /**
4005     * Get the state (Occupied, unoccupied) of the destination layout block at
4006     * index i
4007     *
4008     * @param i index in routes
4009     * @return state of block
4010     */
4011    public int getRouteState(int i) {
4012        return routes.get(i).getState();
4013    }
4014
4015    /**
4016     * Is the route to the destination potentially valid from our block.
4017     *
4018     * @param i index in route
4019     * @return true if route is valid
4020     */
4021    // TODO: Java standard pattern for boolean getters is "isRouteValid()"
4022    public boolean getRouteValid(int i) {
4023        return routes.get(i).isRouteCurrentlyValid();
4024    }
4025
4026    /**
4027     * Get the state of the destination layout block at index i as a string.
4028     *
4029     * @param i index in routes
4030     * @return dest status
4031     */
4032    public String getRouteStateAsString(int i) {
4033        int state = routes.get(i).getState();
4034        switch (state) {
4035            case OCCUPIED: {
4036                return Bundle.getMessage("TrackOccupied"); // i18n using NamedBeanBundle.properties TODO remove duplicate keys
4037            }
4038
4039            case RESERVED: {
4040                return Bundle.getMessage("StateReserved"); // "Reserved"
4041            }
4042
4043            case EMPTY: {
4044                return Bundle.getMessage("StateFree");  // "Free"
4045            }
4046
4047            default: {
4048                return Bundle.getMessage("BeanStateUnknown"); // "Unknown"
4049            }
4050        }
4051    }
4052
4053    int getRouteIndex(Routes r) {
4054        for (int i = 0; i < routes.size(); i++) {
4055            if (routes.get(i) == r) {
4056                return i;
4057            }
4058        }
4059        return -1;
4060    }
4061
4062    /**
4063     * Get the number of layout blocks to our destintation block going from the
4064     * next directly connected block. If the destination block and nextblock are
4065     * the same and the block is also registered as a neighbour then 1 is
4066     * returned. If no valid route to the destination block can be found via the
4067     * next block then -1 is returned. If more than one route exists to the
4068     * destination then the route with the lowest count is returned.
4069     *
4070     * @param destination final block
4071     * @param nextBlock   adjcent block
4072     * @return hop count to final, -1 if not available
4073     */
4074    public int getBlockHopCount(Block destination, Block nextBlock) {
4075        if ((destination == nextBlock) && (isValidNeighbour(nextBlock))) {
4076            return 1;
4077        }
4078
4079        for (Routes route : routes) {
4080            if (route.getDestBlock() == destination) {
4081                if (route.getNextBlock() == nextBlock) {
4082                    return route.getHopCount();
4083                }
4084            }
4085        }
4086        return -1;
4087    }
4088
4089    /**
4090     * Get the metric to our desintation block going from the next directly
4091     * connected block. If the destination block and nextblock are the same and
4092     * the block is also registered as a neighbour then 1 is returned. If no
4093     * valid route to the destination block can be found via the next block then
4094     * -1 is returned. If more than one route exists to the destination then the
4095     * route with the lowest count is returned.
4096     *
4097     * @param destination final block
4098     * @param nextBlock   adjcent block
4099     * @return metric to final block, -1 if not available
4100     */
4101    public int getBlockMetric(Block destination, Block nextBlock) {
4102        if ((destination == nextBlock) && (isValidNeighbour(nextBlock))) {
4103            return 1;
4104        }
4105
4106        for (Routes route : routes) {
4107            if (route.getDestBlock() == destination) {
4108                if (route.getNextBlock() == nextBlock) {
4109                    return route.getMetric();
4110                }
4111            }
4112        }
4113        return -1;
4114    }
4115
4116    /**
4117     * Get the distance to our desintation block going from the next directly
4118     * connected block. If the destination block and nextblock are the same and
4119     * the block is also registered as a neighbour then 1 is returned. If no
4120     * valid route to the destination block can be found via the next block then
4121     * -1 is returned. If more than one route exists to the destination then the
4122     * route with the lowest count is returned.
4123     *
4124     * @param destination final block
4125     * @param nextBlock   adjcent block
4126     * @return length to final, -1 if not viable
4127     */
4128    public float getBlockLength(Block destination, Block nextBlock) {
4129        if ((destination == nextBlock) && (isValidNeighbour(nextBlock))) {
4130            return 1;
4131        }
4132
4133        for (Routes route : routes) {
4134            if (route.getDestBlock() == destination) {
4135                if (route.getNextBlock() == nextBlock) {
4136                    return route.getLength();
4137                }
4138            }
4139        }
4140        return -1;
4141    }
4142
4143    // TODO This needs a propertychange listener adding
4144    private class Routes implements PropertyChangeListener {
4145
4146        int direction;
4147        Block destBlock;
4148        Block nextBlock;
4149        int hopCount;
4150        int routeMetric;
4151        float length;
4152
4153        // int state =-1;
4154        int miscflags = 0x00;
4155        boolean validCurrentRoute = false;
4156
4157        Routes(Block dstBlock, Block nxtBlock, int hop, int dir, int met, float len) {
4158            destBlock = dstBlock;
4159            nextBlock = nxtBlock;
4160            hopCount = hop;
4161            direction = dir;
4162            routeMetric = met;
4163            length = len;
4164            init();
4165        }
4166
4167        final void init() {
4168            validCurrentRoute = checkIsRouteOnValidThroughPath(this);
4169            firePropertyChange(PROPERTY_LENGTH, null, null);
4170            destBlock.addPropertyChangeListener(this);
4171        }
4172
4173        @Override
4174        public String toString() {
4175            return "Routes(dst:" + destBlock + ", nxt:" + nextBlock
4176                    + ", hop:" + hopCount + ", dir:" + direction
4177                    + ", met:" + routeMetric + ", len: " + length + ")";
4178        }
4179
4180        @Override
4181        public void propertyChange(PropertyChangeEvent e) {
4182            if ( Block.PROPERTY_STATE.equals(e.getPropertyName())) {
4183                stateChange();
4184            }
4185        }
4186
4187        public Block getDestBlock() {
4188            return destBlock;
4189        }
4190
4191        public Block getNextBlock() {
4192            return nextBlock;
4193        }
4194
4195        public int getHopCount() {
4196            return hopCount;
4197        }
4198
4199        public int getDirection() {
4200            return direction;
4201        }
4202
4203        public int getMetric() {
4204            return routeMetric;
4205        }
4206
4207        public float getLength() {
4208            return length;
4209        }
4210
4211        public void setMetric(int met) {
4212            if (met == routeMetric) {
4213                return;
4214            }
4215            routeMetric = met;
4216            firePropertyChange(PROPERTY_METRIC, null, getRouteIndex(this));
4217        }
4218
4219        public void setHopCount(int hop) {
4220            if (hopCount == hop) {
4221                return;
4222            }
4223            hopCount = hop;
4224            firePropertyChange(PROPERTY_HOP, null, getRouteIndex(this));
4225        }
4226
4227        public void setLength(float len) {
4228            if (len == length) {
4229                return;
4230            }
4231            length = len;
4232            firePropertyChange(PROPERTY_LENGTH, null, getRouteIndex(this));
4233        }
4234
4235        // This state change is only here for the routing table view
4236        void stateChange() {
4237            firePropertyChange(PROPERTY_STATE, null, getRouteIndex(this));
4238        }
4239
4240        int getState() {
4241            LayoutBlock destLBlock = InstanceManager.getDefault(
4242                    LayoutBlockManager.class).getLayoutBlock(destBlock);
4243            if (destLBlock != null) {
4244                return destLBlock.getBlockStatus();
4245            }
4246
4247            log.debug("Layout Block {} returned as null", destBlock.getDisplayName());
4248            return -1;
4249        }
4250
4251        void setValidCurrentRoute(boolean boo) {
4252            if (validCurrentRoute == boo) {
4253                return;
4254            }
4255            validCurrentRoute = boo;
4256            firePropertyChange(PROPERTY_VALID, null, getRouteIndex(this));
4257        }
4258
4259        boolean isRouteCurrentlyValid() {
4260            return validCurrentRoute;
4261        }
4262
4263        // Misc flags is not used in general routing, but is used for determining route removals
4264        void setMiscFlags(int f) {
4265            miscflags = f;
4266        }
4267
4268        int getMiscFlags() {
4269            return miscflags;
4270        }
4271    }
4272
4273    /**
4274     * Get the number of valid through paths on this block.
4275     *
4276     * @return count of paths through this block
4277     */
4278    public int getNumberOfThroughPaths() {
4279        return throughPaths.size();
4280    }
4281
4282    /**
4283     * Get the source block at index i
4284     *
4285     * @param i index in throughPaths
4286     * @return source block
4287     */
4288    public Block getThroughPathSource(int i) {
4289        return throughPaths.get(i).getSourceBlock();
4290    }
4291
4292    /**
4293     * Get the destination block at index i
4294     *
4295     * @param i index in throughPaths
4296     * @return final block
4297     */
4298    public Block getThroughPathDestination(int i) {
4299        return throughPaths.get(i).getDestinationBlock();
4300    }
4301
4302    /**
4303     * Is the through path at index i active?
4304     *
4305     * @param i index in path
4306     * @return active or not
4307     */
4308    public Boolean isThroughPathActive(int i) {
4309        return throughPaths.get(i).isPathActive();
4310    }
4311
4312    private class ThroughPaths implements PropertyChangeListener {
4313
4314        Block sourceBlock;
4315        Block destinationBlock;
4316        Path sourcePath;
4317        Path destinationPath;
4318
4319        boolean pathActive = false;
4320
4321        HashMap<Turnout, Integer> _turnouts = new HashMap<>();
4322
4323        ThroughPaths(Block srcBlock, Path srcPath, Block destBlock, Path dstPath) {
4324            sourceBlock = srcBlock;
4325            destinationBlock = destBlock;
4326            sourcePath = srcPath;
4327            destinationPath = dstPath;
4328        }
4329
4330        Block getSourceBlock() {
4331            return sourceBlock;
4332        }
4333
4334        Block getDestinationBlock() {
4335            return destinationBlock;
4336        }
4337
4338        Path getSourcePath() {
4339            return sourcePath;
4340        }
4341
4342        Path getDestinationPath() {
4343            return destinationPath;
4344        }
4345
4346        boolean isPathActive() {
4347            return pathActive;
4348        }
4349
4350        void setTurnoutList(List<LayoutTrackExpectedState<LayoutTurnout>> turnouts) {
4351            if (!_turnouts.isEmpty()) {
4352                Set<Turnout> en = _turnouts.keySet();
4353                en.forEach( listTurnout -> listTurnout.removePropertyChangeListener(this));
4354            }
4355
4356            // If we have no turnouts in this path, then this path is always active
4357            if (turnouts.isEmpty()) {
4358                pathActive = true;
4359                setRoutesValid(sourceBlock, true);
4360                setRoutesValid(destinationBlock, true);
4361                return;
4362            }
4363            _turnouts = new HashMap<>(turnouts.size());
4364            for (LayoutTrackExpectedState<LayoutTurnout> turnout : turnouts) {
4365                if (turnout.getObject() instanceof LayoutSlip) {
4366                    int slipState = turnout.getExpectedState();
4367                    LayoutSlip ls = (LayoutSlip) turnout.getObject();
4368                    int taState = ls.getTurnoutState(slipState);
4369                    _turnouts.put(ls.getTurnout(), taState);
4370                    ls.getTurnout().addPropertyChangeListener(this, ls.getTurnoutName(), "Layout Block Routing");
4371
4372                    int tbState = ls.getTurnoutBState(slipState);
4373                    _turnouts.put(ls.getTurnoutB(), tbState);
4374                    ls.getTurnoutB().addPropertyChangeListener(this, ls.getTurnoutBName(), "Layout Block Routing");
4375                } else {
4376                    LayoutTurnout lt = turnout.getObject();
4377                    if (lt.getTurnout() != null) {
4378                        _turnouts.put(lt.getTurnout(), turnout.getExpectedState());
4379                        lt.getTurnout().addPropertyChangeListener(this, lt.getTurnoutName(), "Layout Block Routing");
4380                    } else {
4381                        log.error("{} has no physical turnout allocated, block = {}", lt, block.getDisplayName());
4382                    }
4383                }
4384            }
4385        }
4386
4387        @Override
4388        public void propertyChange(PropertyChangeEvent e) {
4389            if ( Turnout.PROPERTY_KNOWN_STATE.equals(e.getPropertyName())) {
4390                Turnout srcTurnout = (Turnout) e.getSource();
4391                int newVal = (Integer) e.getNewValue();
4392                int values = _turnouts.get(srcTurnout);
4393                boolean allset = false;
4394                pathActive = false;
4395
4396                if (newVal == values) {
4397                    allset = true;
4398
4399                    if (_turnouts.size() > 1) {
4400                        for (Map.Entry<Turnout, Integer> entry : _turnouts.entrySet()) {
4401                            if (srcTurnout != entry.getKey()) {
4402                                int state = entry.getKey().getState();
4403                                if (state != entry.getValue()) {
4404                                    allset = false;
4405                                    break;
4406                                }
4407                            }
4408                        }
4409                    }
4410                }
4411                updateActiveThroughPaths(this, allset);
4412                pathActive = allset;
4413            }
4414        }
4415
4416        // We keep a track of what is paths are active, only so that we can easily mark
4417        // which routes are also potentially valid
4418        private List<ThroughPaths> activePaths;
4419
4420        private void updateActiveThroughPaths(ThroughPaths tp, boolean active) {
4421            updateRouteLog.debug("We have been notified that a through path has changed state");
4422
4423            if (activePaths == null) {
4424                activePaths = new ArrayList<>();
4425            }
4426
4427            if (active) {
4428                activePaths.add(tp);
4429                setRoutesValid(tp.getSourceBlock(), active);
4430                setRoutesValid(tp.getDestinationBlock(), active);
4431            } else {
4432                // We need to check if either our source or des is in use by another path.
4433                activePaths.remove(tp);
4434                boolean sourceInUse = false;
4435                boolean destinationInUse = false;
4436
4437                List<ThroughPaths> copyOfPaths = activePaths;
4438                for (ThroughPaths activePath : copyOfPaths) {
4439                    Block testSour = activePath.getSourceBlock();
4440                    Block testDest = activePath.getDestinationBlock();
4441                    if ((testSour == tp.getSourceBlock()) || (testDest == tp.getSourceBlock())) {
4442                        sourceInUse = true;
4443                    }
4444                    if ((testSour == tp.getDestinationBlock()) || (testDest == tp.getDestinationBlock())) {
4445                        destinationInUse = true;
4446                    }
4447                }
4448
4449                if (!sourceInUse) {
4450                    setRoutesValid(tp.getSourceBlock(), active);
4451                }
4452
4453                if (!destinationInUse) {
4454                    setRoutesValid(tp.getDestinationBlock(), active);
4455                }
4456            }
4457
4458            for (int i = 0; i < throughPaths.size(); i++) {
4459                // This is processed simply for the throughpath table.
4460                if (tp == throughPaths.get(i)) {
4461                    firePropertyChange(PROPERTY_PATH, null, i);
4462                }
4463            }
4464        }
4465
4466    }
4467
4468    @Nonnull
4469    List<Block> getThroughPathSourceByDestination(Block dest) {
4470        List<Block> a = new ArrayList<>();
4471
4472        for (ThroughPaths throughPath : throughPaths) {
4473            if (throughPath.getDestinationBlock() == dest) {
4474                a.add(throughPath.getSourceBlock());
4475            }
4476        }
4477        return a;
4478    }
4479
4480    @Nonnull
4481    List<Block> getThroughPathDestinationBySource(Block source) {
4482        List<Block> a = new ArrayList<>();
4483
4484        for (ThroughPaths throughPath : throughPaths) {
4485            if (throughPath.getSourceBlock() == source) {
4486                a.add(throughPath.getDestinationBlock());
4487            }
4488        }
4489        return a;
4490    }
4491
4492    /**
4493     * When a route is created, check to see if the through path that this route
4494     * relates to is active.
4495     * @param r The route to check
4496     * @return true if that route is active
4497     */
4498    boolean checkIsRouteOnValidThroughPath(Routes r) {
4499        for (ThroughPaths t : throughPaths) {
4500            if (t.isPathActive()) {
4501                if (t.getDestinationBlock() == r.getNextBlock()) {
4502                    return true;
4503                }
4504                if (t.getSourceBlock() == r.getNextBlock()) {
4505                    return true;
4506                }
4507            }
4508        }
4509        return false;
4510    }
4511
4512    /**
4513     * Go through all the routes and refresh the valid flag.
4514     */
4515    public void refreshValidRoutes() {
4516        for (int i = 0; i < throughPaths.size(); i++) {
4517            ThroughPaths t = throughPaths.get(i);
4518            setRoutesValid(t.getDestinationBlock(), t.isPathActive());
4519            setRoutesValid(t.getSourceBlock(), t.isPathActive());
4520            firePropertyChange(PROPERTY_PATH, null, i);
4521        }
4522    }
4523
4524    /**
4525     * Set the valid flag for routes that are on a valid through path.
4526     * @param nxtHopActive the start of the route
4527     * @param state the state to set into the valid flag
4528     */
4529    void setRoutesValid(Block nxtHopActive, boolean state) {
4530        List<Routes> rtr = getRouteByNeighbour(nxtHopActive);
4531        rtr.forEach( rt -> rt.setValidCurrentRoute(state));
4532    }
4533
4534    @Override
4535    public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException {
4536        if (Manager.PROPERTY_CAN_DELETE.equals(evt.getPropertyName())) {
4537            if (evt.getOldValue() instanceof Sensor) {
4538                if (evt.getOldValue().equals(getOccupancySensor())) {
4539                    throw new PropertyVetoException(getDisplayName(), evt);
4540                }
4541            }
4542
4543            if (evt.getOldValue() instanceof Memory) {
4544                if (evt.getOldValue().equals(getMemory())) {
4545                    throw new PropertyVetoException(getDisplayName(), evt);
4546                }
4547            }
4548        } else if (Manager.PROPERTY_DO_DELETE.equals(evt.getPropertyName())) {
4549            // Do nothing at this stage
4550            if (evt.getOldValue() instanceof Sensor) {
4551                if (evt.getOldValue().equals(getOccupancySensor())) {
4552                    setOccupancySensorName(null);
4553                }
4554            }
4555
4556            if (evt.getOldValue() instanceof Memory) {
4557                if (evt.getOldValue().equals(getMemory())) {
4558                    setMemoryName(null);
4559                }
4560            }
4561        }
4562    }
4563
4564    @Override
4565    public List<NamedBeanUsageReport> getUsageReport(NamedBean bean) {
4566        List<NamedBeanUsageReport> report = new ArrayList<>();
4567        if (bean != null) {
4568            if (bean.equals(getBlock())) {
4569                report.add(new NamedBeanUsageReport("LayoutBlockBlock"));  // NOI18N
4570            }
4571            if (bean.equals(getMemory())) {
4572                report.add(new NamedBeanUsageReport("LayoutBlockMemory"));  // NOI18N
4573            }
4574            if (bean.equals(getOccupancySensor())) {
4575                report.add(new NamedBeanUsageReport("LayoutBlockSensor"));  // NOI18N
4576            }
4577            for (int i = 0; i < getNumberOfNeighbours(); i++) {
4578                if (bean.equals(getNeighbourAtIndex(i))) {
4579                    report.add(new NamedBeanUsageReport("LayoutBlockNeighbor", "Neighbor"));  // NOI18N
4580                }
4581            }
4582        }
4583        return report;
4584    }
4585
4586    @Override
4587    public String getBeanType() {
4588        return Bundle.getMessage("BeanNameLayoutBlock");
4589    }
4590
4591    private static final Logger log = LoggerFactory.getLogger(LayoutBlock.class);
4592    private static final Logger searchRouteLog = LoggerFactory.getLogger(LayoutBlock.class.getName()+".SearchRouteLogging");
4593    private static final Logger updateRouteLog = LoggerFactory.getLogger(LayoutBlock.class.getName()+".UpdateRouteLogging");
4594    private static final Logger addRouteLog = LoggerFactory.getLogger(LayoutBlock.class.getName()+".AddRouteLogging");
4595    private static final Logger deleteRouteLog = LoggerFactory.getLogger(LayoutBlock.class.getName()+".DeleteRouteLogging");
4596
4597}