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