InvoiceDataModel.java

/*
  Copyright 2001, Pajato Systems Group
  Copyright 2001, Allaire Corporation
*/

package allaire.samples.invoice;

import allaire.samples.invoice.InvoiceException;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import javax.servlet.jsp.PageContext;
import javax.servlet.ServletContext;

/**
 * This class models file system based invoicing data.  An Emacs
 * package (timecard-mode.el) is used to populate the file system with
 * invoicing information.
 *
 * @author Paul Reilly, Pajato Systems Group <pmr@pajato.com>
 * @version 0.1
 */
public class InvoiceDataModel implements Serializable {

    // The master index of participating companies.
    private static final String COMPANIES_FILE_NAME = "companies.txt";

    // The path to the invoice data files (from the application root).
    private static final String INVOICE_DATA = "/data/invoice/";

    // Extensions for data information files.
    private static final String CLIENT_ADDRESS_EXT = ".address";
    private static final String CLIENT_PO_EXT = ".po";
    private static final String DEVELOPER_NAME_EXT = ".developer";
    private static final String DEVELOPER_RATE_EXT = ".rate";
    private static final String WEEKS_EXT = ".weeks";

    // The client company parameter.
    private String clientCompany;

    // Map the collection names to their associated objects.
    private Map collectionNameToObjectMap;

    // Map company names to company identifiers.
    private Map companyNameToIDMap;
    
    // Map company identifiers to a list of week ending dates.
    private Map companyIDToWeekEndingMap;

    // The JSP page context.
    private PageContext pageContext;

    // Map a property to an extension.
    private Map propertyNameToExtensionMap;
    
    // The week ending parameter.
    private String weekEnding;

    /**
     * Alternative constructor.  The page context is passed in.
     */
    public InvoiceDataModel( PageContext pageContext )
	throws InvoiceException {
	this.pageContext = pageContext;
	createMaps();
    }

    /**
     * Standard constructor.
     */
    public InvoiceDataModel() throws InvoiceException {

	// Set up the various maps.
	createMaps();

    }

    /**
     * Returns a list of company names for which data has been
     * collected.
     */
    public String[] getCompanies() throws InvoiceException {
	String[] result;

	// Determine if the company name to ID map has been
	// initialized.
	if ( companyNameToIDMap == null ) {

	    // It has not.  Do it now.
	    loadCompanies();
	}
	int index = 0;
	Set companies = companyNameToIDMap.keySet();
	result = new String[companies.size()];
	Iterator iterator = companies.iterator();
	while ( iterator.hasNext() ) {
	    result[index++] = (String) iterator.next();
	}
        return result;
    }

    /**
     * Return a seven element array containing the number of hours
     * worked on each day (Monday..Sunday).
     */
    public String[] getHours() throws InvoiceException {

	String[] result = new String[7];

	try {
	    // Load the number of hours worked on the selected week.
	    String id = (String) companyNameToIDMap.get( clientCompany );
	    String path = INVOICE_DATA + id + ".hours" + "/" + weekEnding;
	    BufferedReader reader = getReader( path );
	    StringTokenizer tokenizer =
		new StringTokenizer( reader.readLine(), "," );
	    int index = 0;

	    while ( tokenizer.hasMoreTokens() ) {
		result[index++] = tokenizer.nextToken();
	    }
	} catch (Exception exc) {
	    throw new InvoiceException( exc.getMessage() );
	}

	return result;
    }

    /**
     * Return an iterator for the given collection name.
     *
     * If the name is invalid an invoice exception will be thrown.  If
     * the collection name cannot be established yet, or is empty,
     * null is returned.  This can happen in the case of a client name
     * for which no work has been done yet.
     */
    public Iterator getIterator( String name ) throws InvoiceException {

	Iterator result = null;

	// Validate the collection name.
	if ( !((HashMap)collectionNameToObjectMap).containsKey( name ) ) {
	    // Invalid name
	    throw new InvoiceException( "Invalid collection name" );
	}

	// Fetch the array by name and determine if it has been
	// initialized.
	Object[] array = (Object[]) collectionNameToObjectMap.get( name );
	if ( array == null ) {

	    // It has not.  Do it.
	    if ( name.equals( "clientCompanies" ) ) {
		collectionNameToObjectMap.put( name, getCompanies() );
	    } else if ( name.equals( "weeks" ) ) {
		collectionNameToObjectMap.put( name, getWeeks() );		
	    }
	    array = (Object[]) collectionNameToObjectMap.get( name );
	}
	List list = Arrays.asList( array );
	result = list.iterator();

	return result;
    }

    /**
     * Return the value associated with a property using the selected
     * client company and week ending date.
     */
    public String getProperty( String name ) throws InvoiceException {

	String result = null;

	if ( name.equals( "clientCompany" ) ) {
	    result = clientCompany;
	} else if ( name.equals( "weekEnding" ) ) {
	    result = weekEnding;
	} else {
	    try {
		// Map the property to a file extension and the client
		// company to an identifier.
		String extension =
		    (String) propertyNameToExtensionMap.get( name );
		String id =
		    (String) companyNameToIDMap.get( clientCompany );
		String path = INVOICE_DATA + id + extension;
		BufferedReader reader = getReader( path );
		result = reader.readLine();
		reader.close();
	    } catch (Exception exc) {
		exc.printStackTrace();
		throw new InvoiceException( exc.getMessage() );
	    }
	}

	return result;
    }
    
