public class AchFileWriter {
	
	private static final int BLOCKING_FACTOR = 10;
	private static final int FORMAT_CODE = 1;
	private static final PriorityCode PRIORITY_CODE = PriorityCode.DEFAULT;
	private static final int RECORD_SIZE = 94;

	private final Writer writer;
	private final AchFile file;
	
	public AchFileWriter(AchFile file, Writer writer) {
		this.file = file;
		this.writer = writer;
	}
	
	public void write() throws AchFileFormatException, IOException {
		
		long totalRecordsInFile = 0;

		AchFileFormat format = new AchFileFormat();
		
		format.header = new AchFileHeaderFormat();
		format.header.fileCreationDate = file.getCreationDate();
		format.header.fileCreationTime = file.getCreationTime();
		format.header.fileIdModifier = file.getFileIdModifier();
		format.header.immediateDestination = file.getImmediateDestination();
		format.header.immediateDestinationName = file.getImmediateDestinationName();
		format.header.immediateOrigin = file.getImmediateOrigin();
		format.header.immediateOriginName = file.getImmediateOriginName();
		format.header.referenceCode = file.getReferenceCode();
		/*
		 * automatically assigned
		 */
		format.header.blockingFactor = BLOCKING_FACTOR;
		format.header.formatCode = FORMAT_CODE;
		format.header.priorityCode = PRIORITY_CODE;
		format.header.recordSize = RECORD_SIZE;
		format.header.recordType = RecordType.FILE_HEADER;
		totalRecordsInFile++;
		
		long formatEntryAddendaCount = 0;
		long formatEntryHashSum = 0;
		long totalDebitEntryDollarAmountInFile = 0;
		long totalCreditEntryDollarAmountInFile = 0;
		
		format.batches = new ArrayList<AchBatchFormat>();
		for (AchBatch<?> batch : file.getBatches()) {
			
			AchBatchFormat batchFormat = new AchBatchFormat();
			format.batches.add(batchFormat);
			
			batchFormat.header = new AchBatchHeaderFormat();
			batchFormat.header.companyDescriptiveDate = batch.getCompanyDescriptiveDate();
			batchFormat.header.companyDiscretionaryData = batch.getCompanyDiscretionaryData();
			batchFormat.header.companyEntryDescription = batch.getCompanyEntryDescription();
			batchFormat.header.companyIdentification = batch.getCompanyIdentification();
			batchFormat.header.companyName = batch.getCompanyName();
			batchFormat.header.effectiveEntryDate = batch.getEffectiveEntryDate();
			batchFormat.header.originatingDfiIdentification = batch.getOriginatingDfiIdentification();
			batchFormat.header.originatorStatusCode = batch.getOriginatorStatusCode();
			batchFormat.header.serviceClassCode = batch.getServiceClassCode();
			batchFormat.header.settlementDate = batch.getSettlementDate();
			batchFormat.header.standardEntryClassCode = batch.getStandardEntryClassCode();
			batchFormat.header.recordType = RecordType.BATCH_HEADER;
			batchFormat.header.batchNumber = format.batches.size();
			batchFormat.entries = new ArrayList<AchEntryDetailFormat>();
			totalRecordsInFile++;
			
			long entryAddendaCount = 0;
			long totalCreditEntryDollarAmount = 0;
			long totalDebitEntryDollarAmount = 0;
			long entryHashSum = 0;
			
			for (AchEntry e : batch.getEntries()) {
				
				if (e instanceof RCKEntry) {
					
					RCKEntry entry = (RCKEntry)e;
					RCKEntryFormat entryFormat = new RCKEntryFormat();
					
					entryFormat.amount = entry.getAmount();
					entryFormat.receivingDfiIdentification = entry.getReceivingDfiIdentification();
					entryFormat.checkSerialNumber = entry.getCheckSerialNumber();
					entryFormat.dfiAccountNumber = entry.dfiAccountNumber;
					entryFormat.discretionaryData = entry.getDiscretionaryData();
					entryFormat.individualName = entry.getIndividualName();
					entryFormat.traceNumber = entry.getTraceNumber();
					entryFormat.transactionCode = entry.getTransactionCode();
					
					if (entry.getReceivingDfiIdentification() == null) {
						throw new AchFileFormatException("The RCKEntry.receivingDfiIdentification field cannot be empty.");
					}
					entryFormat.checkDigit = entry.getReceivingDfiIdentification().checkDigit();
					entryFormat.recordType = RecordType.ENTRY_DETAIL;
					entryFormat.addendaRecordIndicator = false;
					
					batchFormat.entries.add(entryFormat);
					
					entryAddendaCount++;
					entryHashSum += entry.getReceivingDfiIdentification().dfiIdentity();
					totalDebitEntryDollarAmount += entry.getAmount();
					
					totalRecordsInFile++;
				}
				else if (e instanceof PPDEntry) {
					
					PPDEntry entry = (PPDEntry)e;
					PPDEntryFormat entryFormat = new PPDEntryFormat();
					
					entryFormat.amount = entry.getAmount();
					entryFormat.receivingDfiIdentification = entry.getReceivingDfiIdentification();
					entryFormat.individualIdentificationNumber = entry.getIndividualIdentificationNumber();
					entryFormat.dfiAccountNumber = entry.dfiAccountNumber;
					entryFormat.discretionaryData = entry.getDiscretionaryData();
					entryFormat.individualName = entry.getIndividualName();
					entryFormat.traceNumber = entry.getTraceNumber();
					entryFormat.transactionCode = entry.getTransactionCode();
					
					if (entry.getReceivingDfiIdentification() == null) {
						throw new AchFileFormatException("The PPDEntry.receivingDfiIdentification field cannot be empty.");
					}
					entryFormat.checkDigit = entry.getReceivingDfiIdentification().checkDigit();
					entryFormat.recordType = RecordType.ENTRY_DETAIL;
					
					if (entry.getAddenda() != null) {
						
						entryFormat.addendaRecordIndicator = true;
						
						entryFormat.addenda = new PPDEntryAddendaFormat();
						entryFormat.addenda.recordType = RecordType.ENTRY_ADDENDA;
						entryFormat.addenda.addendaTypeCode = AddendaTypeCode.ADDENDA;
						entryFormat.addenda.paymentRelatedInformation = entry.getAddenda().getPaymentRelatedInformation();
						entryFormat.addenda.entryDetailSequenceNumber = entryFormat.traceNumber.getEntryDetailSequenceNumber();
						
						/*
						 * TODO: Not sure how this sequence number is to be assigned.
						 */
						entryFormat.addenda.addendaSequenceNumber = 1;
						
						entryAddendaCount++;
					}
					else {
						entryFormat.addendaRecordIndicator = false;
					}
					
					batchFormat.entries.add(entryFormat);
					
					entryAddendaCount++;
					entryHashSum += entry.getReceivingDfiIdentification().dfiIdentity();
					totalDebitEntryDollarAmount += entry.getAmount();
					
					totalRecordsInFile++;
				}
				else {
					throw new RuntimeException("Unsupported entry type: " + e);
				}
			}
			
			batchFormat.control = new AchBatchControlFormat();
			batchFormat.control.batchNumber = format.batches.size();
			batchFormat.control.entryAddendaCount = entryAddendaCount;
			batchFormat.control.entryHash = truncateHash(entryHashSum);
			batchFormat.control.messageAuthenticationCode = null;
			batchFormat.control.totalCreditEntryDollarAmount = totalCreditEntryDollarAmount;
			batchFormat.control.totalDebitEntryDollarAmount = totalDebitEntryDollarAmount;
			batchFormat.control.reserved = null;
			batchFormat.control.serviceClassCode = ServiceClassCode.ACH_DEBITS_ONLY;
			batchFormat.control.companyIdentification = batch.getCompanyIdentification();;
			batchFormat.control.originatingDfiIdentification = batch.getOriginatingDfiIdentification();
			batchFormat.control.recordType = RecordType.BATCH_CONTROL;
			
			formatEntryAddendaCount += entryAddendaCount;
			formatEntryHashSum += entryHashSum;
			totalCreditEntryDollarAmountInFile += totalCreditEntryDollarAmount;
			totalDebitEntryDollarAmountInFile += totalDebitEntryDollarAmount;
			
			totalRecordsInFile++;
		}
		
		format.control = new AchFileControlFormat();
		format.control.batchCount = format.batches.size();
		format.control.entryAddendaCount = formatEntryAddendaCount;
		format.control.entryHash = truncateHash(formatEntryHashSum);
		format.control.recordType = RecordType.FILE_CONTROL;
		format.control.reserved = null;
		format.control.totalCreditEntryDollarAmountInFile = totalCreditEntryDollarAmountInFile;
		format.control.totalDebitEntryDollarAmountInFile = totalDebitEntryDollarAmountInFile;
		totalRecordsInFile++;
		
		long blockCount;
		
		if (totalRecordsInFile % 10 != 0) {
			blockCount = (totalRecordsInFile / 10) + 1;
		}
		else {
			blockCount = totalRecordsInFile / 10;
		}
		
		format.control.blockCount = blockCount;
		
		AchFileFormatWriter formatWriter = new AchFileFormatWriter(format, writer);
		formatWriter.write();
	}
	
