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 }