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}