001package jmri.jmrit.logix;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004
005import java.util.Iterator;
006import java.util.List;
007import java.util.Objects;
008
009import javax.swing.Timer;
010
011import jmri.BeanSetting;
012import jmri.Block;
013import jmri.InstanceManager;
014import jmri.Turnout;
015
016/**
017 * Extends jmri.Path. An OPath is a route that traverses a Block from one
018 * boundary to another. The dest parameter of Path (renamed to owner) is
019 * used to reference the Block to which this OPath belongs. (Not a
020 * destination Block as might be inferred from the naming in Path.java)
021 * <p>
022 * An OPath inherits the List of BeanSettings for all the turnouts needed to
023 * traverse the Block. It also has references to the Portals (block boundary
024 * objects) through which it enters or exits the block. One of these may be
025 * null, if the OPath dead ends within the block.
026 *
027 * @author Pete Cressman Copyright (C) 2009
028 */
029public class OPath extends jmri.Path {
030
031    private Portal _fromPortal;
032    private Portal _toPortal;
033    private String _name;
034    private Timer _timer;
035    private boolean _timerActive = false;
036    private TimeTurnout _listener;
037
038    /**
039     * Create an OPath object with default directions of NONE, and no setting
040     * element.
041     *
042     * @param owner Block owning the path
043     * @param name  name of the path
044     */
045    public OPath(Block owner, String name) {
046        super(owner, 0, 0);
047        _name = name;
048    }
049
050    /**
051     * Create an OPath object with default directions of NONE, and no setting
052     * element.
053     *
054     * @param owner    Block owning the path
055     * @param name     name of the path
056     * @param entry    Portal where path enters
057     * @param exit     Portal where path exits
058     * @param settings array of turnout settings of the path
059     */
060    public OPath(String name, OBlock owner, Portal entry, Portal exit, List<BeanSetting> settings) {
061        super(owner, 0, 0);
062        _name = name;
063        _fromPortal = entry;
064        _toPortal = exit;
065        if (settings != null) {
066            for (BeanSetting setting : settings) {
067                OPath.this.addSetting(setting);
068            }
069        }
070        if (log.isDebugEnabled()) {
071            log.debug("OPath Ctor: name= {}, block= {}, fromPortal= {}, toPortal= {}",
072                name, owner.getDisplayName(), (_fromPortal == null ? "null" : _fromPortal.getName()),
073                    (_toPortal == null ? "null" : _toPortal.getName()));
074        }
075    }
076
077    protected String getOppositePortalName(String name) {
078        if ( _fromPortal != null && _fromPortal.getName().equals(name) && _toPortal != null ) {
079            return _toPortal.getName();
080        }
081        if ( _toPortal != null && _toPortal.getName().equals(name) && _fromPortal != null ) {
082            return _fromPortal.getName();
083        }
084        return null;
085    }
086
087    @SuppressFBWarnings(value="BC_UNCONFIRMED_CAST_OF_RETURN_VALUE", justification="OBlock extends Block")
088    public void setName(String name) {
089        log.debug("OPath \"{}\" setName to \"{}\"", _name, name);
090        if (name == null || name.length() == 0) {
091            return;
092        }
093        String oldName = _name;
094        _name = name;
095        OBlock block = (OBlock) getBlock();
096        block.pseudoPropertyChange("pathName", oldName, _name); // for IndicatorTrack icons
097        InstanceManager.getDefault(WarrantManager.class).pathNameChange(block, oldName, _name);
098        if ( _fromPortal != null && _fromPortal.addPath(this ) ) {
099            return;
100        }
101        if (_toPortal != null) {
102            _toPortal.addPath(this);
103        }
104    }
105
106    public String getName() {
107        return _name;
108    }
109
110    public void setFromPortal(Portal p) {
111        if (p != null) {
112            log.debug("OPath \"{}\" setFromPortal= \"{}\"", _name, p.getName());
113        }
114        _fromPortal = p;
115    }
116
117    public Portal getFromPortal() {
118        return _fromPortal;
119    }
120
121    public void setToPortal(Portal p) {
122        if (p != null) {
123            log.debug("OPath \"{}\" setToPortal= \"{}\"", _name, p.getName());
124        }
125        _toPortal = p;
126    }
127
128    public Portal getToPortal() {
129        return _toPortal;
130    }
131
132    /**
133     * Set path turnout commanded state and lock state
134     *
135     * @param delay     following actions in seconds
136     * @param set       when true, command turnout to settings, false don't set
137     *                  command - just do lock setting
138     * @param lockState set when lock==true, lockState unset when lock==false
139     * @param lock      If lockState==0 setLocked() is not called. (lockState
140     *                  should be 1,2,3)
141     */
142    public void setTurnouts(int delay, boolean set, int lockState, boolean lock) {
143        if (delay > 0) {
144            if (!_timerActive) {
145                // Create a timer if one does not exist
146                if (_timer == null) {
147                    _listener = new TimeTurnout();
148                    _timer = new Timer(2000, _listener);
149                    _timer.setRepeats(false);
150                }
151                _listener.setList(getSettings());
152                _listener.setParams(set, lockState, lock);
153                _timer.setInitialDelay(delay * 1000);
154                _timer.start();
155                _timerActive = true;
156            } else {
157                log.warn("timer already active for delayed turnout action on path {}", this);
158            }
159        } else {
160            fireTurnouts(getSettings(), set, lockState, lock);
161        }
162    }
163
164    private void fireTurnouts(List<BeanSetting> list, boolean set, int lockState, boolean lock) {
165        for (BeanSetting bs : list) {
166            Turnout t = (Turnout) bs.getBean();
167            if (set) {
168                t.setCommandedState(bs.getSetting());
169            }
170            if (lockState > 0) {
171                t.setLocked(lockState, lock);
172            }
173        }
174    }
175
176    public void dispose() {
177        if (_fromPortal != null) {
178            _fromPortal.removePath(this);
179        }
180        if (_toPortal != null) {
181            _toPortal.removePath(this);
182        }
183    }
184
185    /**
186     * Class for defining ActionListener for ACTION_DELAYED_TURNOUT
187     */
188    private class TimeTurnout implements java.awt.event.ActionListener {
189
190        private List<BeanSetting> list;
191        private int lockState;
192        boolean set;
193        boolean lock;
194
195        public TimeTurnout() {
196            // no actions required to construct
197        }
198
199        void setList(List<BeanSetting> l) {
200            list = l;
201        }
202
203        void setParams(boolean s, int ls, boolean l) {
204            set = s;
205            lockState = ls;
206            lock = l;
207        }
208
209        @Override
210        public void actionPerformed(java.awt.event.ActionEvent event) {
211            fireTurnouts(list, set, lockState, lock);
212            // Turn Timer OFF
213            if (_timer != null) {
214                _timer.stop();
215                _timerActive = false;
216            }
217        }
218    }
219
220    public String getDescription() {
221        StringBuilder sb = new StringBuilder("\"");
222        sb.append(_name);
223        sb.append("\" from portal \"");
224        sb.append(_fromPortal==null?"null":_fromPortal.getName());
225        sb.append("\" to portal \"");
226        sb.append(_toPortal==null?"null":_toPortal.getName());
227        sb.append("\"");
228        return sb.toString();
229    }
230
231    @Override
232    public String toString() {
233        StringBuilder sb = new StringBuilder("OPath \"");
234        sb.append(_name);
235        sb.append("\" on block \"");
236        sb.append(getBlock()==null?"null":getBlock().getDisplayName());
237        sb.append("\" from portal \"");
238        sb.append(_fromPortal==null?"null":_fromPortal.getName());
239        sb.append("\" to portal \"");
240        sb.append(_toPortal==null?"null":_toPortal.getName());
241        sb.append("\" sets ");
242        sb.append(getSettings().size());
243        sb.append("\" turnouts.");
244        return sb.toString();
245    }
246
247    /**
248     * {@inheritDoc} Does not allow duplicate settings.
249     */
250    @Override
251    public void addSetting(BeanSetting t) {
252        for (BeanSetting bs : getSettings()) {
253            if (bs.getBeanName().equals(t.getBeanName())) {
254                log.error("TO setting for \"{}\" already set to {}", t.getBeanName(), bs.getSetting());
255                return;
256            }
257        }
258        super.addSetting(t);
259    }
260
261    @Override
262    public int hashCode() {
263        int hash = 7;
264        hash = 67 * hash + Objects.hashCode(this.getBlock());
265        hash = 67 * hash + Objects.hashCode(this._fromPortal);
266        hash = 67 * hash + Objects.hashCode(this._toPortal);
267        hash = 67 * hash + Objects.hashCode(this.getSettings());
268        return hash;
269    }
270
271    /**
272     * {@inheritDoc}
273     *
274     * Override to indicate logical equality for use as paths in OBlocks.
275     */
276    @Override
277    public boolean equals(Object obj) {
278        if (obj == this) {
279            return true;
280        }
281        if (obj == null) {
282            return false;
283        }
284        if (getClass() != obj.getClass()) {
285            return false;
286        }
287        OPath path = (OPath) obj;
288        if (getBlock() != path.getBlock()) {
289            return false;
290        }
291        Portal fromPort = path.getFromPortal();
292        Portal toPort = path.getToPortal();
293        int numPortals = 0;
294        if (fromPort != null) {
295            numPortals++;
296        }
297        if (toPort != null) {
298            numPortals++;
299        }
300        if (_fromPortal != null) {
301            numPortals--;
302        }
303        if (_toPortal != null) {
304            numPortals--;
305        }
306        if (numPortals != 0) {
307            return false;
308        }
309        if (_fromPortal != null && !_fromPortal.equals(fromPort) && !_fromPortal.equals(toPort)) {
310            return false;
311        }
312        if (_toPortal != null && !_toPortal.equals(toPort) && !_toPortal.equals(fromPort)) {
313            return false;
314        }
315        List<BeanSetting> settings = path.getSettings();
316        if (settings.size() != getSettings().size()) {
317            return false;
318        }
319        Iterator<BeanSetting> iter = settings.iterator();
320        Iterator<BeanSetting> it = getSettings().iterator();
321        boolean found = false;
322        while (iter.hasNext()) {
323            BeanSetting beanSetting = iter.next();
324            while (it.hasNext()) {
325                found = false;
326                BeanSetting bs = it.next();
327                if (bs.getBean().getSystemName().equals(beanSetting.getBean().getSystemName())
328                        && bs.getSetting() == beanSetting.getSetting()) {
329                    found = true;
330                    break;
331                }
332            }
333            if (!found) {
334                return false;
335            }
336        }
337        return true;
338    }
339
340    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(OPath.class);
341
342}