The MVC Model (dcstore Package)

The dcstore package manages Event List data stores (off-line files).  In principle, Event Lists can be maintained in any format such as a spreadsheet, database table, or any other off-line format, with the necessary code to perform read/write operations that complies with the requirements in the EventDataStoreServices interface. The advantage of implementing in accordance with the interface, and utilizing EventListManager (Listing 14) to retrieve and append data, is that there is no impact outside the dcstore package if or when you decide to use a different storage mechanism to maintain your data.

EventDataStoreServices Class

I defined an interface class, EventDataStoreServices, which provides a common set of method definitions to read to and write from a data store:

package dcstore;
 
import dcops.EventCalendar;
import java.util.ArrayList;
 
/**
 * The EventDataStoreServices interface class defines data and
 * methods for reading from and writing to a data store used to manage a list
 * of EventCalendar objects.
 *
 * @author Philip Nau
 */
public interface EventDataStoreServices {
 
    /**
     * Returns the set of records from a specified data store.
     * @return the set of all event calendar records.
     */
    public ArrayList readEventRecords();
   
    /**
     * Saves a user-selected EventCalendar as the start event.
     * @param ec Event record to add to the data store.
     * @return status message from write attempt.
     */
    public String writeEventRecord(EventCalendar ec);
   
    /**
     * @return the number of records in the EventList data store.
     */
    public long getEventListLength();
}

Listing 18 – EventDataStoreServices Interface

Currently I have only one implementation of the interface, a FileIO class that reads from and writes to a simple text file with records consisting of an Event name and a String containing the Event date, with a colon as a separator, as in this example:

Columbus lands on Hispanola:1492:10:12:12:00:00:EST
Mayflower lands at Plymouth Rock:1620:12:20:19:00:00:EST
Boston Tea Party:1773:12:16:22:00:00:EST
Shot heard round the World:1775:04:19:08:00:00:EST
Signing of Declaration of Independence:1776:07:04:12:00:00:EST
British Surrender at Yorktown:1781:10:19:12:00:00:EST
Signing of U.S. Constitution:1787:09:17:12:00:00:EST
Louisiana Purchase Treaty signed:1803:04:30:00:00:00:GMT
Start of War of 1812:1812:06:18:12:00:00:EST
Battle of New Orleans:1815:01:08:00:00:00:CST
Start of Civil War:1861:04:18:07:00:00:EST
Emancipation Proclamation:1863:01:01:12:00:00:EST
Gettysburg Address:1863:11:19:14:00:00:EST
End of Civil War:1865:04:10:12:00:00:EST
Assassination of Abraham Lincoln:1865:04:14:22:00:00:EST
Sinking of Battleship Maine:1898:02:15:21:00:00:EST
First Heavier Than Air Flight:1903:12:17:08:00:00:EST
World War I Armistice Day:1918:11:11:11:11:11:GMT
First Stock Market Crash (Black Thursday):1929:10:24:09:00:00:EST
Pearl Harbor Day:1941:12:07:07:00:00:HST
World War II VJ Day:1945:08:30:12:00:00:GMT
First American in Space:1962:02:20:06:00:00:EST
Assassination of John F. Kennedy:1963:11:23:12:00:00:CST
Assassination of Martin Luther King:1968:04:04:18:01:00:EST
First Man Walks on the Moon:1969:07:20:20:00:00:EST
First Space Shuttle Launch:1981:04:12:08:00:00:EST
World Trade Center Attacks:2001:09:11:08:00:00:EST

Listing 19 – Sample Event List Data

EventFileIO Class

I wrote a basic FileIO class extension that implements the EventDataStoreServices interface.  Read/write operations are performed through FileInputStream and FileOutputStream, respectively.

EventFileIO is a work in progress; it assumes all Events are read from and written to a file named EVENTS.CAL that exists in the application top-level directory.

Note there are privately declared routines to convert a String into an EventCalendar instance (convertEventRecordToEventCalendar()) and to convert an EventCalendar date/time to a String (convertEventCalendarToEventRecord()).  An enhancement will allow the creation of user-defined Event files in a user-specified location in the local filesystem.

package dcstore;
 
import java.util.ArrayList;
import java.util.Date;
import java.util.Locale;
import java.util.SimpleTimeZone;
import java.io.*;
import java.nio.file.*;
import dcops.CalendarOps;
import dcops.EventCalendar;
 
/**
 * The EventFileIO class implements the 
 * EventDataStoreServices interface. EventFileIO 
 * utilizes a text file named EVENTS.CAL that contains records of the form
 *

[event] [event-date]

 * where [event] is a String (the name of the event), and 
 * [event-date] is a String representation of the event date.
 * 
 * @author Philip Nau
 */
public class EventFileIO implements EventDataStoreServices {
   
    private final static long MAXEVENTRECS = 1000;
    private static long ACTUALEVENTRECS = 0;
    private static ArrayList eventList = new ArrayList<>();
  
    private final static String eventFile = "EVENTS.CAL";
    private final static Path eventFilePath = Paths.get(eventFile);
 