    /**
     * Weeks accessor for the current client company.
     */
    public String[] getWeeks() {

	// Map the list to a string array.
	String[] result;
	String id = (String) companyNameToIDMap.get( clientCompany );
	ArrayList weeks = (ArrayList) companyIDToWeekEndingMap.get( id );
	if ( weeks == null ) {
	    result = new String[1];
	    result[0] = "No data collected";
	} else {
	    int N = weeks.size();
	    result = new String[ N ];
	    for ( int i = 0; i < N; i++ ) {
		result[i] = (String) weeks.get(i);
	    }
	}

	return result;
    }

    /**
     * Setup the data model for a given client company and week ending date.
     */
    public void initialize( String company, String date ) {
	clientCompany = company;
	weekEnding = date;
    }
    
    /**
     * Indicate whether or not the given item matches the client
     * company or the week ending string.
     */
    public String getSelected( String item ) {

	String result = "";

	if ( item.equals( clientCompany ) ||
	     item.equals( weekEnding ) ) {
	    result = "selected";
	}
	return result;
    }

    /**
     * Client company mutator.  Set the company and establish the set
     * of weeks for that company.
     */
    public void setClientCompany( String company ) {

	// Set the client company property.
	clientCompany = company;

	// Insure that the cache has week ending data for this
	// particular company.
	String id = (String) companyNameToIDMap.get( company );
	ArrayList weeks = (ArrayList) companyIDToWeekEndingMap.get( id );
	if ( weeks == null ) {

	    // Load the list of week ending dates as resource data.
	    String index;
	    String key;
	    String week;
	    weeks = new ArrayList();
	    index = INVOICE_DATA + id + WEEKS_EXT;

	    // Read the list of week ending dates.
	    try {
		BufferedReader reader = getReader( index );
		while ( (week = reader.readLine()) != null ) {
		
		    // Ignore blank lines and comment lines (starts with #)
		    week = week.trim();
		    if ( ! ("".equals( week ) || week.startsWith( "#" ) ) ) {
			
			// Append a week.
			weeks.add( week );
		    }
		}
	    } catch ( Exception exc ) {
		exc.printStackTrace();

		// Assume no data is available for the given company.
		weeks.clear();
		weeks.add( "No data collected." );
	    }

	    // Update the global map.
	    companyIDToWeekEndingMap.put( id, weeks );
	}
    }  

    /**
     * Provide a mutator for the page context.
     */
    public void setPageContext( PageContext pageContext ) {
	this.pageContext = pageContext;
    }

    /**
     * Week ending mutator.
     */
    public void setWeekEnding( String date ) {
	weekEnding = date;
    }


    // Private methods

    // Initialize the various maps used by the data model.
    private void createMaps() {

	// Set up the collection name to object map.
	collectionNameToObjectMap = new HashMap();
	collectionNameToObjectMap.put( "clientCompanies", null );
	collectionNameToObjectMap.put( "weeks", null );

	// Set up the property to extension map.
	propertyNameToExtensionMap = new HashMap();
	propertyNameToExtensionMap.put( "clientAddress", CLIENT_ADDRESS_EXT );
	propertyNameToExtensionMap.put( "clientPO", CLIENT_PO_EXT );
	propertyNameToExtensionMap.put( "developerName", DEVELOPER_NAME_EXT );
	propertyNameToExtensionMap.put( "developerRate", DEVELOPER_RATE_EXT );
    }

    // Obtain a reader for the given resource path.
    private BufferedReader getReader( String path ) throws IOException {

	// Use the URL to open an input stream reader.
	ServletContext context = pageContext.getServletContext();
	InputStream stream = context.getResourceAsStream( path );
	//InputStream stream = url.openStream();
	InputStreamReader reader = new InputStreamReader( stream );
	return new BufferedReader( reader );
    }

    // Load the companies array from the data repository.
    private void loadCompanies() throws InvoiceException {

	// Read a list of company names and identifiers, one pair per
	// line.
	try {
	    String name;
	    String id;
	    String path = INVOICE_DATA + COMPANIES_FILE_NAME;
	    BufferedReader reader = getReader( path );
	    String company = reader.readLine();
	    StringTokenizer tokenizer;
	    companyNameToIDMap = new HashMap();
	    companyIDToWeekEndingMap = new HashMap();
	    while ( company != null ) {

		// Split the id and name that was read in from the
		// file.  Save the pair in a map, create a stub for
		// the week ending map and read in the next line of
		// company information.
		tokenizer = new StringTokenizer( company, ":" );
		id = tokenizer.nextToken();
		name = tokenizer.nextToken();
		companyNameToIDMap.put( name, id );
		companyIDToWeekEndingMap.put( id, null );
		company = reader.readLine();
	    }
	    reader.close();
	} catch (FileNotFoundException fnfExc) {

	    // Deal with the file not found.
	    fnfExc.printStackTrace();
	    throw new InvoiceException( fnfExc.getMessage() );
	} catch (IOException ioExc) {

	    // Deal with an I/O Error
	    ioExc.printStackTrace();
	    throw new InvoiceException( ioExc.getMessage() );
	} catch (Exception exc) {

	    // Deal with any other exception.
	    exc.printStackTrace();
	    throw new InvoiceException( exc.getMessage() );
	}

	// Initialize the client company selector.  Assume that the
	// zeroth element will be the default.
	Object[] clientCompanies = companyNameToIDMap.keySet().toArray();
	setClientCompany( (String) clientCompanies[0] );
    }
}