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