/*
 * 
 * Copyright 2004 Sun Microsystems, Inc. All Rights Reserved.
 * 
 * The contents of this file are subject to the Sun Community Source
 * License v3.0/Jini Technology Specific Attachment v 1.0 (the
 * "License"). You may not use this file except in compliance with the
 * License. You may obtain a copy of the License at
 * http://wwws.sun.com/software/jini/licensing/SCSL3_JiniTSA1.html.
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 * 
 * The Reference Code is Jini Technology Core Platform code, v2.0. The
 * Developer of the Reference Code is Sun Microsystems, Inc.
 * 
 * Contributor(s): Sun Microsystems, Inc.
 * 
 * The contents of this file comply with the Jini Technology Core
 * Platform Compatibility Kit, v2.0A.
 * 
 * Tester(s): Sun Microsystems, Inc.
 * 
 * Test Platform(s):
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Solaris(TM)SPARC/x86
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Linux (Intel x86)
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Windows (Intel Platform)
 * 
 */
package com.sun.jini.mercury;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.SyncFailedException;

/**
 * This class implements the interface for interacting with output log
 * streams. It extends <tt>java.util.OutputStream</tt> and overrides its
 * methods in order to provide buffered output as well as byte offset 
 * tracking, which is useful during recovery situations.
 * The additional methods <tt>drain</tt> and <tt>sync</tt> allow for the
 * flushing of any buffered data and synchronizing data buffers with the
 * underlying device, respectively.
 *
 * @author Sun Microsystems, Inc.
 *
 * @since 1.1
 */

class LogOutputStream extends OutputStream implements LogStream {
    
    /** Holds value of the internal buffer size to allocate */
    private static final int BUFSIZE = 1024;

    /** byte array buffer for written data */
    private byte[] buf = new byte[BUFSIZE];

    /** index/offset into the internal write buffer */
    private int pos = 0;

    /** cumulative index/offset into this stream */
    private long offset = 0;

    /** Underlying output stream from which bytes are written */
    private FileOutputStream out;

    /** Associated key for this stream object */
    private /*final*/ StreamKey key;

    /**
     * Simple constructor that accepts a <tt>File</tt>, <tt>StreamKey</tt>
     * and <tt>boolean</tt> arguments. The <tt>File</tt> and <tt>StreamKey</tt>
     * arguments are assigned to their respective internal fields.  
     * The <tt>boolean</tt> argument is used to determine whether or not to 
     * append bytes to the underlying (file) output stream.
     *
     * @param     file   the <tt>File</tt> to used
     * @param     key    the associated key for this object
     * @param     append determines whether or not to append to the 
     *                   underlying stream
     *
     * @exception  IllegalArgumentException  if either the <tt>file</tt> or 
     *                   <tt>key</tt> argument is null
     *
     * @exception  FileNotFoundException  if the file exists but is a directory
     *                   rather than a regular file, does not exist but cannot
     *                   be created, or cannot be opened for any other reason
     */
    public LogOutputStream(File file, StreamKey key, boolean append) 
	throws FileNotFoundException
    {
        if (file == null || key == null)
            throw new IllegalArgumentException("Arguments cannot be null");

        // Throws an exception if !file.isFile()
	out = new FileOutputStream(file.getPath(), append);

	// If appending, then point to the end of the file.
	if (append) 
	    offset = file.length();
	this.key = key;
    }

    // javadoc inherited from supertype 
    public void write(int b) throws IOException {
	if (pos >= BUFSIZE)
	    drain();
	buf[pos++] = (byte) b;
	offset++;
    }

    // javadoc inherited from supertype 
    public void write(byte[] b) throws IOException {
	write(b, 0, b.length);
    }

    // javadoc inherited from supertype 
    public void write(byte[] b, int off, int len) throws IOException {
	if (pos + len >= BUFSIZE)
	    drain();
	if (len >= BUFSIZE) {
	    out.write(b, off, len);
	} else {
	    System.arraycopy(b, off, buf, pos, len);
	    pos += len;
	}
	offset += len;
    }

    // javadoc inherited from supertype 
    public void flush() throws IOException {
	drain();
	out.flush();
    }

    // javadoc inherited from supertype 
    public void close() throws IOException {
	flush();
	out.close();
    }
    
    /**
     * Returns the current "write" offset into this stream object. 
     */
    long getOffset() {
	return offset;
    }

    /**
     * Synchronizes system buffers with underlying device. 
     *
     * @exception IOException if an I/O error occurs
     *
     * @exception SyncFailedException if the buffers cannot be guaranteed to
     *                                have synchronized with physical media
     */
    void sync() throws IOException, SyncFailedException {
        out.getFD().sync();
    }
    
    /**
     * Writes any unwritten bytes to the underlying output stream.
     *
     * @exception IOException if an I/O error occurs
     *
     */
    void drain() throws IOException {
	if (pos > 0)
	    out.write(buf, 0, pos);
	pos = 0;
    }

    // javadoc inherited from supertype 
    public Object getKey() {
        return key;
    }
}
