package uk.ac.starlink.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;

/**
 * A DataSource which stores its data in a temporary file.  This can be
 * used to represent data which is only available to read once from an
 * intput stream.  The stream is read when this source is constructed,
 * and cached in a temporary file.  The temporary file is removed
 * when this object becomes unreachable or when the VM is terminated normally.
 *
 * @author   Mark Taylor (Starlink)
 * @author   Peter W. Draper (Starlink)
 */
public class TemporaryFileDataSource extends FileDataSource {

    /**
     * Constructs a new DataSource by reading the contents of an
     * input stream.  The name of the source is also supplied; it does
     * not take the name (or URL) of the file, since that does not
     * represent a persistent object.
     *
     * @param   baseStream  the stream which supplies this source's data
     * @param   name  the name of the source
     */
    public TemporaryFileDataSource( InputStream baseStream, String name )
            throws IOException {
        this( baseStream, name, "StreamDataSource", null, null );
    }

    /**
     * Constructs a new DataSource by reading the contents of an
     * input stream.  The name of the source is also supplied; it does
     * not take the name (or URL) of the file, since that does not
     * represent a persistent object. Using this constructor you may also
     * supply the prefix, suffix and directory used for the temporary file
     * see {@link File#createTempFile(String,String,File)}.
     *
     * @param   baseStream  the stream which supplies this source's data
     * @param   name  the name of the source
     * @param   prefix the prefix string to be used in generating the file's
     *          name; must be at least three characters long
     * @param   suffix the suffix string to be used in generating the file's
     *          name; may be null, in which case the suffix ".tmp" will be
     *          used
     * @param directory the directory in which the file is to be created, or
     *        null if the default temporary-file directory is to be used
     */
    @SuppressWarnings("this-escape")
    public TemporaryFileDataSource( InputStream baseStream, String name,
                                    String prefix, String suffix,
                                    File directory )
            throws IOException {
        super( makeTempFile( baseStream, prefix, suffix, directory ) );
        setName( name );
        Cleaner.getInstance().register( this, new Deleter( getFile() ) );
    }

    /**
     * Returns <code>null</code>, since the data is not represented by a
     * persistent object.
     */
    public URL getURL() {
        return null;
    }

    /**
     * Creates a temporary file and fills it with the contents of a stream.
     *
     * @param   strm  the input stream, which will be read and closed
     * @param   prefix the prefix string to be used in generating the file's
     *          name; must be at least three characters long
     * @param   suffix the suffix string to be used in generating the file's
     *          name; may be null, in which case the suffix ".tmp" will be
     *          used
     * @param directory the directory in which the file is to be created, or
     *        null if the default temporary-file directory is to be used
     */
    private static File makeTempFile( InputStream istrm, String prefix, 
                                      String suffix, File directory ) 
        throws IOException {
        File file = File.createTempFile( prefix, suffix, directory );
        try {
            file.deleteOnExit();
            OutputStream ostrm = new FileOutputStream( file );
            byte[] buf = new byte[ 4096 ];
            for ( int n; ( n = istrm.read( buf ) ) >= 0; ) {
                ostrm.write( buf, 0, n );
            }
            istrm.close();
            ostrm.close();
            return file;
        }
        catch ( IOException e ) {
            file.delete();
            throw e;
        }
    }

    /**
     * Used to delete the temporary file.
     */
    private static class Deleter implements Runnable {
        private final File file_;
        Deleter( File file ) {
            file_ = file;
        }
        public void run() {
            file_.delete();
        }
    }
}
