001    package jigcell.compare.impl;
002    
003    import java.awt.EventQueue;
004    import java.beans.PropertyChangeEvent;
005    import java.beans.PropertyChangeListener;
006    import java.beans.PropertyChangeSupport;
007    import jigcell.compare.IDataGenerator;
008    import jigcell.compare.IProgrammableDataGenerator;
009    
010    /**
011     * Evaluates a list of data generators on a background thread.
012     *
013     * <p>
014     * This code is licensed under the DARPA BioCOMP Open Source License.  See LICENSE for more details.
015     * </p>
016     *
017     * @author Nicholas Allen
018     */
019    
020    public class DataGeneratorEvaluator implements PropertyChangeListener {
021    
022       /**
023        * Option for name to display when evaluating a generator
024        */
025    
026       public final static String OPTION_EVALUATIONNAME = "DataGeneratorEvaluator.evaluationDisplayName";
027    
028       /**
029        * Option for indicating the target result when evaluating a generator
030        */
031    
032       public final static String OPTION_EVALUATIONTARGET = "DataGeneratorEvaluator.evaluationTarget";
033    
034       /**
035        * Event name for cancelling the evaluation
036        */
037    
038       public final static String PROPERTY_CANCEL = "DataGeneratorEvaluator.quit";
039    
040       /**
041        * Event name for a complete evaluation
042        */
043    
044       public final static String PROPERTY_EVALUATIONFINISHED = "DataGeneratorEvaluator.evaluateFinished";
045    
046       /**
047        * Event name for updates
048        */
049    
050       public final static String PROPERTY_EVALUATIONUPDATE = "DataGeneratorEvaluator.evaluateUpdate";
051    
052       /**
053        * Event name for a message update
054        */
055    
056       public final static String PROPERTY_MESSAGE = "DataGeneratorEvaluator.message";
057    
058       /**
059        * Event name for a progress update
060        */
061    
062       public final static String PROPERTY_PROGRESS = "DataGeneratorEvaluator.progress";
063    
064       /**
065        * The list of generators
066        */
067    
068       protected IDataGenerator generators [];
069    
070       /**
071        * Whether the evaluation was cancelled
072        */
073    
074       protected boolean cancel;
075    
076       /**
077        * The data generator currently being evaluated
078        */
079    
080       protected IDataGenerator currentGenerator;
081    
082       /**
083        * Current position in the list of generators
084        */
085    
086       protected int position;
087    
088       /**
089        * Callback for processing generators prior to execution
090        */
091    
092       protected IProcessor processor;
093    
094       /**
095        * Support for handling property change events
096        */
097    
098       private PropertyChangeSupport propertySupport;
099    
100       /**
101        * Thread for evaluation
102        */
103    
104       private Thread executor;
105    
106       /**
107        * Performs any needed processing on the data generator before evaluation.
108        */
109    
110       public interface IProcessor {
111    
112          /**
113           * Creates a replacement data generator for evaluation.  This callback will be made on the event thread.  No processing will be done on
114           * the generator prior to performing this callback.
115           *
116           * @param generator Generator
117           */
118    
119          IDataGenerator processGenerator (IDataGenerator generator);
120       }
121    
122       /**
123        * Creates a new evaluator for data generators.
124        *
125        * @param generators List of generators to evaluate
126        */
127    
128       public DataGeneratorEvaluator (IDataGenerator generators []) {
129          this (generators, null);
130       }
131    
132       /**
133        * Creates a new evaluator for data generators.
134        *
135        * @param generators List of generators to evaluate
136        * @param processor Optional component to process a generator before evaluation
137        */
138    
139       public DataGeneratorEvaluator (IDataGenerator generators [], IProcessor processor) {
140          if (generators == null)
141             throw new IllegalArgumentException ("List of generators cannot be null.");
142          this.generators = generators;
143          this.processor = processor;
144          propertySupport = new PropertyChangeSupport (this);
145       }
146    
147       /**
148        * Adds a PropertyChangeListener to the listener list.
149        *
150        * @param listener Listener
151        */
152    
153       public void addPropertyChangeListener (PropertyChangeListener listener) {
154          propertySupport.addPropertyChangeListener (listener);
155       }
156    
157       /**
158        * Attempts to cancel the current evaluation.
159        */
160    
161       public synchronized void cancel () {
162          if (cancel)
163             return;
164          cancel = true;
165          if (executor != null)
166             executor.interrupt ();
167       }
168    
169       /**
170        * Starts evaluating the given generators.
171        */
172    
173       public synchronized void execute () {
174          if (executor != null)
175             throw new IllegalThreadStateException ("This evaluator is already running.");
176          cancel = false;
177          executor = new Thread (ProxyBuilder.proxyRunnable (this, "evaluate"));
178          executor.start ();
179       }
180    
181       public void propertyChange (PropertyChangeEvent e) {
182          if (PROPERTY_CANCEL.equals (e.getPropertyName ()))
183             cancel ();
184       }
185    
186       /**
187        * Removes a PropertyChangeListener from the listener list.
188        *
189        * @param listener Listener
190        */
191    
192       public void removePropertyChangeListener (PropertyChangeListener listener) {
193          propertySupport.removePropertyChangeListener (listener);
194       }
195    
196       /**
197        * Perform a generator evaluation.
198        */
199    
200       protected void doEvaluate () {
201          if (currentGenerator == null || Thread.currentThread ().isInterrupted ())
202             return;
203          if (currentGenerator instanceof IProgrammableDataGenerator) {
204             IDataGenerator target = (IDataGenerator) currentGenerator.getOption (OPTION_EVALUATIONTARGET);
205             if (target != null) {
206                ((IProgrammableDataGenerator) currentGenerator).getElement (new EvaluationCallStack (target));
207                return;
208             }
209          }
210          currentGenerator.getElement ();
211       }
212    
213       /**
214        * Perform a status update following generator evaluation.
215        */
216    
217       protected synchronized void doPostUpdate () {
218          if (currentGenerator != null)
219             propertySupport.firePropertyChange (PROPERTY_EVALUATIONUPDATE, generators [position], currentGenerator);
220          if (cancel || position == generators.length)
221             propertySupport.firePropertyChange (PROPERTY_EVALUATIONFINISHED, null, null);
222          else
223             propertySupport.firePropertyChange (PROPERTY_PROGRESS, new Integer (position), new Integer (position + 1));
224       }
225    
226       /**
227        * Perform a status update before generator evaluation.
228        */
229    
230       protected synchronized void doPreUpdate () {
231          if (processor != null)
232             try {
233                currentGenerator = processor.processGenerator (currentGenerator);
234             } catch (Exception e) {
235                Compare.assertion ("Error encountered while processing generator.", e);
236             }
237          String name = null;
238          if (currentGenerator != null) {
239             name = (String) currentGenerator.getOption (OPTION_EVALUATIONNAME);
240             if (name == null)
241                name = currentGenerator.getName ();
242          }
243          propertySupport.firePropertyChange (PROPERTY_MESSAGE, null, name == null ? "No Value" : name);
244       }
245    
246       /**
247        * Evaluates the list of generators.
248        */
249    
250       protected void evaluate () {
251          position = 0;
252          Runnable preUpdate = ProxyBuilder.proxyRunnable (this, "doPreUpdate");
253          Runnable postUpdate = ProxyBuilder.proxyRunnable (this, "doPostUpdate");
254          for (int l = generators.length; position < l && !cancel; position++) {
255             currentGenerator = generators [position];
256             try {
257                EventQueue.invokeAndWait (preUpdate);
258             } catch (Exception e) {
259                Compare.assertion ("Unable to update display before evaluation.", e);
260             }
261             try {
262                doEvaluate ();
263             } catch (Exception e) {
264                Compare.assertion ("Error encoutnered while evaluating generator.", e);
265             }
266             try {
267                EventQueue.invokeAndWait (postUpdate);
268             } catch (Exception e) {
269                Compare.assertion ("Unable to update display after evaluation.", e);
270             }
271          }
272          currentGenerator = null;
273          doPostUpdate ();
274          synchronized (this) {
275             executor = null;
276          }
277       }
278    }