	private long truncateHash(long hashSum) {
		
		String entryHashString = Long.toString(hashSum);

		if (entryHashString.length() > 8) {
			int index = entryHashString.length() - 8;
			entryHashString = entryHashString.substring(index);
		}

		return Long.parseLong(entryHashString);
	}
} public class FileIdModifier {

	private final char value;

	public FileIdModifier(char value) throws FileIdModifierException {
		
		if (!Pattern.matches("[0-9A-Z]{1}", Character.toString(value))) {
			throw new FileIdModifierException("must be a single character between 0-9 or A-Z: '" + value + "'");
		}
		
		this.value = value;
	}
	
	public char getValue() {
		return value;
	}
	
	public boolean equals(Object o) {
		return
			o != null
			&&
			o instanceof FileIdModifier
			&&
			((FileIdModifier)o).value == value;
	}
	
	public String toString() {
		return Character.toString(value);
	}
	
	public static char[] possibleValues() {
	
		char[] values = new char[36];
		
		for (int i=0; i<26; i++) {
			values[i] = (char) ('A' + i); 
		}
		
		for (int i=0; i<10; i++) {
			values[26 + i] = (char) ('0' + i);
		}
		
		return values;
	}
} public enum OriginatorStatusCode {

	/**
	 * ADV file prepared by an ACH Operator.
	 */
	ADV_FROM_ACH_OPERATOR("0"),
	
	/**
	 * This code identifies the Originator as a depository financial 
	 * institution which has agreed to be bound by these rules.
	 */
	AGENT_SUBJECT_TO_RULES("1"),
	
	/**
	 * This code identifies the Originator as a federal government entity or 
	 * agency not subject to these rules.
	 */
	AGENT_NOT_SUBJECT_TO_RULES("2");
	
	private final String value;

	private OriginatorStatusCode(String value) {
		this.value = value;
	}

	public String getValue() {
		return value;
	}
} public class PPDEntry implements AchEntry {

	long amount;
	RoutingNumber receivingDfiIdentification;
	String dfiAccountNumber;
	String discretionaryData;
	String individualName;
	TransactionCode transactionCode;
	String individualIdentificationNumber;
	TraceNumber traceNumber;
	PPDEntryAddenda addenda;

	public long getAmount() {
		return amount;
	}

	public void setAmount(long amount) {
		this.amount = amount;
	}

	public RoutingNumber getReceivingDfiIdentification() {
		return receivingDfiIdentification;
	}

	public void setReceivingDfiIdentification(
			RoutingNumber receivingDfiIdentification) {
		this.receivingDfiIdentification = receivingDfiIdentification;
	}

	public String getDfiAccountNumber() {
		return dfiAccountNumber;
	}

	public void setDfiAccountNumber(String dfiAccountNumber) {
		this.dfiAccountNumber = dfiAccountNumber;
	}

	public String getDiscretionaryData() {
		return discretionaryData;
	}

	public void setDiscretionaryData(String discretionaryData) {
		this.discretionaryData = discretionaryData;
	}

	public String getIndividualName() {
		return individualName;
	}

	public void setIndividualName(String individualName) {
		this.individualName = individualName;
	}

	public TransactionCode getTransactionCode() {
		return transactionCode;
	}

	public void setTransactionCode(TransactionCode transactionCode) {
		this.transactionCode = transactionCode;
	}

	public String getIndividualIdentificationNumber() {
		return individualIdentificationNumber;
	}

	public void setIndividualIdentificationNumber(
			String individualIdentificationNumber) {
		this.individualIdentificationNumber = individualIdentificationNumber;
	}

	public TraceNumber getTraceNumber() {
		return traceNumber;
	}

	public void setTraceNumber(TraceNumber traceNumber) {
		this.traceNumber = traceNumber;
	}

	public PPDEntryAddenda getAddenda() {
		return addenda;
	}

	public void setAddenda(PPDEntryAddenda addenda) {
		this.addenda = addenda;
	}
} public class PPDEntryAddenda {

	String paymentRelatedInformation;
	
	public String getPaymentRelatedInformation() {
		return paymentRelatedInformation;
	}

	public void setPaymentRelatedInformation(String paymentRelatedInformation) {
		this.paymentRelatedInformation = paymentRelatedInformation;
	}
} public class SimpleDate {

	int year, month, day;

	/**
	 * Take note that the year field must be two digits.
	 */
	public SimpleDate(int year, int month, int day) throws SimpleDateException {
		
		if (year < 0 || year > 99) {
			throw new SimpleDateException("The year value must be a two digit year");
		}
		
		if (month < 1 || month > 12) {
			throw new SimpleDateException("The month value is invalid");
		}
		
		if (day < 1 || month > 31) {
			throw new SimpleDateException("The day value is invalid");
		}
		
		this.year = year;
		this.month = month;
		this.day = day;
	}
	
	public int getYear() {
		return year;
	}

	public int getMonth() {
		return month;
	}

	public int getDay() {
		return day;
	}
	
	public boolean equals(Object o) {
		return
			o != null
			&&
			o instanceof SimpleDate
			&&
			((SimpleDate)o).year == year
			&&
			((SimpleDate)o).month == month
			&&
			((SimpleDate)o).day == day;
	}
} public class SimpleTime {

	int hours;
	int minutes;
	
	public SimpleTime(int hours, int minutes) throws SimpleTimeException {
		
		if (hours < 0 || hours > 23) {
			throw new SimpleTimeException("The hours value is invalid");
		}
		
		if (minutes < 0 || minutes > 59) {
			throw new SimpleTimeException("The minutes value is invalid");
		}
		
		this.hours = hours;
		this.minutes = minutes;
	}

	public int getHours() {
		return hours;
	}

	public int getMinutes() {
		return minutes;
	}
	
	public boolean equals(Object o) {
		return
			o != null
			&&
			o instanceof SimpleTime
			&&
			((SimpleTime)o).hours == hours
			&&
			((SimpleTime)o).minutes == minutes;
	}
} public enum StandardEntryClassCode {

	/**
	 * ACH Payment Acknowlegement
	 */
	ACK,
	
	/**
	 * Automated Accounting Advice
	 */
	ADV,
	
	/**
	 * Accounts Receivable Entry
	 */
	ARC,
	
	/**
	 * Financial EDI Acknowlegement 
	 */
	ATX,
	
	/**
	 * Corporate Cross-Border Payment
	 */
	CBR,
	
	/**
	 * Cash Concentration or Disbursement
	 */
	CCD,
	
	/**
	 * Customer Initiated Entry
	 */
	CIE,
	
	/**
	 * Automated Notification of Change and Automated Refused Notification of Change
	 */
	COR,
	
	/**
	 * Corporate Trade Exchange
	 */
	CTX,
	
	/**
	 * Death Notification Entry
	 */
	DNE,
	
	/**
	 * Automated Enrollment Entry
	 */
	ENR,
	
	/**
	 * Machine Transfer Entry
	 */
	MTE,
	
	/**
	 * Consumer Cross-Border Payment
	 */
	PBR,
	
	/**
	 * Point-of-Purchase Entry
	 */
	POP,
	
	/**
	 * Point of Sale Entry
	 */
	POS,
	
	/**
	 * Prearranged Payment and Deposit Entry
	 */
	PPD,
	
	/**
	 * Re-presented Check Entry
	 */
	RCK,
	
	/**
	 * Shared Network Transaction
	 */
	SHR,
	
	/**
	 * Telephone Initiated Entry
	 */
	TEL,
	
	/**
	 * Truncated Entry
	 */
	TRC,
	
	/**
	 * Truncated Entries Exchange
	 */
	TRX,
	
	/**
	 * Internet-Initiated Entry
	 */
	WEB,
	
	/**
	 * Destroyed Check Entry
	 */
	XCK;
} public class TraceNumber {
	
	private final RoutingNumber routingNumber;
	private final long sequenceNumber;
	
	public TraceNumber(RoutingNumber routingNumber, long sequenceNumber) throws TraceNumberException {
		
		if (routingNumber == null) {
			throw new NullPointerException();
		}
		
		if (sequenceNumber < 0) {
			throw new TraceNumberException("The entry detail sequence number must be a number greater than or equal to zero");
		}
		
		String value = Long.toString(sequenceNumber);
		if (value.length() > 7) {
			throw new TraceNumberException("The entry detail sequence number must be less than or equal to seven digits.");
		}
		
		this.sequenceNumber = sequenceNumber;
		this.routingNumber = routingNumber;
	}
	
	public TraceNumber(String value)  throws TraceNumberException {
		
		RoutingNumber routingNumber;
		try {
			routingNumber = RoutingNumber.fromNoChecksum(value.substring(0, 8));
		}
		catch (RoutingNumberException ex) {
			throw new TraceNumberException(ex.getMessage());
		}
		
		String sequenceNumberString = value.substring(8);
		sequenceNumberString = FieldUtil.leftStrip(sequenceNumberString, '0');
		long sequenceNumber = Long.parseLong(sequenceNumberString);
		
		this.sequenceNumber = sequenceNumber;
		this.routingNumber = routingNumber;
	}
	
	public RoutingNumber getOdfiRoutingNumber() {
		return routingNumber;
	}

	public long getEntryDetailSequenceNumber() {
		return sequenceNumber;
	}
	
	public boolean equals(Object o) {
		return
			o != null
			&&
			o instanceof TraceNumber
			&&
			routingNumber.equals(((TraceNumber)o).routingNumber)
			&&
			sequenceNumber == ((TraceNumber)o).sequenceNumber;
	}
	
	public String toString() {
		
		String routingNumberString = this.getOdfiRoutingNumber().toString();
		routingNumberString = routingNumberString.substring(0, routingNumberString.length() - 1);
		
		String sequenceNumberString = Long.toString(this.getEntryDetailSequenceNumber());
		sequenceNumberString = FieldUtil.leftPad(sequenceNumberString, '0', 7);
		
		return routingNumberString + sequenceNumberString;
	}
} public enum TransactionCode {
	
	/*
	 * Demand Credit Records (for checking, NOW, and share draft accounts).
	 */

	/**
	 * Reserved.
	 */
	RESERVED_20("20"),
	
	/**
	 * Automated Return or Notification of Change for original transaction 
	 * codes {@link #CODE_22}, {@link #CODE_23}, or {@link #CODE_24}.
	 */
	CODE_21("21"),
	
	/**
	 * Automated Deposit
	 */
	CODE_22("22"),
	
	/**
	 * Prenotification of Demand Credit Authorization; Death Notification 
	 * (non-dollar); Automated Enrollment Entry (non-dollar).
	 */
	CODE_23("23"),
	
	/**
	 * Zero dollar with remittance data (for {@link StandardEntryClassCode#CCD}
	 * and {@link StandardEntryClassCode#CTX} entries only); Acknowledgement 
	 * Entries ({@link StandardEntryClassCode#ACK} and 
	 * {@link StandardEntryClassCode#ATX} entries only).
	 */
	CODE_24("24"),
	
	/*
	 * Demand Debit Records (for checking, NOW, and share draft accounts).
	 */
	
	/**
	 * Reserved.
	 */
	RESERVED_25("25"),
	
	/**
	 * Automated Return or Notification of Change for original transaction code 
	 * {@link #CODE_27}, {@link #CODE_28}, or {@link #CODE_29}.
	 */
	CODE_26("26"),
	
	/**
	 * Automated Payment
	 */
	CODE_27("27"),
	
	/**
	 * Prenotification of Demand Debit Authorization (non-dollar).
	 */
	CODE_28("28"),
	
	/**
	 * Zero dollar with remittance data (for {@link StandardEntryClassCode#CCD}
	 * and {@link StandardEntryClassCode#CTX} entries only).
	 */
	CODE_29("29"),
	
	/*
	 * Savings Account Credit Records
	 */
	
	/**
	 * Reserved.
	 */
	RESERVED_30("30"),
	
	/**
	 * Automated Return or Notification of Change for original transaction 
	 * code {@link #CODE_32}, {@link #CODE_33} or {@link #CODE_34}.
	 */
	CODE_31("31"),
	
	/**
	 * Automated Deposit
	 */
	CODE_32("32"),
	
	/**
	 * Prenotification of Savings Credit Authorization; Death Notification 
	 * (non-dollar); Automated Enrollment Entry (non-dollar).
	 */
	CODE_33("33"),
	
	/**
	 * Zero dollar with remittance data (for {@link StandardEntryClassCode#CCD}
	 * and {@link StandardEntryClassCode#CTX} entries only); Acknowledgement 
	 * Entries ({@link StandardEntryClassCode#ACK} and 
	 * {@link StandardEntryClassCode#ATX} entries only).
	 */
	CODE_34("34"),
	
	/*
	 * Savings Account Debit Records
	 */
	
	/**
	 * Reserved.
	 */
	RESERVED_35("35"),
	
	/**
	 * Automated Return or Notification of Change for original transaction 
	 * code {@link #CODE_37}, {@link #CODE_38} or {@link #CODE_39}.
	 */
	CODE_36("36"),
	
	/**
	 * Automated Payment
	 */
	CODE_37("37"),
	
	/**
	 * Prenotification of Savings Debit Authorization (non-dollar)
	 */
	CODE_38("38"),
	
	/**
	 * Zero dollar with remittance data (for {@link StandardEntryClassCode#CCD}
	 * and {@link StandardEntryClassCode#CTX} entries only).
	 */
	CODE_39("39"),
	
	/*
	 * Financial Institution General Ledger Credit Records
	 */
	
	/**
	 * Automated Return or Notification of Change for originatl transaction 
	 * {@link #CODE_42}, {@link #CODE_43}, or {@link #CODE_44};
	 */
	CODE_41("41"),
	
	/**
	 * Automated General Ledger Deposit (Credit).
	 */
	CODE_42("42"),
	
	/**
	 * Prenotification of General Ledger Credit Authorization (non-dollar).
	 */
	CODE_43("43"),
	
	/**
	 * Zero dollar with remittance data (for {@link StandardEntryClassCode#CCD}
	 * and {@link StandardEntryClassCode#CTX} entries only).
	 */
	CODE_44("44"),
	
	/*
	 * Financial Institution General Ledger Debit Records
	 */

	/**
	 * Automated Return or Notification of Change for original transaction 
	 * {@link #CODE_47}, {@link #CODE_48} or {@link #CODE_49}.
	 */
	CODE_46("46"),
	
	/**
	 * Automated General Ledger Payment (Debit).
	 */
	CODE_47("47"),
	
	/**
	 * Prenotification of General Ledger Debit Authorization (non-dollar).
	 */
	CODE_48("48"),
	
	/**
	 * Zero dollar with remittance data (for {@link StandardEntryClassCode#CCD}
	 * and {@link StandardEntryClassCode#CTX} entries only).
	 */
	CODE_49("49"),
	
	/*
	 * Loan Account Credit Records
	 */
	
	/**
	 * Automated Return or Notification of Change for original transaction 
	 * {@link #CODE_52}, {@link #CODE_53}, or {@link #CODE_54}.
	 */
	CODE_51("51"),
	
	/**
	 * Automated Loan Account Deposit (Credit).
	 */
	CODE_52("52"),
	
	/**
	 * Prenotification of Loan Account Credit Authorization (non-dollar).
	 */
	CODE_53("53"),
	
	/**
	 * Zero dollar with remittance data (for {@link StandardEntryClassCode#CCD}
	 * and {@link StandardEntryClassCode#CTX} entries only).
	 */
	CODE_54("54"),
	
	/*
	 * Loan Account Debit Records (for Reversals Only)
	 */
	
	/**
	 * Automated Loan Account Debit (Reversals Only).
	 */
	CODE_55("55"),
	
	/**
	 * Automated Return or Notification of Change for original transaction 
	 * {@link #CODE_55}.
	 */
	CODE_56("56"),

	/*
	 * Automated Accounting Records (for use in ADV files only).
	 */
	
	/**
	 * Credit for ACH debits originated.
	 */
	CODE_81("81"),
	
	/**
	 * Debit for ACH debits originated.
	 */
	CODE_82("82"),
	
	/**
	 * Credit for ACH credits received.
	 */
	CODE_83("83"),
	
	/**
	 * Debit for ACH debits received.
	 */
	CODE_84("84"),
	
	/**
	 * Credit for ACH credits in rejected balances.
	 */
	CODE_85("85"),
	
	/**
	 * Debit for ACH debits in rejected balances.
	 */
	CODE_86("86"),
	
	/**
	 * Summary credit for respondent ACH activity.
	 */
	CODE_87("87"),
	
	/**
	 * Summary debit for respondent ACH activity. 
	 */
	CODE_88("88");
	
	private final String value;

	private TransactionCode(String value) {
		this.value = value;
	}

	public String getValue() {
		return value;
	}
}
import java.io.StringReader;
import java.io.StringWriter;
import org.junit.Assert;
import org.junit.Test;
import com.moss.ach.file.AchBatch;
import com.moss.ach.file.AchFile;
import com.moss.ach.file.AchFileReader;
import com.moss.ach.file.AchFileWriter;
import com.moss.ach.file.FileIdModifier;
import com.moss.ach.file.OriginatorStatusCode;
import com.moss.ach.file.PPDEntry;
import com.moss.ach.file.PPDEntryAddenda;
import com.moss.ach.file.ServiceClassCode;
import com.moss.ach.file.SimpleDate;
import com.moss.ach.file.SimpleTime;
import com.moss.ach.file.StandardEntryClassCode;
import com.moss.ach.file.TraceNumber;
import com.moss.ach.file.TransactionCode;
import com.moss.usbanknumbers.RoutingNumber;
/**
 * Copyright (C) 2013, Moss Computing Inc.
 *
 * This file is part of ach.
 *
 * ach is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * ach is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with ach; see the file COPYING.  If not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 *
 * Linking this library statically or dynamically with other modules is
 * making a combined work based on this library.  Thus, the terms and
 * conditions of the GNU General Public License cover the whole
 * combination.
 *
 * As a special exception, the copyright holders of this library give you
 * permission to link this library with independent modules to produce an
 * executable, regardless of the license terms of these independent
 * modules, and to copy and distribute the resulting executable under
 * terms of your choice, provided that you also meet, for each linked
 * independent module, the terms and conditions of the license of that
 * module.  An independent module is a module which is not derived from
 * or based on this library.  If you modify this library, you may extend
 * this exception to your version of the library, but you are not
 * obligated to do so.  If you do not wish to do so, delete this
 * exception statement from your version.
 */
package com.moss.ach.file;




public class TestPPDAddenda {
	
	@Test
	public void ppdWriteRead() throws Exception {
		
		AchFile file = new AchFile();
		
		file.creationDate = new SimpleDate(8, 7, 29);
		file.creationTime = new SimpleTime(15, 11);