001 package jigcell.compare.impl;
002
003 import EDU.oswego.cs.dl.util.concurrent.ReadWriteLock;
004 import EDU.oswego.cs.dl.util.concurrent.ReentrantWriterPreferenceReadWriteLock;
005 import EDU.oswego.cs.dl.util.concurrent.Sync;
006 import java.beans.PropertyChangeEvent;
007 import java.beans.PropertyChangeListener;
008 import java.beans.PropertyChangeSupport;
009 import java.beans.XMLDecoder;
010 import java.beans.XMLEncoder;
011 import java.io.ByteArrayInputStream;
012 import java.io.ByteArrayOutputStream;
013 import java.io.InputStream;
014 import java.util.HashMap;
015 import java.util.Iterator;
016 import java.util.Map;
017 import java.util.StringTokenizer;
018 import javax.swing.Icon;
019 import jigcell.compare.IDataSource;
020 import jigcell.compare.IPersistableDataSource;
021 import jigcell.compare.IReadableDataSource;
022 import jigcell.compare.IWriteableDataSource;
023
024 /**
025 * A helper implementation for data source classes. Handles maintaining the state of the source and supports both readable and writeable data
026 * sources. All source state should use options instead of instance variables. Subclasses should only hold a lock from OPTIONS_LOCK for a
027 * brief time. The read, write, and configure methods should cache all of the needed options prior to performing a blocking operation. This
028 * means data source options can safely be set on the event thread. Users of a data source should treat all objects acquired by
029 * get<i>NAME</i>Option methods or passed to set<i>NAME</i>Option methods as immutable.
030 *
031 * <p>
032 * This code is licensed under the DARPA BioCOMP Open Source License. See LICENSE for more details.
033 * </p>
034 *
035 * @author Nicholas Allen
036 */
037
038 public abstract class DataSource extends Transferer implements IDataSource, PropertyChangeListener {
039
040 /**
041 * Separator between two options when providing state information
042 */
043
044 public final static String SEPARATOR_PART = "|";
045
046 /**
047 * Separator between option name and value when providing state information
048 */
049
050 public final static String SEPARATOR_PAIR = "@";
051
052 /**
053 * All separators used when providing state information
054 */
055
056 public final static String SEPARATORS = SEPARATOR_PART + SEPARATOR_PAIR;
057
058 /**
059 * Options persistence table organized by name
060 */
061
062 private final static Map optionsTable = new HashMap ();
063
064 /**
065 * Lock for manipulating the options persistence table
066 */
067
068 private final static ReadWriteLock OPTIONTYPE_LOCK = new ReentrantWriterPreferenceReadWriteLock ();
069
070 /**
071 * Lock for manipulating source options
072 */
073
074 protected final ReadWriteLock OPTIONS_LOCK = new ReentrantWriterPreferenceReadWriteLock ();
075
076 /**
077 * Options for this data source
078 */
079
080 private transient Map options;
081
082 /**
083 * Support for handling property change events
084 */
085
086 private transient PropertyChangeSupport propertySupport;
087
088 static {
089 addFlavor (IDataSource.class, new ComparatorDataFlavor ("Data Source", IDataSource.class, ComparatorDataFlavor.FLAVOR_LOCALOBJECT));
090 addFlavor (IDataSource.class,
091 new ComparatorDataFlavor ("Data Source", InputStream.class, ComparatorDataFlavor.FLAVOR_XMLSERIALIZEDOBJECT));
092 setOptionType (OPTION_NAME, Option.COPYONLY);
093 setOptionType (IReadableDataSource.OPTION_READDESCRIPTION, Option.COPYONLY);
094 setOptionType (IReadableDataSource.OPTION_READICON, Option.COPYONLY);
095 setOptionType (IWriteableDataSource.OPTION_WRITEDESCRIPTION, Option.COPYONLY);
096 setOptionType (IWriteableDataSource.OPTION_WRITEICON, Option.COPYONLY);
097 }
098
099 /**
100 * Persists a data source.
101 *
102 * @param source Source to persist
103 */
104
105 public static String persistSource (IPersistableDataSource source) {
106 if (source == null)
107 return null;
108 ByteArrayOutputStream byteOut = new ByteArrayOutputStream ();
109 XMLEncoder encoder = new XMLEncoder (byteOut);
110 Compare.updateEncoder (encoder);
111 ExceptionRecorder recorder = new ExceptionRecorder ("Unable to persist data source.", true, false, true);
112 encoder.setExceptionListener (recorder);
113 encoder.writeObject (source);
114 encoder.close ();
115 return recorder.hasException () ? null : new String (byteOut.toByteArray ());
116 }
117
118 /**
119 * Unpersists a data source.
120 *
121 * @param source Persistent representation of a source
122 */
123
124 public static IPersistableDataSource unpersistSource (String source) {
125 if (source == null)
126 return null;
127 ExceptionRecorder recorder = new ExceptionRecorder ("Unable to restore data source.", true, false, true);
128 ByteArrayInputStream byteIn = new ByteArrayInputStream (source.getBytes ());
129 XMLDecoder decoder = new XMLDecoder (byteIn, null, recorder);
130 IPersistableDataSource persist = (IPersistableDataSource) decoder.readObject ();
131 decoder.close ();
132 if (recorder.hasException ())
133 return null;
134 return persist;
135 }
136
137 /**
138 * The persistence type for an option.
139 *
140 * @param name Option name
141 */
142
143 protected static Option getOptionType (String name) {
144 Sync sync = OPTIONTYPE_LOCK.readLock ();
145 Compare.acquireSync (sync);
146 try {
147 Option type = (Option) optionsTable.get (name);
148 return type == null ? Option.UNSAFE : type;
149 } finally {
150 sync.release ();
151 }
152 }
153
154 /**
155 * Sets the persistence type for an option. Options are assumed to be unsafe by default.
156 *
157 * @param name Option name
158 * @param type Option persistence type
159 */
160
161 protected static void setOptionType (String name, Option type) {
162 assert name != null && type != null;
163 Sync sync = OPTIONTYPE_LOCK.writeLock ();
164 Compare.acquireSync (sync);
165 try {
166 optionsTable.put (name, type);
167 } finally {
168 sync.release ();
169 }
170 }
171
172 /**
173 * Creates a new data source.
174 */
175
176 public DataSource () {
177 options = new HashMap ();
178 options.put (OPTION_NAME, "");
179 propertySupport = new PropertyChangeSupport (this);
180 addPropertyChangeListener (this);
181 initializeSource ();
182 }
183
184 /**
185 * {@inheritDoc}
186 */
187
188 public void addOption (String key, Option type) {
189 setOptionType (key, type);
190 }
191
192 /**
193 * Adds a PropertyChangeListener to the listener list.
194 *
195 * @param listener Listener
196 */
197
198 public void addPropertyChangeListener (PropertyChangeListener listener) {
199 propertySupport.addPropertyChangeListener (listener);
200 }
201
202 /**
203 * Adds a PropertyChangeListener for a specific property.
204 *
205 * @param property Property
206 * @param listener Listener
207 */
208
209 public void addPropertyChangeListener (String property, PropertyChangeListener listener) {
210 propertySupport.addPropertyChangeListener (property, listener);
211 }
212
213 /**
214 * {@inheritDoc}
215 */
216
217 public Object clone () {
218 Sync sync = OPTIONS_LOCK.readLock ();
219 Compare.acquireSync (sync);
220 try {
221 DataSource source = (DataSource) super.clone ();
222 Map sourceOptions = new HashMap (options);
223 for (Iterator iterator = sourceOptions.keySet ().iterator (); iterator.hasNext (); )
224 if (getOptionType ((String) iterator.next ()) == Option.UNSAFE)
225 iterator.remove ();
226 source.setOptions (sourceOptions);
227 source.initializeSource ();
228 return source;
229 } catch (CloneNotSupportedException e) {
230 Compare.assertion ("Unable to clone data source.", e);
231 } finally {
232 sync.release ();
233 }
234 return null;
235 }
236
237 /**
238 * {@inheritDoc}
239 */
240
241 public boolean configure () {
242 return true;
243 }
244
245 /**
246 * {@inheritDoc}
247 */
248
249 public String getName () {
250 return (String) getOption (OPTION_NAME);
251 }
252
253 /**
254 * {@inheritDoc}
255 */
256
257 public Object getOption (String key) {
258 Sync sync = OPTIONS_LOCK.readLock ();
259 Compare.acquireSync (sync);
260 try {
261 return options.get (key);
262 } finally {
263 sync.release ();
264 }
265 }
266
267 /**
268 * An array of all the PropertyChangeListeners added to this Compare with addPropertyChangeListener.
269 */
270
271 public PropertyChangeListener [] getPropertyChangeListeners () {
272 return propertySupport.getPropertyChangeListeners ();
273 }
274
275 /**
276 * An array of all the listeners which have been associated with the named property.
277 *
278 * @param property Property
279 */
280
281 public PropertyChangeListener [] getPropertyChangeListeners (String property) {
282 return propertySupport.getPropertyChangeListeners (property);
283 }
284
285 /**
286 * The read description option.
287 */
288
289 public String getReadDescriptionOption () {
290 return (String) getOption (IReadableDataSource.OPTION_READDESCRIPTION);
291 }
292
293 /**
294 * The read description icon.
295 */
296
297 public Icon getReadIconOption () {
298 return (Icon) getOption (IReadableDataSource.OPTION_READICON);
299 }
300
301 /**
302 * {@inheritDoc}
303 */
304
305 public String getState () {
306 Sync sync = OPTIONS_LOCK.readLock ();
307 Compare.acquireSync (sync);
308 try {
309 StringBuffer state = null;
310 for (Iterator iterator = getOptions ().keySet ().iterator (); iterator.hasNext (); ) {
311 String key = (String) iterator.next ();
312 if (getOptionType (key) != Option.SAFE)
313 continue;
314 String value = (String) getOption (key);
315 if (value == null || value.length () == 0)
316 continue;
317 if (state == null)
318 state = new StringBuffer ();
319 else
320 state.append (SEPARATOR_PART);
321 state.append (key).append (SEPARATOR_PAIR).append (value);
322 }
323 return state == null ? "" : state.toString ();
324 } finally {
325 sync.release ();
326 }
327 }
328
329 /**
330 * The write description option.
331 */
332
333 public String getWriteDescriptionOption () {
334 return (String) getOption (IWriteableDataSource.OPTION_WRITEDESCRIPTION);
335 }
336
337 /**
338 * The write description icon.
339 */
340
341 public Icon getWriteIconOption () {
342 return (Icon) getOption (IWriteableDataSource.OPTION_WRITEICON);
343 }
344
345 public void propertyChange (PropertyChangeEvent e) {}
346
347 /**
348 * Removes a PropertyChangeListener from the listener list.
349 *
350 * @param listener Listener
351 */
352
353 public void removePropertyChangeListener (PropertyChangeListener listener) {
354 propertySupport.removePropertyChangeListener (listener);
355 }
356
357 /**
358 * Removes a PropertyChangeListener for a specific property.
359 *
360 * @param property Property
361 * @param listener Listener
362 */
363
364 public void removePropertyChangeListener (String property, PropertyChangeListener listener) {
365 propertySupport.removePropertyChangeListener (property, listener);
366 }
367
368 /**
369 * {@inheritDoc}
370 */
371
372 public boolean setOption (String key, Object value) {
373 Sync sync = OPTIONS_LOCK.writeLock ();
374 Compare.acquireSync (sync);
375 try {
376 Object oldValue = options.get (key);
377 if (value == oldValue)
378 return false;
379 options.put (key, value);
380 propertySupport.firePropertyChange (key, oldValue, value);
381 } finally {
382 sync.release ();
383 }
384 return true;
385 }
386
387 /**
388 * Sets the read description option.
389 *
390 * @param description Description
391 */
392
393 public void setReadDescriptionOption (String description) {
394 setOption (IReadableDataSource.OPTION_READDESCRIPTION, description);
395 }
396
397 /**
398 * Sets the read description icon.
399 *
400 * @param icon Icon
401 */
402
403 public void setReadIconOption (Icon icon) {
404 setOption (IReadableDataSource.OPTION_READICON, icon);
405 }
406
407 /**
408 * {@inheritDoc}
409 */
410
411 public void setState (String state) {
412 for (StringTokenizer tokenizer = new StringTokenizer (state, SEPARATORS); tokenizer.hasMoreElements (); ) {
413 String key = tokenizer.nextToken ();
414 if (!tokenizer.hasMoreElements ())
415 throw new IllegalArgumentException ("Unable to fully parse expression");
416 setOption (key, tokenizer.nextToken ());
417 }
418 }
419
420 /**
421 * Sets the write description option.
422 *
423 * @param description Description
424 */
425
426 public void setWriteDescriptionOption (String description) {
427 setOption (IWriteableDataSource.OPTION_WRITEDESCRIPTION, description);
428 }
429
430 /**
431 * Sets the write description icon.
432 *
433 * @param icon Icon
434 */
435
436 public void setWriteIconOption (Icon icon) {
437 setOption (IWriteableDataSource.OPTION_WRITEICON, icon);
438 }
439
440 public String toString () {
441 return getName () + "[" + getState () + "]";
442 }
443
444 /**
445 * The configuration options map.
446 */
447
448 protected Map getOptions () {
449 Sync sync = OPTIONS_LOCK.readLock ();
450 Compare.acquireSync (sync);
451 try {
452 return options;
453 } finally {
454 sync.release ();
455 }
456 }
457
458 /**
459 * Performs any initialization work for the view that must be duplicated for each new instance. This method is called exactly once and only
460 * during object construction.
461 */
462
463 protected void initializeSource () {}
464
465 /**
466 * Sets the data source name.
467 *
468 * @param name Name
469 */
470
471 protected void setName (String name) {
472 assert name != null;
473 setOption (OPTION_NAME, name);
474 }
475
476 /**
477 * Sets the configuration options map.
478 */
479
480 protected void setOptions (Map options) {
481 Sync sync = OPTIONS_LOCK.writeLock ();
482 Compare.acquireSync (sync);
483 try {
484 this.options = options;
485 } finally {
486 sync.release ();
487 }
488 }
489 }