001    package jigcell.compare.impl;
002    
003    import EDU.oswego.cs.dl.util.concurrent.Sync;
004    import java.io.File;
005    import java.io.IOException;
006    import java.io.InputStream;
007    import java.io.OutputStream;
008    import javax.swing.JFileChooser;
009    import javax.swing.UIManager;
010    import jigcell.compare.IMultipleDataSource;
011    import jigcell.compare.IPersistableDataSource;
012    import jigcell.compare.IReadableDataSource;
013    import jigcell.compare.IWriteableDataSource;
014    
015    /**
016     * A generic transfer agent for a source of data that supports reading and writing to a file.
017     *
018     * <p>
019     * This code is licensed under the DARPA BioCOMP Open Source License.  See LICENSE for more details.
020     * </p>
021     *
022     * @author Nicholas Allen
023     */
024    
025    public abstract class FileDataSource extends DataSource implements IMultipleDataSource, IPersistableDataSource, IReadableDataSource,
026       IWriteableDataSource {
027    
028       /**
029        * Encoder for controlling the format this source uses for reading and writing
030        */
031    
032       public final static String OPTION_ENCODER = "FileDataSource.encoder";
033    
034       /**
035        * Exception listener to use for detecting errors
036        */
037    
038       public final static String OPTION_EXCEPTIONLISTENER = "FileDataSource.exceptionListener";
039    
040       /**
041        * File chooser to use when selecting the file
042        */
043    
044       public final static String OPTION_FILECHOOSER = "FileDataSource.chooser";
045    
046       /**
047        * File chooser selection description
048        */
049    
050       public final static String OPTION_FILECHOOSER_SELECT = "FileDataSource.chooserSelectText";
051    
052       /**
053        * Option indicating the path of the file
054        */
055    
056       public final static String OPTION_FILEPATH = "FileDataSource.path";
057    
058       /**
059        * Default option for file chooser
060        */
061    
062       protected final static String DEFAULT_FILECHOOSER = "Select";
063    
064       /**
065        * Read operation description
066        */
067    
068       protected final static String DESCRIPTION_READ = "Open...";
069    
070       /**
071        * Write operation description
072        */
073    
074       protected final static String DESCRIPTION_WRITE = "Save As...";
075    
076       /**
077        * Error message when the directory to place the file in cannot be created
078        */
079    
080       protected final static String MESSAGE_BADDIRECTORYERROR = "Unable to create path to file";
081    
082       /**
083        * Option indicating the input handle to the file
084        */
085    
086       protected final static String OPTION_FILEINPUT = "FileDataSource.input";
087    
088       /**
089        * Option indicating the output handle to the file
090        */
091    
092       protected final static String OPTION_FILEOUTPUT = "FileDataSource.output";
093    
094       /**
095        * An encoder for a FileDataSource.  All instance variables of an encoder should be transient and otherwise invisible to serialization
096        * mechanisms.
097        */
098    
099       public interface IFileEncoder {
100    
101          /**
102           * @see jigcell.compare.impl.FileDataSource#finish()
103           */
104    
105          void finish ();
106    
107          /**
108           * @see jigcell.compare.IWriteableDataSource#getPredictedCompatibility(java.lang.Class)
109           */
110    
111          boolean getPredictedCompatibility (Class clazz);
112    
113          /**
114           * @see jigcell.compare.IReadableDataSource#getPredictedContents
115           */
116    
117          Class getPredictedContents ();
118    
119          /**
120           * Loads any data the encoder needs from the source's options prior to reading.  This method may only be called while holding the write
121           * lock of OPTIONS_LOCK in the source.
122           */
123    
124          void prepareForRead () throws Exception;
125    
126          /**
127           * Loads any data the encoder needs from the source's options prior to writing.  This method may only be called while holding the write
128           * lock of OPTIONS_LOCK in the source.
129           */
130    
131          void prepareForWrite () throws Exception;
132    
133          /**
134           * @see jigcell.compare.IReadableDataSource#read
135           */
136    
137          Object read () throws Exception;
138    
139          /**
140           * @see jigcell.compare.IWriteableDataSource#write(java.lang.Object)
141           */
142    
143          void write (Object data) throws Exception;
144       }
145    
146       static {
147          Compare.addTransient (FileDataSource.class, "fileChooserOption");
148          Compare.addTransient (FileDataSource.class, "fileChooserSelectOption");
149          Compare.addTransient (FileDataSource.class, "fileInputOption");
150          Compare.addTransient (FileDataSource.class, "fileOutputOption");
151          setOptionType (OPTION_EXCEPTIONLISTENER, Option.COPYONLY);
152          setOptionType (OPTION_FILECHOOSER_SELECT, Option.COPYONLY);
153          setOptionType (OPTION_FILEPATH, Option.SAFE);
154       }
155    
156       /**
157        * Creates a new data source that represents a file.
158        */
159    
160       public FileDataSource () {
161          this (null);
162       }
163    
164       /**
165        * Creates a new data source that represents a file.
166        *
167        * @param path File path
168        */
169    
170       public FileDataSource (String path) {
171          super ();
172          setReadDescriptionOption (DESCRIPTION_READ);
173          setReadIconOption (UIManager.getIcon ("jigcell.compare.icon.OPEN_SMALL"));
174          setWriteDescriptionOption (DESCRIPTION_WRITE);
175          setWriteIconOption (UIManager.getIcon ("jigcell.compare.icon.SAVEAS_SMALL"));
176          setExceptionListenerOption (new ExceptionRecorder ("Error encountered during file operation.", true, false, true));
177          setFileChooserSelectOption (DEFAULT_FILECHOOSER);
178          setFilePathOption (path);
179       }
180    
181       /**
182        * {@inheritDoc}
183        */
184    
185       public boolean configure () {
186          JFileChooser chooser;
187          String select;
188          Sync sync = OPTIONS_LOCK.readLock ();
189          Compare.acquireSync (sync);
190          try {
191             chooser = getFileChooserOption ();
192             select = getFileChooserSelectOption ();
193          } finally {
194             sync.release ();
195          }
196          String path = null;
197          synchronized (chooser) {
198             chooser.cancelSelection ();
199             if (chooser.showDialog (null, select) == JFileChooser.APPROVE_OPTION)
200                try {
201                   path = chooser.getSelectedFile ().getCanonicalPath ();
202                } catch (Exception e) {
203                   Compare.assertion ("Unable to get path for file selection.", e);
204                }
205          }
206          if (path == null)
207             return false;
208          sync = OPTIONS_LOCK.writeLock ();
209          Compare.acquireSync (sync);
210          try {
211             setFilePathOption (path);
212          } finally {
213             sync.release ();
214          }
215          return true;
216       }
217    
218       /**
219        * {@inheritDoc}
220        */
221    
222       public void finish () {
223          IFileEncoder encoder;
224          InputStream input;
225          OutputStream output;
226          Sync sync = OPTIONS_LOCK.writeLock ();
227          Compare.acquireSync (sync);
228          try {
229             encoder = getEncoderOption ();
230             input = getFileInputOption ();
231             output = getFileOutputOption ();
232             setFileInputOption (null);
233             setFileOutputOption (null);
234          } finally {
235             sync.release ();
236          }
237          if (encoder != null)
238             encoder.finish ();
239          try {
240             if (input != null)
241                input.close ();
242          } catch (Exception e) {
243             Compare.assertion ("Unable to close file input stream.", e);
244          }
245          try {
246             if (output != null)
247                output.close ();
248          } catch (Exception e) {
249             Compare.assertion ("Unable to close file output stream.", e);
250          }
251       }
252    
253       /**
254        * The file encoder to control reading and writing from this source.
255        */
256    
257       public IFileEncoder getEncoderOption () {
258          return (IFileEncoder) getOption (OPTION_ENCODER);
259       }
260    
261       /**
262        * Returns and clears any exception associated with the last operation.
263        */
264    
265       public Exception getException () {
266          return getExceptionListenerOption ().getLastException ();
267       }
268    
269       /**
270        * The exception listener option.
271        */
272    
273       public ExceptionRecorder getExceptionListenerOption () {
274          return (ExceptionRecorder) getOption (OPTION_EXCEPTIONLISTENER);
275       }
276    
277       /**
278        * The file chooser option.
279        */
280    
281       public JFileChooser getFileChooserOption () {
282          return (JFileChooser) getOption (OPTION_FILECHOOSER);
283       }
284    
285       /**
286        * The file chooser select option.
287        */
288    
289       public String getFileChooserSelectOption () {
290          return (String) getOption (OPTION_FILECHOOSER_SELECT);
291       }
292    
293       /**
294        * The file input option.
295        */
296    
297       public InputStream getFileInputOption () {
298          return (InputStream) getOption (OPTION_FILEINPUT);
299       }
300    
301       /**
302        * The file output option.
303        */
304    
305       public OutputStream getFileOutputOption () {
306          return (OutputStream) getOption (OPTION_FILEOUTPUT);
307       }
308    
309       /**
310        * The file path option.
311        */
312    
313       public String getFilePathOption () {
314          return (String) getOption (OPTION_FILEPATH);
315       }
316    
317       /**
318        * {@inheritDoc}
319        */
320    
321       public String getLocation () {
322          return getFilePathOption ();
323       }
324    
325       /**
326        * {@inheritDoc}
327        */
328    
329       public boolean getPredictedCompatibility (Class clazz) {
330          IFileEncoder encoder;
331          Sync sync = OPTIONS_LOCK.readLock ();
332          Compare.acquireSync (sync);
333          try {
334             encoder = getEncoderOption ();
335          } finally {
336             sync.release ();
337          }
338          return encoder == null ? clazz == byte [].class : encoder.getPredictedCompatibility (clazz);
339       }
340    
341       /**
342        * {@inheritDoc}
343        */
344    
345       public boolean getPredictedCompatibility (Object instance) {
346          return instance == null ? false : getPredictedCompatibility (instance.getClass ());
347       }
348    
349       /**
350        * {@inheritDoc}
351        */
352    
353       public Class getPredictedContents () {
354          IFileEncoder encoder;
355          Sync sync = OPTIONS_LOCK.readLock ();
356          Compare.acquireSync (sync);
357          try {
358             encoder = getEncoderOption ();
359          } finally {
360             sync.release ();
361          }
362          return encoder == null ? byte [].class : encoder.getPredictedContents ();
363       }
364    
365       /**
366        * {@inheritDoc} If the file input option has not been initialized, this method may have to do some file work while holding the options lock
367        * to avoid race conditions.
368        */
369    
370       public Object read () throws Exception {
371          IFileEncoder encoder;
372          InputStream input;
373          Sync sync = OPTIONS_LOCK.writeLock ();
374          Compare.acquireSync (sync);
375          try {
376             input = getFileInputOption ();
377             if (input == null) {
378                input = createFileInput (getFilePathOption ());
379                setFileInputOption (input);
380             }
381             encoder = getEncoderOption ();
382             if (encoder != null)
383                encoder.prepareForRead ();
384          } finally {
385             sync.release ();
386          }
387          if (encoder != null)
388             return encoder.read ();
389          byte buffer [];
390          int read;
391          synchronized (input) {
392             buffer = new byte [input.available ()];
393             read = input.read (buffer);
394          }
395          if (read < buffer.length) {
396             byte oldBuffer [] = buffer;
397             buffer = new byte [read];
398             System.arraycopy (oldBuffer, 0, buffer, 0, read);
399          }
400          return buffer;
401       }
402    
403       /**
404        * Sets the file encoder to control reading and writing from this source.
405        *
406        * @param encoder File encoder
407        */
408    
409       public void setEncoderOption (IFileEncoder encoder) {
410          setOption (OPTION_ENCODER, encoder);
411       }
412    
413       /**
414        * Sets the exception listener option.
415        *
416        * @param listener Exception listener
417        */
418    
419       public void setExceptionListenerOption (ExceptionRecorder listener) {
420          assert listener != null;
421          setOption (OPTION_EXCEPTIONLISTENER, listener);
422       }
423    
424       /**
425        * Sets the file chooser option.
426        *
427        * @param chooser File chooser
428        */
429    
430       public void setFileChooserOption (JFileChooser chooser) {
431          setOption (OPTION_FILECHOOSER, chooser);
432       }
433    
434       /**
435        * Sets the file chooser select option.
436        *
437        * @param select File chooser select
438        */
439    
440       public void setFileChooserSelectOption (String select) {
441          setOption (OPTION_FILECHOOSER_SELECT, select);
442       }
443    
444       /**
445        * Sets the file input option.
446        *
447        * @param file Input file
448        */
449    
450       public void setFileInputOption (InputStream file) {
451          setOption (OPTION_FILEINPUT, file);
452       }
453    
454       /**
455        * Sets the file output option.
456        *
457        * @param file Output file
458        */
459    
460       public void setFileOutputOption (OutputStream file) {
461          setOption (OPTION_FILEOUTPUT, file);
462       }
463    
464       /**
465        * Sets the file path option.
466        *
467        * @param path File path
468        */
469    
470       public void setFilePathOption (String path) {
471          if (path != null)
472             try {
473                path = new File (path).getCanonicalPath ();
474             } catch (IOException e) {
475                path = null;
476                Compare.assertion ("Unable to set file path.", e);
477             }
478          setOption (OPTION_FILEPATH, path);
479          setName (path == null ? "" : path);
480       }
481    
482       /**
483        * {@inheritDoc}
484        */
485    
486       public void setLocation (String location) {
487          setFilePathOption (location);
488       }
489    
490       /**
491        * {@inheritDoc} If the file output option has not been initialized, this method may have to do some file work while holding the options lock
492        * to avoid race conditions.
493        */
494    
495       public void write (Object data) throws Exception {
496          IFileEncoder encoder;
497          OutputStream output;
498          Sync sync = OPTIONS_LOCK.writeLock ();
499          Compare.acquireSync (sync);
500          try {
501             output = getFileOutputOption ();
502             if (output == null) {
503                output = createFileOutput (getFilePathOption ());
504                setFileOutputOption (output);
505             }
506             encoder = getEncoderOption ();
507             if (encoder != null)
508                encoder.prepareForWrite ();
509          } finally {
510             sync.release ();
511          }
512          if (encoder == null) {
513             if (data != null)
514                synchronized (output) {
515                   output.write ((byte []) data);
516                }
517          } else
518             encoder.write (data);
519       }
520    
521       /**
522        * Creates an input stream for a file.  An exclusive lock for the file will be obtained.  Since the lock is not returned, the stream must be
523        * closed to free the lock.
524        *
525        * @param path Path to file
526        */
527    
528       protected abstract InputStream createFileInput (String path) throws IOException;
529    
530       /**
531        * Creates an output stream for a file.  An exclusive lock for the file will be obtained.  Since the lock is not returned, the stream must be
532        * closed to free the lock.  All necessary directories for the file will be created if they do not already exist.
533        *
534        * @param path Path to file
535        */
536    
537       protected abstract OutputStream createFileOutput (String path) throws IOException;
538    
539       /**
540        * {@inheritDoc}
541        */
542    
543       protected ExceptionRecorder getExceptionRecorder () {
544          return getExceptionListenerOption ();
545       }
546    }