001    package jigcell.compare.data;
002    
003    import EDU.oswego.cs.dl.util.concurrent.ReentrantLock;
004    import EDU.oswego.cs.dl.util.concurrent.Sync;
005    import java.beans.PropertyChangeEvent;
006    import java.lang.reflect.Constructor;
007    import javax.swing.JComponent;
008    import javax.swing.event.EventListenerList;
009    import jigcell.compare.IComponentDescription;
010    import jigcell.compare.ICustomizableInterface;
011    import jigcell.compare.IDataElement;
012    import jigcell.compare.IDataGenerator;
013    import jigcell.compare.IEvaluationCallStack;
014    import jigcell.compare.IEvaluationListener;
015    import jigcell.compare.IProgrammableDataGenerator;
016    import jigcell.compare.ITypeChecker;
017    import jigcell.compare.data.type.UntypedTypeChecker;
018    import jigcell.compare.impl.Compare;
019    import jigcell.compare.impl.EvaluationCallStack;
020    
021    /**
022     * A data generator type whose value is determined programmatically, usually with some kind of interface for controlling the program action.
023     *
024     * <p>
025     * There are three safe usage modes for this generator:
026     * </p>
027     *
028     * <ol>
029     * <li>
030     * Caller holds no locks, operations have no guarantee of consistency
031     * </li>
032     * <li>
033     * Caller synchronizes on this generator accepting an unbounded delay, operations have a guarantee of consistency
034     * </li>
035     * <li>
036     * Caller tests the evaluation lock, operations have a guarantee of consistency
037     * </li>
038     * </ol>
039     *
040     * <p>
041     * This code is licensed under the DARPA BioCOMP Open Source License.  See LICENSE for more details.
042     * </p>
043     *
044     * @author Nicholas Allen
045     */
046    
047    public abstract class ProgrammableDataGenerator extends EditableDataGenerator implements IProgrammableDataGenerator {
048    
049       /**
050        * Option for the main display of this generator
051        */
052    
053       private final static String OPTION_DISPLAYCONSTRUCTOR = "ProgrammableDataGenerator.displayConstructor";
054    
055       /**
056        * Option for the main display of this generator
057        */
058    
059       private final static String OPTION_DISPLAYINSTANCE = "ProgrammableDataGenerator.displayInstance";
060    
061       /**
062        * Whether this generator can have its input description queried
063        */
064    
065       protected boolean allowDescriptionQuery;
066    
067       /**
068        * Manages listeners for this generator
069        */
070    
071       protected transient EventListenerList listenerList;
072    
073       /**
074        * The current evaluation call stack
075        */
076    
077       protected transient IEvaluationCallStack callStack;
078    
079       /**
080        * Lock for controlling user interface
081        */
082    
083       private transient Object LOCK_INTERFACE;
084    
085       /**
086        * Lock for controlling element evaluation
087        */
088    
089       private final transient Sync LOCK_EVALUATION = new ReentrantLock ();
090    
091       /**
092        * Acts as a placeholder for data generators that do not define a user interface handler.
093        */
094    
095       private final static class DummyCustomizableInterface implements ICustomizableInterface {
096    
097          /**
098           * Data generator being wrapped
099           */
100    
101          private final ProgrammableDataGenerator generator;
102    
103          /**
104           * Wraps the data generator with a dummy interface.
105           *
106           * @param generator Programmable data generator to wrap
107           */
108    
109          public DummyCustomizableInterface (IProgrammableDataGenerator generator) {
110             this.generator = (ProgrammableDataGenerator) generator;
111          }
112    
113          /**
114           * {@inheritDoc}
115           */
116    
117          public JComponent createCustomizer (String name) {
118             return generator.createCustomizer (name);
119          }
120    
121          /**
122           * {@inheritDoc}
123           */
124    
125          public void destroyCustomizer (String name) {}
126    
127          /**
128           * {@inheritDoc}
129           */
130    
131          public IComponentDescription getInputDescription () {
132             return null;
133          }
134    
135          /**
136           * {@inheritDoc}
137           */
138    
139          public IComponentDescription getOutputDescription () {
140             return null;
141          }
142    
143          /**
144           * {@inheritDoc}
145           */
146    
147          public boolean hasMultipleViewSupport () {
148             return false;
149          }
150    
151          /**
152           * {@inheritDoc}
153           */
154    
155          public void updateInterface (String propertyName) {}
156       }
157    
158       static {
159          Compare.addTransient (ProgrammableDataGenerator.class, "allowDescriptionQuery");
160          setOptionType (OPTION_COMPARE, Option.COPYONLY);
161          setOptionType (OPTION_DISPLAYCONSTRUCTOR, Option.COPYONLY);
162          setOptionType (OPTION_INPUTTYPECHECKER, Option.COPYONLY);
163          setOptionType (OPTION_OUTPUTTYPECHECKER, Option.COPYONLY);
164       }
165    
166       /**
167        * Creates a new data generator.
168        */
169    
170       public ProgrammableDataGenerator () {
171          super (null);
172          setInterfaceClass ("jigcell.compare.data.ProgrammableDataGenerator$DummyCustomizableInterface");
173          setAllowDescriptionQuery (true);
174          setInputTypeChecker (UntypedTypeChecker.getInstance ());
175          setOutputTypeChecker (UntypedTypeChecker.getInstance ());
176       }
177    
178       /**
179        * {@inheritDoc}
180        */
181    
182       public void addEvaluationListener (IEvaluationListener listener) {
183          listenerList.add (IEvaluationListener.class, listener);
184       }
185    
186       /**
187        * {@inheritDoc}
188        */
189    
190       public synchronized void clear () {
191          super.clear ();
192          updateInterface (null);
193       }
194    
195       /**
196        * Whether this generator allows the input description to be queried.
197        */
198    
199       public boolean getAllowDescriptionQuery () {
200          return allowDescriptionQuery;
201       }
202    
203       /**
204        * {@inheritDoc}
205        */
206    
207       public IEvaluationCallStack getCallStack () {
208          return callStack;
209       }
210    
211       /**
212        * {@inheritDoc}
213        */
214    
215       public IDataElement getElement () {
216          return getElement (new EvaluationCallStack ());
217       }
218    
219       /**
220        * {@inheritDoc}
221        */
222    
223       public IDataElement getElement (IEvaluationCallStack callStack) {
224          assert callStack != null;
225          IDataElement result = null;
226          fireEvaluationStart ();
227          try {
228             Compare.acquireSync (LOCK_EVALUATION);
229             this.callStack = callStack;
230             callStack.pushFrame (this);
231             synchronized (this) {
232                result = super.getElement ();
233                if (result == null) {
234                   result = evaluate ();
235                   if (result != null)
236                      setElement (result);
237                }
238             }
239             return result;
240          } catch (RuntimeException e) {
241             fireEvaluationError (new RuntimeException ("Uncaught exception during evaluation", e));
242          } finally {
243             if (callStack != null) {
244                callStack.popFrame ();
245                callStack = null;
246             }
247             LOCK_EVALUATION.release ();
248             fireEvaluationStop (result);
249          }
250          return null;
251       }
252    
253       /**
254        * {@inheritDoc}
255        */
256    
257       public Sync getEvaluationLock () {
258          return LOCK_EVALUATION;
259       }
260    
261       /**
262        * {@inheritDoc}
263        */
264    
265       public ITypeChecker getInputTypeChecker () {
266          return (ITypeChecker) getOption (OPTION_INPUTTYPECHECKER);
267       }
268    
269       /**
270        * The customizable interface for this generator.  An interface is lazily created so calling this method will create an interface if one does
271        * not already exist.
272        */
273    
274       public ICustomizableInterface getInterface () {
275          ICustomizableInterface display = null;
276          synchronized (LOCK_INTERFACE) {
277             display = peekInterface ();
278             if (display == null) {
279                Constructor displayConstructor = (Constructor) getOption (OPTION_DISPLAYCONSTRUCTOR);
280                try {
281                   display = (ICustomizableInterface) displayConstructor.newInstance (new Object [] {this});
282                   setOption (OPTION_DISPLAYINSTANCE, display);
283                } catch (Exception e) {
284                   Compare.assertion ("Unable to create interface customizer.", e);
285                }
286             }
287          }
288          return display;
289       }
290    
291       /**
292        * {@inheritDoc}
293        */
294    
295       public ITypeChecker getOutputTypeChecker () {
296          return (ITypeChecker) getOption (OPTION_OUTPUTTYPECHECKER);
297       }
298    
299       /**
300        * {@inheritDoc}
301        */
302    
303       public boolean isCached () {
304          return isSet ();
305       }
306    
307       /**
308        * {@inheritDoc}
309        */
310    
311       public IDataElement peekElement () {
312          Sync sync = getEvaluationLock ();
313          if (!Compare.attemptSync (sync))
314             return null;
315          try {
316             return isCached () ? getElement () : null;
317          } finally {
318             sync.release ();
319          }
320       }
321    
322       public void propertyChange (PropertyChangeEvent e) {
323          updateInterface (e.getPropertyName ());
324          super.propertyChange (e);
325       }
326    
327       /**
328        * {@inheritDoc}
329        */
330    
331       public void removeEvaluationListener (IEvaluationListener listener) {
332          listenerList.remove (IEvaluationListener.class, listener);
333       }
334    
335       /**
336        * Sets whether this generator allows the input description to be queried.
337        *
338        * @param allowDescriptionQuery Show context menu query choices
339        */
340    
341       public void setAllowDescriptionQuery (boolean allowDescriptionQuery) {
342          this.allowDescriptionQuery = allowDescriptionQuery;
343       }
344    
345       /**
346        * {@inheritDoc}
347        */
348    
349       protected void copy (boolean shallow, IDataGenerator target) {
350          super.copy (shallow, target);
351          ProgrammableDataGenerator generator = (ProgrammableDataGenerator) target;
352          if (isCached ())
353             generator.setElement (getElement ());
354       }
355    
356       /**
357        * A customization component for this generator.  This method will be called if the generator does not have a CustomizableInterface.
358        *
359        * @param name Customizer name
360        */
361    
362       protected JComponent createCustomizer (String name) {
363          return null;
364       }
365    
366       /**
367        * Evaluates the associated element.
368        */
369    
370       protected abstract IDataElement evaluate ();
371    
372       /**
373        * Notifies listeners of an evaluation error event.
374        *
375        * @param message Error
376        */
377    
378       protected void fireEvaluationError (String message) {
379          fireEvaluationError (new RuntimeException (message));
380       }
381    
382       /**
383        * Notifies listeners of an evaluation error event.
384        *
385        * @param t Error
386        */
387    
388       protected void fireEvaluationError (Throwable t) {
389          Object listeners [] = listenerList.getListenerList ();
390          for (int i = listeners.length - 2; i >= 0; i -= 2)
391             if (listeners [i] == IEvaluationListener.class)
392                ((IEvaluationListener) listeners [i + 1]).evaluationError (this, t);
393          Compare.assertion ("Error encountered during evaluation.", t);
394       }
395    
396       /**
397        * Notifies listeners of an evaluation start event.
398        */
399    
400       protected void fireEvaluationStart () {
401          Object listeners [] = listenerList.getListenerList ();
402          for (int i = listeners.length - 2; i >= 0; i -= 2)
403             if (listeners [i] == IEvaluationListener.class)
404                ((IEvaluationListener) listeners [i + 1]).evaluationStart (this);
405       }
406    
407       /**
408        * Notifies listeners of an evaluation stop event.
409        *
410        * @param result Evaluation result
411        */
412    
413       protected void fireEvaluationStop (IDataElement result) {
414          Object listeners [] = listenerList.getListenerList ();
415          for (int i = listeners.length - 2; i >= 0; i -= 2)
416             if (listeners [i] == IEvaluationListener.class)
417                ((IEvaluationListener) listeners [i + 1]).evaluationStop (this, result);
418       }
419    
420       /**
421        * {@inheritDoc}
422        */
423    
424       protected void initializeSource () {
425          listenerList = new EventListenerList ();
426          LOCK_INTERFACE = new Object ();
427          super.initializeSource ();
428       }
429    
430       /**
431        * Retrieves the current interface without creating a new one.
432        */
433    
434       protected ICustomizableInterface peekInterface () {
435          synchronized (LOCK_INTERFACE) {
436             return (ICustomizableInterface) getOption (OPTION_DISPLAYINSTANCE);
437          }
438       }
439    
440       /**
441        * Sets a type checker that can validate the input to this generator.
442        *
443        * @param checker Type checker
444        */
445    
446       protected void setInputTypeChecker (ITypeChecker checker) {
447          setOption (OPTION_INPUTTYPECHECKER, checker);
448       }
449    
450       /**
451        * Sets an interface class for this generator.  The class must implement CustomizableInterface. This method should not be called after the
452        * creation of customizers.
453        *
454        * @param name Interface class
455        */
456    
457       protected void setInterfaceClass (String name) {
458          try {
459             Class clazz = Class.forName (name);
460             assert ICustomizableInterface.class.isAssignableFrom (clazz);
461             setOption (OPTION_DISPLAYCONSTRUCTOR, clazz.getDeclaredConstructor (new Class [] {IProgrammableDataGenerator.class}));
462          } catch (Exception e) {
463             Compare.assertion ("Unable to set interface class for customizer.", e);
464          }
465       }
466    
467       /**
468        * Sets a type checker that can validate the output from this generator.
469        *
470        * @param checker Type checker
471        */
472    
473       protected void setOutputTypeChecker (ITypeChecker checker) {
474          setOption (OPTION_OUTPUTTYPECHECKER, checker);
475       }
476    
477       /**
478        * Notifies the interface that a property has change.
479        *
480        * @param property Property name
481        */
482    
483       protected void updateInterface (String property) {
484          synchronized (LOCK_INTERFACE) {
485             ICustomizableInterface display = peekInterface ();
486             if (display != null)
487                display.updateInterface (property);
488          }
489       }
490    }