/*
 * 
 * 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.outrigger.logstore;

import com.sun.jini.outrigger.OutriggerServerImpl;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * The base class for the logging file classes.  This class provides
 * the common functionality, but you should not instantiate it.
 * <p>
 * (Note -- I use <code>protected</code> here as an advisory notice.
 * Clearly, since this is package code, all classes in the package have
 * access, but fields marked <code>protected</code> are expected to be
 * used only by subclasses.  Use good taste.)
 *
 * @author Sun Microsystems, Inc.
 *
 * @see LogOutputFile
 * @see LogInputFile
 */
class LogFile {
    /**
     * The directory in which the log files live.
     */
    protected File baseDir;

    /**
     * The base part of the file name (e.g., <code>"log."</code> for
     * <code>"log.0"</code>, <code>"log.1"</code>, ...)
     */
    protected String baseFile;

    /**
     * The type of log stream
     */
    static final String LOG_TYPE = "LogStore";

    /**
     * The version of the log stream (the highest one known).
     */
    protected static final byte LOG_VERSION = 1;

    /** A log entry that records a boot. */
    protected static final byte BOOT_OP		= 1;
    /** A log entry that records the join state. */
    protected static final byte JOINSTATE_OP	= 11;
    /** A log entry that records a <code>write</code>. */
    protected static final byte WRITE_OP	= 2;
    /** A log entry that records a <code>take</code>. */
    protected static final byte TAKE_OP		= 3;
    /** A log entry that records a <code>notify</code>. */
    protected static final byte REGISTER_OP	= 4;
    /** A log entry that records a <code>notify</code>. */
    protected static final byte RENEW_OP	= 5;
    /** A log entry that records a notification and new sequence number. */
    protected static final byte NOTIFIED_OP	= 6;
    /** A log entry that records a <code>cancel</code>. */
    protected static final byte CANCEL_OP	= 7;
    /** A log entry that records a transaction <code>prepare</code>. */
    protected static final byte PREPARE_OP	= 8;
    /** A log entry that records a transaction <code>commit</code>. */
    protected static final byte COMMIT_OP	= 9;
    /** A log entry that records a transaction <code>abort</code>. */
    protected static final byte ABORT_OP	= 10;
    /** A log entry that records the service's <code>Uuid</code>. */
    protected static final byte UUID_OP 	= 12;

    /** Logger for logging persistent store related information */
    private static final Logger logger = 
	Logger.getLogger(OutriggerServerImpl.storeLoggerName);

    /**
     * Create a log file with the given base directory, base file name
     * within the directory.  This is intended only for
     * when you are sure of the exact values -- no modifications are
     * made to the values.
     */
    protected LogFile(File baseDir, String baseFile) {
	this.baseDir = baseDir;
	this.baseFile = baseFile;
    }

    /**
     * Create a log file from the given template.  If
     * <code>basePath</code> has a directory component, it is used as
     * the base directory.  Otherwise the base directory is
     * <code>"."</code>.  If <code>basePath</code> names a directory,
     * the base name will be <code>""</code>.  Otherwise the file
     * component is used as the base, with a "." added at the end if it
     * is not already present.
     */
    protected LogFile(String basePath) throws IOException {
	baseDir = new File(basePath);
	if (baseDir.isDirectory()) {
	    baseFile = "";
	} else {
	    baseFile = baseDir.getName();
	    String pname = baseDir.getParent();
	    if (pname == null)
		pname = ".";
	    baseDir = new File(pname);
	    if (baseFile.charAt(baseFile.length() - 1) != '.')
		baseFile += ".";
	}
    }

    /**
     * Fill in a list of existing matching log files, oldest to newest,
     * returning the highest number used as a suffix, or -1 if
     * no files were found.  If two files have the same time, they are
     * sorted by the numeric value of the suffix.
     */
    int existingLogs(Collection files) {
	if (logger.isLoggable(Level.FINE)) {
	    logger.log(Level.FINE, "scanning {0} for {1} baseFile",
		       new Object[]{baseDir, baseFile});
	}

	String[] inDir = baseDir.list();	// directory contents
	TreeMap found = new TreeMap();		// the files we've found
	int highest = -1;			// largest # suffix seen

	// no directory or files (can happen on destroy)
	if (inDir == null)
	    return highest;

    fileLoop:
	for (int f = 0; f < inDir.length; f++) {

	    String name = inDir[f];

	    logger.log(Level.FINE, "checking {0}", name);

	    if (!name.startsWith(baseFile))		// is it one of ours?
		continue;

	    // ensure that there is a numerical suffix
	    int num;
	    try {
		num = Integer.parseInt(name.substring(baseFile.length()));
		if (num > highest)		       // keep track of highest
		    highest = num;
	    } catch (NumberFormatException e) {
		continue fileLoop;		       // can't be one of ours
	    }

	    found.put(new Integer(num), new File(baseDir, name));
	}

	files.addAll(found.values());
	if (logger.isLoggable(Level.FINE)) {
	    logger.log(Level.FINE, "returning {0} files",
		       new Integer(files.size()));
	    Iterator it = files.iterator();
	    while (it.hasNext())
		logger.log(Level.FINE, it.next().toString());
	}

	return highest;
    }

    /**
     * Destroy all log files associated with this stream.
     */
    void destroy() {
	if (logger.isLoggable(Level.FINE))
	    logger.log(Level.FINE, "destroy");

	ArrayList files = new ArrayList();
	existingLogs(files);
	for (int i = 0; i < files.size(); i++) {
	    File log = (File) files.get(i);
	    try {
		if (!log.delete()) {
		    logger.log(Level.INFO, "Could not delete {0}", log);
		}
	    } catch (SecurityException e) {
		if (!log.delete()) {
		    logger.log(Level.INFO,
			       "SecurityException : Could not delete " + log,
			       e);
		}
	    }
	}
    }
}
