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