001package jmri.managers;
002
003import java.util.ArrayList;
004import java.util.List;
005import java.util.Objects;
006import java.util.Set;
007
008import javax.annotation.CheckForNull;
009import javax.annotation.Nonnull;
010
011import jmri.Block;
012import jmri.BlockManager;
013import jmri.EntryPoint;
014import jmri.InstanceManager;
015import jmri.JmriException;
016import jmri.NamedBean;
017import jmri.Manager;
018import jmri.Path;
019import jmri.Section;
020import jmri.Sensor;
021import jmri.SensorManager;
022import jmri.SignalHead;
023import jmri.SignalHeadManager;
024
025import jmri.implementation.DefaultSection;
026
027import jmri.jmrit.display.EditorManager;
028import jmri.jmrit.display.layoutEditor.LayoutEditor;
029import jmri.jmrit.display.layoutEditor.LayoutBlockManager;
030import jmri.jmrit.display.layoutEditor.LayoutBlock;
031
032import org.slf4j.Logger;
033import org.slf4j.LoggerFactory;
034
035/**
036 * Basic Implementation of a SectionManager.
037 * <p>
038 * This doesn't have a "new" interface, since Sections are independently
039 * implemented, instead of being system-specific.
040 * <p>
041 * Note that Section system names must begin with system prefix and type character,
042 * usually IY, and be followed by a string, usually, but not always, a number. This
043 * is enforced when a Section is created.
044 * <br>
045 * <hr>
046 * This file is part of JMRI.
047 * <p>
048 * JMRI is free software; you can redistribute it and/or modify it under the
049 * terms of version 2 of the GNU General Public License as published by the Free
050 * Software Foundation. See the "COPYING" file for a copy of this license.
051 * <p>
052 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY
053 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
054 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
055 *
056 * @author Dave Duchamp Copyright (C) 2008
057 */
058public class DefaultSectionManager extends AbstractManager<Section> implements jmri.SectionManager {
059
060    public DefaultSectionManager() {
061        super();
062        addListeners();
063    }
064
065    final void addListeners() {
066        InstanceManager.getDefault(SensorManager.class).addVetoableChangeListener(this);
067        InstanceManager.getDefault(BlockManager.class).addVetoableChangeListener(this);
068    }
069
070    @Override
071    public int getXMLOrder() {
072        return Manager.SECTIONS;
073    }
074
075    @Override
076    public char typeLetter() {
077        return 'Y';
078    }
079
080    @Override
081    public Class<Section> getNamedBeanClass() {
082        return Section.class;
083    }
084
085    /**
086     * Create a new Section if the Section does not exist.
087     *
088     * @param systemName the desired system name
089     * @param userName   the desired user name
090     * @return a new Section or
091     * @throws IllegalArgumentException if a Section with the same systemName or
092     *         userName already exists, or if there is trouble creating a new
093     *         Section.
094     */
095    @Nonnull
096    public Section createNewSection(@Nonnull String systemName, String userName) throws IllegalArgumentException {
097        Objects.requireNonNull(systemName, "SystemName cannot be null. UserName was " + ((userName == null) ? "null" : userName));  // NOI18N
098        // check system name
099        if (systemName.isEmpty()) {
100            throw new IllegalArgumentException("Section System Name Empty");
101            // no valid system name entered, return without creating
102        }
103        String sysName = systemName;
104        if (!sysName.startsWith(getSystemNamePrefix())) {
105            sysName = makeSystemName(sysName);
106        }
107        // Check that Section does not already exist
108        Section y;
109        if (userName != null && !userName.isEmpty()) {
110            y = getByUserName(userName);
111            if (y != null) {
112                throw new IllegalArgumentException("Section Already Exists with UserName " + userName);
113            }
114        }
115        y = getBySystemName(sysName);
116        if (y != null) {
117            throw new IllegalArgumentException("Section Already Exists with SystemName " + sysName);
118        }
119        // Section does not exist, create a new Section
120        y = new DefaultSection(sysName, userName);
121        // save in the maps
122        register(y);
123
124        // Keep track of the last created auto system name
125        updateAutoNumber(systemName);
126
127        return y;
128    }
129
130    /**
131     * Create a New Section with Auto System Name.
132     * @param userName UserName for new Section
133     * @return new Section with Auto System Name.
134     * @throws IllegalArgumentException if existing Section, or
135     *          unable to create a new Section.
136     */
137    @Nonnull
138    public Section createNewSection(String userName) throws IllegalArgumentException {
139        return createNewSection(getAutoSystemName(), userName);
140    }
141
142    /**
143     * Remove an existing Section.
144     *
145     * @param y the section to remove
146     */
147    public void deleteSection(Section y) {
148        // delete the Section
149        deregister(y);
150        y.dispose();
151    }
152
153    /**
154     * Get an existing Section. First look up assuming that name is a User
155     * Name. If this fails look up assuming that name is a System Name.
156     *
157     * @param name the name to find; user names are searched for a match first,
158     *             followed by system names
159     * @return the found section of null if no matching Section found
160     */
161    public Section getSection(String name) {
162        Section y = getByUserName(name);
163        if (y != null) {
164            return y;
165        }
166        return getBySystemName(name);
167    }
168
169    /**
170     * Validate all Sections.
171     *
172     * @return number or validation errors; -2 is returned if there are no sections
173     */
174    public int validateAllSections() {
175        Set<Section> set = getNamedBeanSet();
176        int numSections = 0;
177        int numErrors = 0;
178        if (set.size() <= 0) {
179            return -2;
180        }
181        for (Section section : set) {
182            String s = section.validate();
183            if (!s.isEmpty()) {
184                log.error("Validate result for section {}: {}", section.getDisplayName(), s);
185                numErrors++;
186            }
187            numSections++;
188        }
189        log.debug("Validated {} Sections - {} errors or warnings.", numSections, numErrors);
190        return numErrors;
191    }
192
193    /**
194     * Check direction sensors in SSL for signals.
195     *
196     * @return the number or errors; 0 if no errors; -1 if the panel is null; -2 if there are no sections
197     */
198    public int setupDirectionSensors() {
199        Set<Section> set = getNamedBeanSet();
200        int numSections = 0;
201        int numErrors = 0;
202        if (set.size() <= 0) {
203            return -2;
204        }
205        for (Section section : set) {
206            int errors = section.placeDirectionSensors();
207            numErrors = numErrors + errors;
208            numSections++;
209        }
210        log.debug("Checked direction sensors for {} Sections - {} errors or warnings.", numSections, numErrors);
211        return numErrors;
212    }
213
214    /**
215     * Remove direction sensors from SSL for all signals.
216     *
217     * @return the number or errors; 0 if no errors; -1 if the panel is null; -2 if there are no sections
218     */
219    public int removeDirectionSensorsFromSSL() {
220        Set<Section> set = getNamedBeanSet();
221        if (set.size() <= 0) {
222            return -2;
223        }
224        int numErrors = 0;
225        List<String> sensorList = new ArrayList<>();
226        for (Section s : set) {
227            String name = s.getReverseBlockingSensorName();
228            if ((name != null) && (!name.isEmpty())) {
229                sensorList.add(name);
230            }
231            name = s.getForwardBlockingSensorName();
232            if ((name != null) && (!name.isEmpty())) {
233                sensorList.add(name);
234            }
235        }
236
237        var editorManager = InstanceManager.getDefault(EditorManager.class);
238        var shManager = InstanceManager.getDefault(SignalHeadManager.class);
239
240        for (var panel : editorManager.getAll(LayoutEditor.class)) {
241            var cUtil = panel.getConnectivityUtil();
242            for (SignalHead sh : shManager.getNamedBeanSet()) {
243                if (!cUtil.removeSensorsFromSignalHeadLogic(sensorList, sh)) {
244                    numErrors++;
245                }
246            }
247        }
248        return numErrors;
249    }
250
251    /**
252     * Initialize all blocking sensors that exist - set them to 'ACTIVE'.
253     */
254    public void initializeBlockingSensors() {
255        for (Section s : getNamedBeanSet()) {
256            try {
257                if (s.getForwardBlockingSensor() != null) {
258                    s.getForwardBlockingSensor().setState(Sensor.ACTIVE);
259                }
260                if (s.getReverseBlockingSensor() != null) {
261                    s.getReverseBlockingSensor().setState(Sensor.ACTIVE);
262                }
263            } catch (JmriException reason) {
264                log.error("Exception when initializing blocking Sensors for Section {}", s.getDisplayName(NamedBean.DisplayOptions.USERNAME_SYSTEMNAME));
265            }
266        }
267    }
268
269    /**
270     * Generate Block Sections in stubs/sidings. Called after generating signal logic.
271     */
272
273
274    public void generateBlockSections() {
275        //find blocks with no paths through i.e. stub (siding)
276        LayoutBlockManager LayoutBlockManager = InstanceManager.getDefault(LayoutBlockManager.class);
277        //print "Layout Block"
278        for (LayoutBlock layoutBlock : LayoutBlockManager.getNamedBeanSet()){
279            if (layoutBlock.getNumberOfThroughPaths() == 0){
280                if (!blockSectionExists(layoutBlock)){
281                    //create block section"
282                    createBlockSection(layoutBlock);
283                }
284            }
285        }
286    }
287
288    /**
289     * Check if Block Section already exists
290     * @param layoutBlock
291     * @return true or false
292     */
293    private boolean blockSectionExists(LayoutBlock layoutBlock){
294
295        for (Section section : getNamedBeanSet()){
296            if (section.getNumBlocks() == 1
297                    && section.getSectionType() != Section.SIGNALMASTLOGIC
298                    && layoutBlock.getBlock().equals(section.getEntryBlock())){
299                return true;
300            }
301        }
302        return false;
303    }
304
305    private void createBlockSection(LayoutBlock layoutBlock){
306        Section section;
307        try {
308            section = createNewSection(layoutBlock.getUserName());
309        }
310        catch (IllegalArgumentException ex){
311            log.error("Could not create Section from LayoutBlock {}",layoutBlock.getDisplayName());
312            return;
313        }
314        section.addBlock(layoutBlock.getBlock());
315        ArrayList<EntryPoint> entryPointList = new ArrayList<>();
316        Block sb = layoutBlock.getBlock();
317        List <Path> paths = sb.getPaths();
318        for (int j=0; j<paths.size(); j++){
319            Path p = paths.get(j);
320            if (p.getBlock() != sb){
321                //this is path to an outside block, so need an Entry Point
322                String pbDir = Path.decodeDirection(p.getFromBlockDirection());
323                EntryPoint ep = new EntryPoint(sb, p.getBlock(), pbDir);
324                entryPointList.add(ep);
325            }
326        }
327
328        Block beginBlock = sb;
329        // Set directions where possible
330        List <EntryPoint> epList = getBlockEntryPointsList(beginBlock,entryPointList);
331        if (epList.size() == 1) {
332            (epList.get(0)).setTypeForward();
333        }
334        Block endBlock = sb;
335        epList = getBlockEntryPointsList(endBlock, entryPointList);
336        if (epList.size() == 1) {
337            (epList.get(0)).setTypeReverse();
338        }
339
340        for (int j=0; j<entryPointList.size(); j++){
341            EntryPoint ep = entryPointList.get(j);
342            if (ep.isForwardType()){
343                section.addToForwardList(ep);
344            }else if (ep.isReverseType()){
345                section.addToReverseList(ep);
346            }
347        }
348    }
349
350    private List <EntryPoint> getBlockEntryPointsList(Block b, List <EntryPoint> entryPointList) {
351        List <EntryPoint> list = new ArrayList<>();
352        for (int i=0; i<entryPointList.size(); i++) {
353            EntryPoint ep = entryPointList.get(i);
354            if (ep.getBlock().equals(b)) {
355                list.add(ep);
356            }
357        }
358        return list;
359    }
360
361    @Override
362    @Nonnull
363    public String getBeanTypeHandled(boolean plural) {
364        return Bundle.getMessage(plural ? "BeanNameSections" : "BeanNameSection");
365    }
366
367    @Override
368    public void dispose(){
369        InstanceManager.getDefault(SensorManager.class).removeVetoableChangeListener(this);
370        InstanceManager.getDefault(BlockManager.class).removeVetoableChangeListener(this);
371        super.dispose();
372    }
373
374    private final static Logger log = LoggerFactory.getLogger(DefaultSectionManager.class);
375
376}