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    }