// TransactionExtracter
package org.javamoney.examples.ez.money.importexport;
import static org.javamoney.examples.ez.money.ApplicationProperties.getImportExportCurrencyFormat;
import static org.javamoney.examples.ez.money.ApplicationProperties.getImportExportDateFormat;
import static org.javamoney.examples.ez.money.importexport.QIFConstants.CATEGORY_SEPARATOR;
import static org.javamoney.examples.ez.money.model.persisted.transaction.Transaction.MAX_CHECK_LENGTH;
import static org.javamoney.examples.ez.money.model.persisted.transaction.Transaction.MAX_NOTES_LENGTH;
import static org.javamoney.examples.ez.money.utility.IDHelper.canUseIdentifier;
import static org.javamoney.examples.ez.money.utility.IDHelper.purgeIdentifier;
import static org.javamoney.examples.ez.money.utility.IDHelper.truncateID;
import java.io.BufferedReader;
import java.text.ParseException;
import java.util.Date;
import java.util.StringTokenizer;
import org.javamoney.examples.ez.money.locale.CurrencyFormat;
import org.javamoney.examples.ez.money.model.dynamic.transaction.TransactionTypeKeys;
import org.javamoney.examples.ez.money.model.persisted.transaction.Transaction;
/**
* This class facilitates extracting transactions from a file.
*/
abstract
class
TransactionExtracter
{
/**
* Constructs a new transaction extractor.
*/
protected
TransactionExtracter()
{
setCurrency(getImportExportCurrencyFormat().getFormat());
setDateFormat(getImportExportDateFormat());
setType(null);
}
/**
* This method extracts the account from the incoming text.
*
* @param line The incoming text from the file stream.
*
* @return The account from the incoming text.
*/
protected
static
final
String
extractAccount(String line)
{
if(line.startsWith("*") == true)
{
// Quicken puts this in front of accounts it doesn't create during import.
line = line.substring(1);
}
else
{
// The QIF format specifies that all accounts be enclosed within '[' ']'.
line = line.substring(1, line.length() - 1);
}
return purgeIdentifier(line);
}
/**
* This method extracts the amount from the incoming text.
*
* @param line The incoming text from the file stream.
*
* @return The amount from the incoming text.
*
* @throws ParseException If an error occurs during parsing.
*/
protected
final
double
extractAmount(String line)
throws ParseException
{
line = removeNonmoneyValues(line);
if(line.length() == 0)
{
line = "0";
}
return getCurrency().parse(line);
}
/**
* This method extracts the amount from the incoming text to be used in a
* split.
* <p>
* <b>Note:</b> This method differs from extractAmount() in that it returns an
* amount to be used in a split, which is always positive.
*
* @param line The incoming text from the file stream.
*
* @return The amount from the incoming text.
*
* @throws ParseException If an error occurs during parsing.
*/
protected
final
String
extractAmountForSplit(String line)
throws ParseException
{
double amount = extractAmount(line);
// Amounts in splits are always positive.
amount = Math.abs(amount);
return getCurrency().format(amount);
}
/**
* This method extracts the category from the incoming text.
*
* @param line The incoming text from the file stream.
*
* @return The category from the incoming text.
*/
protected
static
final
String
extractCategory(String line)
{
StringTokenizer tokens = new StringTokenizer(line, CATEGORY_SEPARATOR);
String category = "";
// Break category down into its tokens to verify, and then reconstruct it.
while(tokens.hasMoreTokens() == true)
{
String token = tokens.nextToken();
if(canUseIdentifier(token) == false)
{
break;
}
// Reconstruct category.
category += truncateID(token);
if(tokens.hasMoreTokens() == true)
{
category += CATEGORY_SEPARATOR;
}
}
return category;
}
/**
* This method extracts the check number from the incoming text.
*
* @param line The incoming text from the file stream.
*
* @return The check number from the incoming text.
*/
protected
static
final
String
extractCheckNumber(String line)
{
if(line.length() > MAX_CHECK_LENGTH)
{
line = line.substring(0, MAX_CHECK_LENGTH);
}
return line;
}
/**
* This method extracts the date from the incoming text.
*
* @param line The incoming text from the file stream.
*
* @return The date from the incoming text.
*
* @throws ParseException If an error occurs during parsing.
*/
protected
final
Date
extractDate(String line)
throws ParseException
{
StringBuffer buffer = new StringBuffer(line);
for(int len = 0; len < buffer.length(); ++len)
{
// MS Money formats dates as MM/DD'YYYY
if(buffer.charAt(len) == '\'')
{
buffer.setCharAt(len, '/');
}
}
return getDateFormat().parse(buffer.toString());
}
/**
* This method extracts the notes from the incoming text.
*
* @param line The incoming text from the file stream.
*
* @return The notes from the incoming text.
*/
protected
final
static
String
extractNotes(String line)
{
if(line.length() > MAX_NOTES_LENGTH)
{
line = line.substring(0, MAX_NOTES_LENGTH);
}
return line;
}
/**
* This method extracts the payee from the incoming text.
*
* @param line The incoming text from the file stream.
*
* @return The payee from the incoming text.
*/
protected
final
static
String
extractPayee(String line)
{
// Purging also takes care of the max length requirements.
return purgeIdentifier(line);
}
/**
* This method returns the currency being used to parse amounts.
*
* @return The currency being used to parse amounts.
*/
protected
final
CurrencyFormat
getCurrency()
{
return itsCurrency;
}
/**
* This method returns the last extracted transaction's type.
*
* @return The last extracted transaction's type.
*/
protected
final
TransactionTypeKeys
getType()
{
return itsType;
}
/**
* This method returns true if the incoming text contains an account,
* otherwise false.
*
* @param line The incoming text from the file stream.
*
* @return true or false.
*/
protected
static
final
boolean
isAccount(String line)
{
boolean result = false;
// The QIF format specifies that all accounts be enclosed within '[' ']'.
if(line.startsWith("[") == true && line.endsWith("]") == true)
{
result = true;
}
else if(line.startsWith("*") == true)
{
// Quicken puts this in front of accounts it doesn't create during import.
result = true;
}
return result;
}
/**
* This method extracts and returns the next transaction from the stream. If
* there are no more transactions in the stream, then null is returned.
*
* @param stream The input stream that has the transactions.
*
* @return The next transaction or null if there are no more transactions in
* the stream.
*
* @throws Exception If an error occurs.
*/
protected
abstract
Transaction
next(BufferedReader stream)
throws Exception;
/**
* This method returns a string amount, based on the specified parameter, that
* can be parsed.
*
* @param line The string that contains an amount.
*
* @return A string amount that can be parsed.
*/
protected
static
String
removeNonmoneyValues(String line)
{
StringBuffer buffer = new StringBuffer(line.length());
// Remove non-money values incase file is formatted incorrectly.
for(int len = 0; len < line.length(); ++len)
{
char ch = line.charAt(len);
if(isMoneyValue(ch) == true)
{
buffer.append(ch);
}
}
return buffer.toString();
}
/**
* This method sets the last extracted transaction's type.
*
* @param type The last extracted transaction's type.
*/
protected
final
void
setType(TransactionTypeKeys type)
{
itsType = type;
}
//////////////////////////////////////////////////////////////////////////////
// Start of private methods.
//////////////////////////////////////////////////////////////////////////////
private
ImportExportDateFormatKeys
getDateFormat()
{
return itsDateFormat;
}
private
static
boolean
isMoneyValue(char ch)
{
boolean result = Character.isDigit(ch);
if(result == false)
{
// Ensure we don't lose money signs and decimal indicators.
if(ch == '-' || ch == '.' || ch == ',')
{
result = true;
}
}
return result;
}
private
void
setCurrency(CurrencyFormat currency)
{
itsCurrency = currency;
}
private
void
setDateFormat(ImportExportDateFormatKeys key)
{
itsDateFormat = key;
}
//////////////////////////////////////////////////////////////////////////////
// Start of class members.
//////////////////////////////////////////////////////////////////////////////
private CurrencyFormat itsCurrency; // TODO use actual CurrencyUnit here
private ImportExportDateFormatKeys itsDateFormat;
private TransactionTypeKeys itsType;
}