001package jmri.jmrit.logix;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004import java.util.Iterator;
005import java.util.List;
006import java.util.Objects;
007import javax.swing.Timer;
008import jmri.BeanSetting;
009import jmri.Block;
010import jmri.InstanceManager;
011import jmri.Turnout;
012
013import org.slf4j.Logger;
014import org.slf4j.LoggerFactory;
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                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)) {
079            if (_toPortal != null) {
080                return _toPortal.getName();
081            }
082        }
083        if (_toPortal != null && _toPortal.getName().equals(name)) {
084            if (_fromPortal != null) {
085                return _fromPortal.getName();
086            }
087        }
088        return null;
089    }
090
091    @SuppressFBWarnings(value="BC_UNCONFIRMED_CAST_OF_RETURN_VALUE", justification="OBlock extends Block")
092    public void setName(String name) {
093        log.debug("OPath \"{}\" setName to \"{}\"", _name, name);
094        if (name == null || name.length() == 0) {
095            return;
096        }
097        String oldName = _name;
098        _name = name;
099        OBlock block = (OBlock) getBlock();
100        block.pseudoPropertyChange("pathName", oldName, _name); // for IndicatorTrack icons
101        InstanceManager.getDefault(WarrantManager.class).pathNameChange(block, oldName, _name);
102        if (_fromPortal != null) {
103            if (_fromPortal.addPath(this)) {
104                return;
105            }
106        }
107        if (_toPortal != null) {
108            _toPortal.addPath(this);
109        }
110    }
111
112    public String getName() {
113        return _name;
114    }
115
116    public void setFromPortal(Portal p) {
117        if (p != null) {
118            log.debug("OPath \"{}\" setFromPortal= \"{}\"", _name, p.getName());
119        }
120        _fromPortal = p;
121    }
122
123    public Portal getFromPortal() {
124        return _fromPortal;
125    }
126
127    public void setToPortal(Portal p) {
128        if (p != null) {
129            log.debug("OPath \"{}\" setToPortal= \"{}\"", _name, p.getName());
130        }
131        _toPortal = p;
132    }
133
134    public Portal getToPortal() {
135        return _toPortal;
136    }
137
138    /**
139     * Set path turnout commanded state and lock state
140     *
141     * @param delay     following actions in seconds
142     * @param set       when true, command turnout to settings, false don't set
143     *                  command - just do lock setting
144     * @param lockState set when lock==true, lockState unset when lock==false
145     * @param lock      If lockState==0 setLocked() is not called. (lockState
146     *                  should be 1,2,3)
147     */
148    public void setTurnouts(int delay, boolean set, int lockState, boolean lock) {
149        if (delay > 0) {
150            if (!_timerActive) {
151                // Create a timer if one does not exist
152                if (_timer == null) {
153                    _listener = new TimeTurnout();
154                    _timer = new Timer(2000, _listener);
155                    _timer.setRepeats(false);
156                }
157                _listener.setList(getSettings());
158                _listener.setParams(set, lockState, lock);
159                _timer.setInitialDelay(delay * 1000);
160                _timer.start();
161                _timerActive = true;
162            } else {
163                log.warn("timer already active for delayed turnout action on path {}", this);
164            }
165        } else {
166            fireTurnouts(getSettings(), set, lockState, lock);
167        }
168    }
169
170    private void fireTurnouts(List<BeanSetting> list, boolean set, int lockState, boolean lock) {
171        for (BeanSetting bs : list) {
172            Turnout t = (Turnout) bs.getBean();
173            if (set) {
174                t.setCommandedState(bs.getSetting());
175            }
176            if (lockState > 0) {
177                t.setLocked(lockState, lock);
178            }
179        }
180    }
181
182    public void dispose() {
183        if (_fromPortal != null) {
184            _fromPortal.removePath(this);
185        }
186        if (_toPortal != null) {
187            _toPortal.removePath(this);
188        }
189    }
190
191    /**
192     * Class for defining ActionListener for ACTION_DELAYED_TURNOUT
193     */
194    class TimeTurnout implements java.awt.event.ActionListener {
195
196        private List<BeanSetting> list;
197        private int lockState;
198        boolean set;
199        boolean lock;
200
201        public TimeTurnout() {
202            // no actions required to construct
203        }
204
205        void setList(List<BeanSetting> l) {
206            list = l;
207        }
208
209        void setParams(boolean s, int ls, boolean l) {
210            set = s;
211            lockState = ls;
212            lock = l;
213        }
214
215        @Override
216        public void actionPerformed(java.awt.event.ActionEvent event) {
217            fireTurnouts(list, set, lockState, lock);
218            // Turn Timer OFF
219            if (_timer != null) {
220                _timer.stop();
221                _timerActive = false;
222            }
223        }
224    }
225
226    public String getDescription() {
227        StringBuilder sb = new StringBuilder("\"");
228        sb.append(_name);
229        sb.append("\" from portal \"");
230        sb.append(_fromPortal==null?"null":_fromPortal.getName());
231        sb.append("\" to portal \"");
232        sb.append(_toPortal==null?"null":_toPortal.getName());
233        sb.append("\"");
234        return sb.toString();
235    }
236
237    @Override
238    public String toString() {
239        StringBuilder sb = new StringBuilder("OPath \"");
240        sb.append(_name);
241        sb.append("\" on block \"");
242        sb.append(getBlock()==null?"null":getBlock().getDisplayName());
243        sb.append("\" from portal \"");
244        sb.append(_fromPortal==null?"null":_fromPortal.getName());
245        sb.append("\" to portal \"");
246        sb.append(_toPortal==null?"null":_toPortal.getName());
247        sb.append("\" sets ");
248        sb.append(getSettings().size());
249        sb.append("\" turnouts.");
250        return sb.toString();
251    }
252
253    /**
254     * {@inheritDoc} Does not allow duplicate settings.
255     */
256    @Override
257    public void addSetting(BeanSetting t) {
258        for (BeanSetting bs : getSettings()) {
259            if (bs.getBeanName().equals(t.getBeanName())) {
260                log.error("TO setting for \"{}\" already set to {}", t.getBeanName(), bs.getSetting());
261                return;
262            }
263        }
264        super.addSetting(t);
265    }
266
267    @Override
268    public int hashCode() {
269        int hash = 7;
270        hash = 67 * hash + Objects.hashCode(this.getBlock());
271        hash = 67 * hash + Objects.hashCode(this._fromPortal);
272        hash = 67 * hash + Objects.hashCode(this._toPortal);
273        hash = 67 * hash + Objects.hashCode(this.getSettings());
274        return hash;
275    }
276
277    /**
278     * {@inheritDoc}
279     *
280     * Override to indicate logical equality for use as paths in OBlocks.
281     */
282    @Override
283    public boolean equals(Object obj) {
284        if (obj == this) {
285            return true;
286        }
287        if (obj == null) {
288            return false;
289        }
290        if (getClass() != obj.getClass()) {
291            return false;
292        }
293        OPath path = (OPath) obj;
294        if (getBlock() != path.getBlock()) {
295            return false;
296        }
297        Portal fromPort = path.getFromPortal();
298        Portal toPort = path.getToPortal();
299        int numPortals = 0;
300        if (fromPort != null) {
301            numPortals++;
302        }
303        if (toPort != null) {
304            numPortals++;
305        }
306        if (_fromPortal != null) {
307            numPortals--;
308        }
309        if (_toPortal != null) {
310            numPortals--;
311        }
312        if (numPortals != 0) {
313            return false;
314        }
315        if (_fromPortal != null && !_fromPortal.equals(fromPort) && !_fromPortal.equals(toPort)) {
316            return false;
317        }
318        if (_toPortal != null && !_toPortal.equals(toPort) && !_toPortal.equals(fromPort)) {
319            return false;
320        }
321        List<BeanSetting> settings = path.getSettings();
322        if (settings.size() != getSettings().size()) {
323            return false;
324        }
325        Iterator<BeanSetting> iter = settings.iterator();
326        Iterator<BeanSetting> it = getSettings().iterator();
327        boolean found = false;
328        while (iter.hasNext()) {
329            BeanSetting beanSetting = iter.next();
330            while (it.hasNext()) {
331                found = false;
332                BeanSetting bs = it.next();
333                if (bs.getBean().getSystemName().equals(beanSetting.getBean().getSystemName())
334                        && bs.getSetting() == beanSetting.getSetting()) {
335                    found = true;
336                    break;
337                }
338            }
339            if (!found) {
340                return false;
341            }
342        }
343        return true;
344    }
345
346    private static final Logger log = LoggerFactory.getLogger(OPath.class);
347
348}