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