    /**
     * Reads records from the EVENTS.CAL file to the eventList ArrayList.
     * Each record is converted to an EventCalendar object, which includes the 
     * Gregorian Calendar associated with the event date, as well as String
     * representations of the event name and event date.
     */
    @Override
    public ArrayList readEventRecords() {
        String fileRecord;       
        int recCount = 0;
        try {
            FileInputStream fs = new FileInputStream(eventFile);
            BufferedReader in = new BufferedReader(new InputStreamReader(fs));
           
            eventList.clear();
            while ((fileRecord = in.readLine()) != null) {
                EventCalendar ec = convertEventRecordToEventCalendar(fileRecord);
                String eventDate = CalendarOps.convertCalendarDateToString(ec);
                ec.setEventDate(eventDate);
                eventList.add(ec);
                recCount++;
                if (recCount >= MAXEVENTRECS) {
                    in.close();
                    ACTUALEVENTRECS = MAXEVENTRECS;
                }    
            }
            in.close();
            fs.close();
            ACTUALEVENTRECS = recCount;
        }
        catch (FileNotFoundException ex) {
            ACTUALEVENTRECS = -1;
        }
        catch (IOException ex) {
            ACTUALEVENTRECS = -1;
        }
        return eventList;
    }
   
    /**
     * Appends an EventCalendar record formatted as a String in the form
     * described in the convertEventCalendarToEventRecord header 
     * to the EVENTS.CAL Event File.
     * @param ec the EventCalendar record.
     * @return the result of the write attempt.
     */
    @Override
    public String writeEventRecord(EventCalendar ec) {
        try {
            boolean isNewFile = createEventFileIfNecessary();
            
            // Format the Event Record from the Calendar components.
            String eventRecord = convertEventCalendarToEventRecord(ec);
           
            FileOutputStream fs = new FileOutputStream(eventFile, true); // true => append
            BufferedWriter out = new BufferedWriter(new OutputStreamWriter(fs));
               out.write(eventRecord);
               out.close();
               fs.flush();
               fs.close();
 
            return (ec.getEventName() + " Event saved.");
        }
        catch (IOException ex) {
            return ("ERROR - unable to save Event: " + ec.getEventName());
        }
    }
   
    /**
     * Returns the number of records in the EventList data store.
     * @return number of records, or -1 if the file is corrupt or cannot be read.
     */
    @Override
    public long getEventListLength() {
        return ACTUALEVENTRECS;
    }
   
    /**
     * Creates a new Event file if one does not exist.  
     * @return true if this is a new file, false if it already exists.
     * @throws IOException if file does not exist or cannot be accessed.
     */
    private static boolean createEventFileIfNecessary() throws java.io.IOException {
        if (!Files.exists(eventFilePath)) {
            try {
                File newFile = new File(eventFile);
                newFile.createNewFile();
                return true;
            }
            catch (java.io.IOException e) {
                throw new java.io.IOException(e);  
            }
        }
        else if (!Files.isReadable(eventFilePath)) {
            throw new java.io.IOException();
        }
        else {
            return false;
        }
    }
   
    /**
     * Converts an EventCalendar record consisting of an event name and date 
     * into its constituent parts. The record is assumed to have the format:
     *

[event name]:[event date]

     *

Where [event date] has the format

     *

[year]:[month]:[day]:[hour]:[minute]:[second]:[zone]

     *

For example:

     *

"End of World War I:1918:11:11:11:11:11:GMT"

     * @param eventRecord the Event record formatted as described above.
     * @return the EventCalendar instance to convert.
     */
    private EventCalendar convertEventRecordToEventCalendar(String eventRecord) {
        EventCalendar ec = new EventCalendar();
        String separator = ":";
        String[] tokens = eventRecord.split(separator, 10);
        ec.clear();
        if (tokens.length == 8) {
            ec.setEventName(tokens[0]);
            int year = Integer.parseInt(tokens[1]);
            int month = Integer.parseInt(tokens[2]) - 1;  // Month offset is 0.
            int day = Integer.parseInt(tokens[3]);
            int hour = Integer.parseInt(tokens[4]);
            int minute = Integer.parseInt(tokens[5]);
            int second = Integer.parseInt(tokens[6]);
            SimpleTimeZone tz = new SimpleTimeZone(0, tokens[7]);  // Only GMT for now.
            ec.set(year, month, day, hour, minute, second);
            ec.setTimeZone(tz);
        }
        return ec;
    }
   
    /**
     * Converts an EventCalendar date (maintained as GregorianCalendar values
     * into a string-formatted date as required by the EVENTS.CAL file. 
     *

The String format is:

     *

[year]:[month]:[day]:[hour]:[minute]:[second]:[zone]

     *

For example:

     *

"End of World War I:1918:11:11:11:11:11:GMT"

     * @param ec the Event Calendar record.
     * @return the formatted date string as described above.
     */
    private String convertEventCalendarToEventRecord(EventCalendar ec) {
        String rec = "";
        String separator = ":";
       
        rec += ec.getEventName() + separator;
        rec += ec.get(EventCalendar.YEAR) + separator;
        rec += String.format("%02d", (ec.get(EventCalendar.MONTH) + 1)) + separator;
        rec += String.format("%02d", ec.get(EventCalendar.DAY_OF_MONTH)) + separator;
        rec += String.format("%02d", ec.get(EventCalendar.HOUR_OF_DAY)) + separator;
        rec += String.format("%02d", ec.get(EventCalendar.MINUTE)) + separator;
        rec += String.format("%02d", ec.get(EventCalendar.SECOND)) + separator;
        rec += "GMT";
       
        return rec;
    }
}

Listing 19 – EventFileIO Class