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 }