/*
 * @(#)Database.java	1.3 04/01/27
 *
 * Copyright (c) 1999-2004 Sun Microsystems, Inc. All rights reserved. 
 * PROPRIETARY/CONFIDENTIAL
 * Use is subject to license terms
 */

package example.stock;

import javax.microedition.rms.*;
import java.util.*;
import java.io.EOFException;

/**
 * <p>This class provides a wrapper class for the 
 * <code>RecordStore</code> class.
 * It allows for easier addition and deletion as well as better searching and
 * updating of records.  The used recordIDs are kept in a <code>Vector</code>
 * which we use to access the indices of the records.  The last used recordID
 * is stored at the beginning of the database and when the database is opened,
 * each recordID up to the last one used is tested to see if a record exists in
 * that position and a new <code>Vector</code> of used recordIDs
 * is generated.</p>
 */
public abstract class Database {

    /**
     * The database storing all the records and the last
     * used recordID in position 1
     */
    protected volatile RecordStore database = null;

    /**
     * The <code>Vector</code> of used recordIDs that are in the database
     */
    protected volatile Vector recordIDs = null;

    /**
     * The last used ID in the database
     */
    protected int lastID = 1;

    /**
     * The object used to compare two records and see if they are equal
     */
    protected RecordComparator rc = null;

    /**
     * <p>Initializes the database and if it's not a new database, loads the
     * recordID of the last record out of the first position in the
     * <code>RecordStore</code>.  We have stored it there when we closed the
     * database, then checks each ID from 1 to lastID to see if they exist in
     * the database and then add the IDs that exist to the recordIDs
     * <code>Vector</code></p>
     *
     * @param fileName The name of the <code>RecordStore</code> to open
     * @throws <code>RecordStoreNotFoundException</code> is thrown if the
     *         <code>RecordStore</code> indicated with <code>fileName</code>
     *         cannot be found
     * @throws <code>RecordStoreException</code> is thrown when a general
     *         exception occurs in a <code>RecordStore</code> operation
     * @throws <code>RecordStoreFullException</code> is thrown when the
     *         storage system is is full
     */
    public void open(String fileName) throws RecordStoreNotFoundException,
                                             RecordStoreException,
                                             RecordStoreFullException {
        database = RecordStore.openRecordStore(fileName, true);
        recordIDs = new Vector();
        try {
            if (database.getNumRecords() != 0) {
                try {
                    lastID = 
			Integer.valueOf(
			    new String(database.getRecord(1))).intValue();
                    for (int i = 1; i <= lastID; i++) {
                        try {
                            database.getRecord(i);
                            recordIDs.addElement(new Integer(i));
                        } catch (RecordStoreException rs) {}
                    }
                } catch (InvalidRecordIDException iri) {
                    throw new RecordStoreException(iri.getMessage());
                }
            }
        } catch (RecordStoreNotOpenException rsno) {
            throw new RecordStoreException(rsno.getMessage());
        }
    }

    /**
     * <p>Close the database and remove it from persistant 
     * storage if it is empty</p>
     *
     * @throws <code>RecordStoreNotOpenException</code> is thrown when trying
     *         to close a <code>RecordStore</code> that is not open
     * @throws <code>RecordStoreException</code> is thrown when a general
     *         exception occurs in a <code>RecordStore</code> operation
     */
    public void close() throws RecordStoreNotOpenException,
                               RecordStoreException {
        if (database.getNumRecords() == 0) {
            String fileName = database.getName();
            database.closeRecordStore();
            database.deleteRecordStore(fileName);
        } else {
            database.closeRecordStore();
        }
    }

    /**
     * <p>Remove the database from persistant storage</p>
     *
     * @param fileName the name of the <code>RecordStore</code> to remove
     */
    public void cleanUp(String fileName) throws RecordStoreNotFoundException,
                                                RecordStoreException {
        RecordStore.deleteRecordStore(fileName);
        open(fileName);
    }

    /**
     * <p>Add the record to the database<BR>
     * Add the recordID to our vector<BR>
     * Update the database's last ID counter</p>
     *
     * @param record The record data to be added to the database
     * @throws <code>RecordStoreNotOpenException</code> is thrown when
     *         trying to close a <code>RecordStore</code> that is not open
     * @throws <code>RecordStoreFullException</code> is thrown when the storage
     *         system is is full
     * @throws <code>RecordStoreException</code> is thrown when a general
     *         exception occurs in a <code>RecordStore</code> operation
     */
    public synchronized void add(String record)
	throws RecordStoreNotOpenException,
	RecordStoreFullException,
	RecordStoreException {
        if (database.getNumRecords() != 0) {
            database.addRecord(record.getBytes(), 0, record.getBytes().length);
            recordIDs.addElement(new Integer(++lastID));
            database.setRecord(1, (String.valueOf(lastID)).getBytes(),
                               0, (String.valueOf(lastID)).length());
        } else {
            recordIDs.addElement(new Integer(++lastID));
            database.addRecord((String.valueOf(lastID)).getBytes(),
                               0, (String.valueOf(lastID)).length());
            try {
                database.addRecord(record.getBytes(), 0,
				   record.getBytes().length);
            } catch (RecordStoreException rs) {
                recordIDs.removeElement(new Integer(lastID--));
                database.setRecord(1, (String.valueOf(lastID)).getBytes(),
                                   0, (String.valueOf(lastID)).length());
                throw rs;
            }
        }
    }

