001package jmri.jmrit.automat;
002
003import java.beans.PropertyChangeEvent;
004import java.util.Arrays;
005import jmri.NamedBean;
006import jmri.util.PropertyChangeEventQueue;
007import jmri.util.ThreadingUtil;
008
009/**
010 * A Siglet is a "an embedded signal automation", like an "applet" an embedded
011 * application.
012 * <p>
013 * Subclasses must load the inputs and outputs arrays during the defineIO
014 * method. When any of these change, the Siglet must then recompute and apply
015 * the output signal settings via their implementation of the {@link #setOutput}
016 * method.
017 * <p>
018 * Siglets may not run in their own thread; they should not use wait() in any of
019 * its various forms.
020 * <p>
021 * Siglet was separated from AbstractAutomaton in JMRI 4.9.2
022 * <p>
023 * Do not have any overlap between the items in the input and output lists; this
024 * will cause a recursive invocation when the output changes.
025 *
026 * @author Bob Jacobsen Copyright (C) 2003, 2017
027 */
028abstract public class Siglet {
029
030    public Siglet() {
031        this.name = "";
032    }
033
034    public Siglet(String name) {
035        this.name = name;
036    }
037
038    public NamedBean[] inputs;      // public for Jython subclass access
039    public NamedBean[] outputs;     // public for Jython subclass access
040
041    /**
042     * User-provided routine to define the input and output objects to be
043     * handled. Invoked during the Siglet {@link #start()} call.
044     */
045    abstract public void defineIO();
046
047    /**
048     * User-provided routine to compute new output state and apply it.
049     */
050    abstract public void setOutput();
051
052    final public String getName() {
053        return name;
054    }
055    private String name;
056
057    final public void setName(String name) {
058        this.name = name;
059    }
060
061    public void start() {
062        Thread previousThread = thread;
063        try {
064            if (previousThread != null) {
065                previousThread.join();
066            }
067        } catch (InterruptedException e) {
068            log.warn("Aborted start() due to interrupt");
069        }
070        if (thread != null) {
071            log.error("Found thread != null, which is an internal synchronization error for {}", name);
072        }
073
074        defineIO(); // user method that will load inputs
075        if (inputs == null || inputs.length < 1 || (inputs.length == 1 && inputs[0] == null)) {
076            log.error("Siglet start invoked {}, but no inputs provided", ((name!=null && !name.isEmpty()) ? "for \""+name+"\"" : "(without a name)") );
077            throw new IllegalArgumentException("No defineIO inputs");
078        }
079
080        pq = new PropertyChangeEventQueue(inputs);
081        setOutput();
082
083        // run one cycle at start
084        thread = jmri.util.ThreadingUtil.newThread(() -> {
085            while (true) {
086                try {
087                    PropertyChangeEvent pe = pq.take();
088                    // _any_ event drives output
089                    log.trace("driving setOutput from {}", pe);
090                    ThreadingUtil.runOnLayout(() -> {
091                        setOutput();
092                    });
093                } catch (InterruptedException e) {
094                    log.trace("InterruptedException");
095                    thread.interrupt();
096                }
097                if (thread.isInterrupted()) {
098                    log.trace("isInterrupted()");
099                    // done
100                    pq.dispose();
101                    thread = null; // flag that this won't execute again
102                    return;
103                }
104            }
105        });
106        thread.setDaemon(true);
107        thread.setName(getName());
108        thread.start();
109    }
110
111    /**
112     * Stop execution of the logic.
113     */
114    public void stop() {
115        if (thread != null) {
116            Thread tempThread = thread;
117            tempThread.interrupt();
118            try {
119                tempThread.join();
120            } catch (InterruptedException ex) {
121                log.debug("stop interrupted");
122            }
123        }
124    }
125
126    public boolean isRunning() {
127        return thread != null;
128    }
129    /**
130     * Set inputs to the items in in.
131     *
132     * @param in the inputs to set
133     */
134    public void setInputs(NamedBean[] in) {
135        inputs = Arrays.copyOf(in, in.length);
136    }
137
138    protected PropertyChangeEventQueue pq;
139    protected Thread thread;
140    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Siglet.class);
141
142}