The Database Class


The Database Class

The Database class could be adapted to be a subclass of Sun's database-equivalent class that you download for the assignment. You have to decide whether to modify or subclass that Sun class. I recommend subclassing to maintain legacy compatibility for the superclass class, to cleanly separate new functionality and changes from the old, and to use a new name that seems more descriptive of the class responsibilities. The Database class is responsible for being a more complete database, providing access to a single local database file and including reading, writing, and searching facilities. Finally, if any methods in the Sun class use deprecated code, you can override them in a subclass. Some candidates choose to modify the class, but I prefer the subclass route. Both routes are equally valid, but you must account for the deprecated code; doing so is part of your score.

Listing 7.4 shows an example of a Database class that acts as the database. It could be modified to be a subclass of Sun's class, if you chose to do that. Whether you modify or subclass Sun's classes, this class does give you a clear idea of what you need to do.

Listing 7.4 An Example of a Database Class
 package superbowl.database; import java.io.*; import java.util.StringTokenizer ; import java.util.Vector; import java.util.Set; import java.util.TreeSet; import java.util.ArrayList; import java.util.Iterator; /**  * This class is a new subclass of Data called Database.  * I extended the Data class to maintain legacy compatibility  * for the Data class, to cleanly separate the new functionality and  * changes from the old, and to use a new name that seems more descriptive  * of the class responsibilities. The Database class, which is a subclass of  * Data, is responsible for being a more complete database, providing access  * to a single local database file, and including reading, writing, and  * searching facilities. Two methods in Data use deprecated code, which  * might be required for legacy applications, but are overridden in the  * Database subclass.  * @author     Alain Trottier  * @version 1.0  10-Feb-2003  *  * @version 1.0  10-Feb-2003  */ public class Database implements DatabaseInterface {     public int recordCount;     public int rowNumber;     public static final String UNEXPECTED =     "Database: Unexpected database access problem";         String[] columnNames = {"Gate",                             "Level",                             "Aisle",                             "Row",                             "Seat",                             "Available"};         String[][] data =           {             {"North", "100",              "First", "A", "1", "1"},             {"North", "100",              "Second", "V", "24", "1"},             {"North", "100",              "First", "B", "39", "1"},             {"South", "150",              "Second", "C", "18", "1"},             {"South", "150",              "Third", "E", "6", "1"},             {"North", "100",              "First", "A", "10", "1"},             {"North", "100",              "First", "A", "13", "1"},             {"North", "100",              "First", "A", "21", "1"},             {"North", "100",              "First", "A", "42", "1"},             {"North", "100",              "First", "A", "69", "1"},             {"North", "30",              "First", "F", "29", "1"},             {"North", "30",              "Second", "W", "4", "1"},             {"North", "30",              "First", "L", "95", "1"},             {"South", "50",              "Second", "P", "108", "1"},             {"South", "50",              "Third", "T", "71", "1"},             {"North", "10",              "First", "X", "12", "1"},             {"North", "10",              "First", "Q", "41", "1"},             {"North", "10",              "First", "N", "52", "1"},             {"North", "10",              "First", "U", "25", "1"},             {"North", "10",              "First", "A", "8", "1"}           };    /**     * This constructor fills the database with data.     * It could open a database file to do so.     *     * @param dbname The name of the database file to open.     * @exception IOException     */      public Database(String dbname) throws IOException      {         recordCount = data.length;         // dbFile = new RandomAccessFile(new File(dbname), "rw");         // process file      }    /**    * Searches the database for distinct values for the specified field.    * The distinct values are kept in the Set setDistinctValues    * and updated only if a record is added or deleted.    * This method retains the generic nature of the database.    *    * @param fieldIndex The column or field index.    * @return The unique field values.    * @exception SuperBowlException Thrown when database can't be accessed    */    public String[] getDistinctValuesForField(int fieldIndex)        throws SuperBowlException    {         Set setDistinctValues = new TreeSet();         // add column value to set         setDistinctValues.add( "any" );         for (int rowCount = 0; rowCount <= recordCount-1; rowCount++)         {               // add column value to set               setDistinctValues.add( data[rowCount][fieldIndex] );         }         // Create a String array of unique values         String[] distinctValues = (String[])setDistinctValues.toArray(                                    new String[setDistinctValues.size()]);         return distinctValues;    }     /**     * Returns an integer based on the row number.     * @return The row number.     * @exception SuperBowlException Thrown if cannot open connection.     * @exception RemoteException Thrown if RMI fails.     */    public int getRowNumber() throws SuperBowlException    {       return rowNumber;    }     /**     * Returns a two dimensional array of data.     *     * @return String[][] - This array of data objects     * @exception SuperBowlException - Thrown if cannot open connection.     * @exception RemoteException - Thrown if RMI fails.     */    public String[][] getData() throws SuperBowlException    {       return data;    }   /**    * Searches the database for entries with desired    * fields that exactly match the string supplied. If the required    * record cannot be found, this method returns null. For this    * assignment, the key field is the record number field.    *    * @param criteria - The key field value to match for    *           a successful find.    * @return The matching records.    * @exception SuperBowlException - Thrown when database connection fails    */    public synchronized String[][] searchSeat(String criteria)        throws SuperBowlException    {        ArrayList rowData = new ArrayList();        String[] values = null;        String[][] newData = null;        String WILDCARD = "any";        StringTokenizer stk = new StringTokenizer(criteria,                                                 "\t\n\r\f'\"=;,");        // return null if tokens are odd or 0        if ( stk.countTokens () % 2 != 0  stk.countTokens () ==0)        {            return null;        }        // define and assign criteriaString and fieldIndex array        String [] criteriaString = new String[stk.countTokens()];        for( int i = 0; i < criteriaString.length; i++)        {            criteriaString[i] = stk.nextToken().trim();        }        try        {             boolean isMatch = false;             int column;             String value;             NextRow:             for (int rowCount = 0; rowCount <= recordCount-1; rowCount++)             {                 values = data[rowCount];                 if (values != null)                 {                     isMatch = false;                     for( int criteriaCount = 0;                          criteriaCount < criteriaString.length;                          criteriaCount += 2)                     {                         column = getColumn(criteriaString[criteriaCount]);                         value = criteriaString[criteriaCount + 1];                         // if search criteria is "any" or database field                         // value matches criteria value, go to next test.                         if ( !(value.equalsIgnoreCase(WILDCARD)                                 values[column].equalsIgnoreCase(value)) )                         {                             //fails one criteria, so skip row                             continue NextRow;                         }                     }                     // all criteria are met, so add seat number to the set                     rowData.add(new Integer(rowCount));                     rowNumber = rowCount;                 }             }            newData = new String[rowData.size()][];            int rowCount = 0;            for (Iterator it=rowData.iterator(); it.hasNext(); rowCount++) {                newData[rowCount] = data[((Integer)it.next()).intValue()] ;            }            // return the 2D array; null if no record found            return newData;        }  catch (Exception e)        {            throw new SuperBowlException(UNEXPECTED + e);        }    }     /**     * This method finds which column index a given name matches.     *     * @param columnName The name of the column.     * @return The index of the matching column.     */     public int getColumn(String columnName)     {         int columnCount = columnNames.length;         //find column position         for (int index = 0; index < columnCount; index++)         {             if ( columnName.trim().equalsIgnoreCase(                                           columnNames[index].trim()) )             {                     return index;             }         }         return -1;     }     /**     * This method returns a description of the database schema as an     * array of ColumnData objects.     *     * @return The array of ColumnData objects that form     *          the schema to this database.     */     public String[] getColumnNames() {         return columnNames;     }     /**     * Gets the number of records stored in the database.     */     public int getRowCount() { return recordCount; }     /**     * Gets a requested record from the database based on record number.     * @param rowNumber The number of the record to read (first record is 1).     * @return RowData for the record or null if the record has been     * marked for deletion.     * @exception SuperBowlException Thrown if database connection fails.     */     public synchronized String[] getRow(int rowNumber)                                  throws SuperBowlException     {         try {            if (rowNumber<1) {               throw new SuperBowlException(                                "Record number must be greater than 1");            }             String[] records = data[rowNumber];             if (records == null) {                return null;             }             return records;         } catch(Exception ex) {             throw new SuperBowlException(UNEXPECTED);         }     }    /**    * Writes a new record to the database using the current location of the    * underlying random access file.    * @param newData An array of strings in the database-specified order.     * @return The value at that row and column.    * @exception SuperBowlException - Thrown when database connection fails    */    public synchronized String getValue(int row, int column)                               throws SuperBowlException    {         return data[row][column];    }    /**    * Writes a new record to the database using the current location of the    * underlying random access file.    * @param newData An array of strings in the database-specified order.    * @exception SuperBowlException Thrown when database connection fails    */    public synchronized void setValue(String value, int row, int column)                             throws SuperBowlException    {         data[row][column] = value;    }    /**    * Lock the requested record. If the argument is -1, lock the whole    * database. This method blocks until the lock succeeds. No timeouts    * are defined for this.    * @param record The record number to lock.    * @exception IOException If the record position is invalid.    */    public void lock(int record) throws IOException    {         //do nothing in local mode    }     /**    * Unlock the requested record. Ignored if the caller does not have    * a current lock on the requested record.    * @param record The record number to unlock.    */    public void unlock(int record)    {        //do nothing in local mode    } } 

Notice that the searchSeat() method finds the row with the seat that matches the search criteria. Although there are numerous ways to conduct that search algorithm, this one is abstract enough to work on any table, not just the one in this particular assignment.

The searchSeat() method is abstracted so that it returns a two-dimensional String array containing all rows that have at least one value matching one criteria value. The column names and values are dynamic, so this method processes any number of criteria for any table, not just the one in the assignment download. This is the algorithm used:

  1. Parse the criteria string with StringTokenizer .

  2. Get a new row of data from the database.

  3. Find a column that matches the criteria field name.

  4. Test for a match between the row value and search value.

  5. If there's a match, save the row of data and go to step 2.

  6. If there's no match for current criteria, go to the next criteria and start at step 3.

  7. When the criteria are exhausted, go to the next row and start at step 2.

The searchSeat() method uses java.util.StringTokenizer to parse the search criteria. This class breaks Strings into chunks at the delimiters. The requirements specify making the search criteria String simple, so I stayed with StringTokenizer . I would have chosen the regular expression route if the input search String were more complicated. If you decide to use SQL to define the search criteria, you could parse the SQL string in the searchSeat() method. It is more work, but the result is stronger than what I did.

This Database class is the one used for local mode. Listing 7.5 shows the equivalent class on the RMI side. The comments are the same as in Listing 7.4, so they have been removed from this listing. The two listings are similar, but not the same, so examine this one carefully .

Listing 7.5 An Example of a Database Class for RMI
 package superbowl.server; import superbowl.database.Database; import superbowl.database.SuperBowlException; import superbowl.database.DatabaseInterface; import superbowl.database.LockManager; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; import java.io.IOException; /**  * This class is the implementation of RMI for the application.  * It is here that the RMI engine looks to for the stub. In other  * words the Database methods (local) are wrapped with these  * remote methods so the calling routine can treat these remote methods  * as is they are local.  * @author     Alain Trottier  * @version     1.0, 2/10/03  */ public class DatabaseRMIImpl extends UnicastRemoteObject                              implements DatabaseInterface {     private final static LockManager lockManager = new LockManager();     private static Database database;     protected static final String UNEXPECTED =         "DatabaseRMIImpl-Unexpected database access problem: ";     public DatabaseRMIImpl(String dbname)           throws SuperBowlException, RemoteException     {         super();          try         {             database = new Database(dbname);         } catch(RemoteException rex)         {             throw new SuperBowlException(UNEXPECTED + rex);         } catch(IOException ex)         {             throw new SuperBowlException(UNEXPECTED + ex);         }     }     /**     * Returns a description of the database columns.     *     * @return This array of ColumnData objects     *   defines the database schema.     * @exception SuperBowlException Thrown if cannot open connection.     * @exception RemoteException Thrown if RMI fails.     */     public String[] getColumnNames()               throws SuperBowlException, RemoteException     {         return database.getColumnNames();     }     /**     * Returns a two dimensional array of data.     *     * @return This array of data objects     * @exception SuperBowlException Thrown if cannot open connection.     * @exception RemoteException Thrown if RMI fails.     */    public String[][] getData() throws SuperBowlException    {         return database.getData();    }     /**     * Gets the number of rows stored in the database.     * @return The total row number     * @exception SuperBowlException Thrown if cannot open connection.     * @exception RemoteException Thrown if RMI fails.     */     public int getRowCount() throws SuperBowlException, RemoteException     {         return database.getRowCount();     }     /**     * returns a given row based on row number.     * @param rowNumber The row number.     * @return The row.     * @exception SuperBowlException Thrown if cannot open connection.     * @exception RemoteException - Thrown if RMI fails.     */    public synchronized String[] getRow(int rowNumber)                     throws SuperBowlException ,RemoteException     {         return database.getRow(rowNumber);     }     /**     * returns a given value based on row and column.     * @param row The row number.     * @param column The column number.     * @return The particular value at row and column.     * @exception SuperBowlException Thrown if cannot open connection.     * @exception RemoteException Thrown if RMI fails.     */     public String getValue(int row, int column)            throws SuperBowlException, RemoteException     {         return database.getValue(row, column);     }     /**     * Returns an integer based on the row number.     * @return The row number.     * @exception SuperBowlException Thrown if cannot open connection.     * @exception RemoteException Thrown if RMI fails.     */    public int getRowNumber()            throws SuperBowlException, RemoteException    {        return database.getRowNumber();    }     /**     * sets a given value based on row and column numbers.     * @param value The actual cell value.     * @param row The row number.     * @param column The column number.     * @exception SuperBowlException Thrown if cannot open connection.     * @exception RemoteException Thrown if RMI fails.     */     public void setValue(String value, int row, int column)            throws SuperBowlException, RemoteException     {         database.setValue(value, row, column);     }     /**     * Lock the row of interest..     * @param recno The row number to lock.     * @exception IOException If the row position is invalid.     * @exception RemoteException Thrown if RMI fails.     */     public void lock(int record) throws IOException, RemoteException     {         try         {             lockManager.lock(record, this);         } catch(InterruptedException iex)         {             throw new IOException(UNEXPECTED + iex);         }     }     /**     * Unlock the requested row. Ignored if the caller does not have     * a current lock on the requested row.     * @param row The row number to unlock.     * @exception SuperBowlException Thrown if cannot open connection.     * @exception RemoteException Thrown if RMI fails.     */     public void unlock(int record)            throws SuperBowlException, RemoteException     {          lockManager.unlock(record, this);     }     /**     * Searches the database for entries which have desired     * fields which exactly match the string supplied. If the required     * row cannot be found, this method returns null. For this     * assignment, the key field is the row number field.     *     * @param criteria The key field value to match upon for     *           a successful find.     * @return The matching rows.     * @exception SuperBowlException Thrown if cannot open connection.     * @exception RemoteException Thrown if RMI fails.     */     public String[][] searchSeat(String criteria) throws SuperBowlException                                                          , RemoteException     {          return database.searchSeat(criteria);     }     /**     * Searches the database for the distinct values for the specified field.     * The distinct values are kept in the Set setDistinctValues     * and updated only if addition/deletion of row.     * This method retains the generic nature of the database.     *     * @param fieldIndex The column or field index.     * @return The unique field values.     * @exception SuperBowlException Thrown if cannot open connection.     * @exception RemoteException Thrown if RMI fails.     */     public String[] getDistinctValuesForField(int fieldIndex)                throws SuperBowlException, RemoteException     {          return database.getDistinctValuesForField(fieldIndex);     } } 

This class uses the Decorator pattern. It decorates Database with the intent to attach a responsibility to an object at runtime. Decoration is a flexible alternative to extending a class. Sometimes you want to add a responsibility to object A, not its whole class. In this project, DatabaseRMIImpl wraps Database and adds the responsibility for IRL via a LockManager . The decorator ( DatabaseRMIImpl ) implements Database 's interface ( DatabaseInterface ), so Database 's clients interact with DatabaseRMIImpl as though it were Database . Transparency enables you to decorate the decorated object, adding many responsibilities.

Notice that the lock() and unlock() methods are different from what you saw in Listing 7.4. In fact, these two methods are the main differences between the two classes. Listing 7.5 shows you how this class uses LockManager . Listing 7.4 didn't because that example is used in local mode and you don't need LockManager . Whether operation is in local or remote mode, the client simply calls for a lock. If the class in Listing 7.4 is referenced, the lock() and unlock() methods are passed to the superclass where these methods are empty. If the class in Listing 7.5 is referenced, the lock() and unlock() methods in LockManager are actually called.



JavaT 2 Developer Exam CramT 2 (Exam CX-310-252A and CX-310-027)
JavaT 2 Developer Exam CramT 2 (Exam CX-310-252A and CX-310-027)
ISBN: N/A
EAN: N/A
Year: 2003
Pages: 187

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net