    /**
     * <p>Delete the record from the database and remove that recordID from the
     * vector of used recordIDs</p>
     *
     * @param s The name of the record to delete from the database
     * @throws <code>RecordStoreNotOpenException</code> is thrown when trying
     *         to close a <code>RecordStore</code> that is not open
     * @throws <code>RecordStoreException</code> is thrown when a general
     *         exception occurs in a <code>RecordStore</code> operation
     */
    public synchronized void delete(String s)
	throws RecordStoreNotOpenException,
	RecordStoreException {
        action(s, null, 0);
    }

    /**
     * <p>Find and return a record</p>
     *
     * @return The record that we're looking for or 
     * <code>null</code> if not found
     * @param s The name of the record to search for
     * @throws <code>RecordStoreNotOpenException</code> is thrown when trying
     *         to close a <code>RecordStore</code> that is not open
     * @throws <code>RecordStoreException</code> is thrown when a general
     *         exception occurs in a <code>RecordStore</code> operation
     */
    public synchronized String search(String s)
            throws RecordStoreNotOpenException, RecordStoreException {
        return (String) action(s, null, 1);
    }

    /**
     * <p>Update the record with the name <code>s</code> with the data
     * in the byte[] array</p>
     *
     * @param s The name of the record to update
     * @param data the new data to update the record with
     * @throws <code>RecordStoreNotOpenException</code> is thrown when trying
     *         to close a <code>RecordStore</code> that is not open
     * @throws <code>RecordStoreFullException</code> is thrown when the storage
     *         system is is full
     * @throws <code>RecordStoreException</code> is thrown when a general
     *         exception occurs in a <code>RecordStore</code> operation
     */
    public synchronized void update(String s, byte[] data)
            throws RecordStoreNotOpenException, RecordStoreFullException,
	RecordStoreException {
        action(s, data, 2);
    }
    /**
     * <p>Go to the index of the record specified by <code>s</code> and perform
     * an action.  Either an update, search or deletion.  This method is for
     * code compaction as the process for updating, searching and
     * deleting varies only slightly.</p>
     *
     * @param s The name of the record to perform the action on
     * @param data Data to use in the action
     * @param action What to do. 0 = delete, 1 = search, 2 = update
     * @throws <code>RecordStoreNotOpenException</code> is thrown when trying
     *         to close a <code>RecordStore</code> that is not open
     * @throws <code>RecordStoreFullException</code> is thrown when the storage
     *         system is is full
     * @throws <code>RecordStoreException</code> is thrown when a general
     *         exception occurs in a <code>RecordStore</code> operation
     */
    private synchronized Object action(String s, byte[] data, int action)
        throws RecordStoreNotOpenException, RecordStoreFullException,
	RecordStoreException {
        if ((action != 1) && (recordIDs.size() == 0)) {
            throw new RecordStoreException();
        }
        Enumeration IDs = recordIDs.elements();
        while (IDs.hasMoreElements()) {
            int index = ((Integer) IDs.nextElement()).intValue();
            try {
                if (rc.compare(database.getRecord(index), s.getBytes())
                        == RecordComparator.EQUIVALENT) {
                    switch (action) {
                    case 0:
                        database.deleteRecord(index);
                        recordIDs.removeElement(new Integer(index));
                        return null;
                    case 1:
                        return new String(database.getRecord(index));
                    case 2:
                        database.setRecord(index, data, 0, data.length);
                        return null;
                    default:
                        break;
                    }
                }
            } catch (InvalidRecordIDException iri) {
                throw new RecordStoreException(iri.getMessage());
            }
        }
        return null;
    }

    /**
     * <p>Return the number of records in the database</p>
     *
     * @return the number of records in the database
     * @throws <code>RecordStoreNotOpenException</code> is thrown when trying
     *         to close a <code>RecordStore</code> that is not open
     */
    public int getNumRecords() throws RecordStoreNotOpenException {
        return database.getNumRecords();
    }
}
