package hirondelle.stocks.quotes;
import java.text.StringCharacterIterator;
import java.util.*;
import java.util.logging.*;
import java.math.BigDecimal;
import hirondelle.stocks.util.Util;
import hirondelle.stocks.util.HashCodeUtil;
import hirondelle.stocks.util.EqualsUtil;;
public final class Stock implements Comparable<Stock> {
public Stock (
String aName,
String aTicker,
Exchange aExchange,
Integer aNumShares,
BigDecimal aAveragePrice
) {
fName = aName;
fTicker = aTicker;
fExchange = aExchange;
fNumShares = aNumShares;
fAveragePrice = aAveragePrice;
validateState();
}
public Stock valueOf(String aStockText) {
StringTokenizer parser = new StringTokenizer(aStockText, FIELD_DELIMITER);
try {
String ticker = parser.nextToken();
String name = parser.nextToken();
Exchange exchange = Exchange.valueFrom(parser.nextToken());
Integer numShares = Integer.valueOf(parser.nextToken());
BigDecimal avgPrice = new BigDecimal(parser.nextToken());
return new Stock(name, ticker, exchange, numShares, avgPrice);
}
catch (NoSuchElementException ex){
fLogger.severe("Cannot parse into Stock object: \"" + aStockText + "\"");
throw ex;
}
}
@Override public String toString() {
StringBuilder result = new StringBuilder();
result.append(fTicker).append(FIELD_DELIMITER);
result.append(fName).append(FIELD_DELIMITER);
result.append(fExchange).append(FIELD_DELIMITER);
result.append(fNumShares).append(FIELD_DELIMITER);
result.append(fAveragePrice);
return result.toString();
}
public static boolean isValidInput(
List<String> aErrorMessages,
String aName,
String aTicker,
Exchange aExchange,
Integer aNumShares,
BigDecimal aAvgPrice
){
if (aErrorMessages == null) {
throw new IllegalArgumentException("List for error messages must be non-null.");
}
if ( !aErrorMessages.isEmpty() ) {
throw new IllegalArgumentException("List for error messages must be initially empty.");
}
if ( ! isValidName(aName) ) {
aErrorMessages.add("Name must have content.");
}
if ( ! isValidTicker(aTicker) ) {
String message =
"Ticker symbols must have 1..20 characters, " +
"which are only letters, periods, underscores, and ^."
;
aErrorMessages.add(message);
}
if ( ! isValidExchange(aExchange) ) {
aErrorMessages.add("An exchange must be selected.");
}
if ( ! isValidNumShares(aNumShares) ) {
aErrorMessages.add("Quantity must not be null.");
}
if ( ! isValidAveragePrice(aAvgPrice) ) {
aErrorMessages.add("Average price must be zero or positive.");
}
return aErrorMessages.isEmpty();
}
public String getName() {
return fName;
}
public String getTicker() {
return fTicker;
}
public Exchange getExchange() {
return fExchange;
}
public Integer getNumShares() {
return fNumShares;
}
public BigDecimal getAveragePrice() {
return fAveragePrice;
}
public BigDecimal getBookValue(){
BigDecimal numShares = new BigDecimal(getNumShares().toString());
return numShares.multiply(getAveragePrice()) ;
}
public boolean isIndex(){
return fTicker.startsWith("^");
}
@Override public boolean equals(Object aThat) {
if ( this == aThat ) return true;
if ( !(aThat instanceof Stock) ) return false;
Stock that = (Stock)aThat;
return
EqualsUtil.areEqual(this.fName, that.fName) &&
EqualsUtil.areEqual(this.fTicker, that.fTicker) &&
EqualsUtil.areEqual(this.fExchange, that.fExchange) &&
EqualsUtil.areEqual(this.fNumShares, that.fNumShares) &&
EqualsUtil.areEqual(this.fAveragePrice, that.fAveragePrice)
;
}
@Override public int hashCode() {
int result = HashCodeUtil.SEED;
result = HashCodeUtil.hash(result, fName);
result = HashCodeUtil.hash(result, fTicker);
result = HashCodeUtil.hash(result, fExchange);
result = HashCodeUtil.hash(result, fNumShares);
result = HashCodeUtil.hash(result, fAveragePrice);
return result;
}
@Override public int compareTo (Stock aThat) {
final int BEFORE = -1;
final int EQUAL = 0;
final int AFTER = 1;
if ( this == aThat ) return EQUAL;
if (!this.isIndex() && aThat.isIndex()) return AFTER;
if (this.isIndex() && !aThat.isIndex()) return BEFORE;
int comparison = this.fName.compareTo(aThat.fName);
if ( comparison != EQUAL ) return comparison;
comparison = this.fTicker.compareTo(aThat.fTicker);
if ( comparison != EQUAL ) return comparison;
comparison = this.fExchange.compareTo(aThat.fExchange);
if ( comparison != EQUAL ) return comparison;
comparison = this.fNumShares.compareTo(aThat.fNumShares);
if ( comparison != EQUAL ) return comparison;
comparison = this.fAveragePrice.compareTo(aThat.fAveragePrice);
if ( comparison != EQUAL ) return comparison;
assert this.equals(aThat) : "compareTo inconsistent with equals.";
return EQUAL;
}
private final String fName;
private final String fTicker;
private final Exchange fExchange;
private final Integer fNumShares;
private final BigDecimal fAveragePrice;
private static final Logger fLogger = Util.getLogger(Stock.class);
private static final String FIELD_DELIMITER = ":";
private void validateState(){
boolean hasValidState =
isValidName(fName) &&
isValidTicker(fTicker) &&
isValidExchange(fExchange) &&
isValidNumShares(fNumShares) &&
isValidAveragePrice(fAveragePrice)
;
if ( ! hasValidState ) {
throw new IllegalArgumentException(this.toString());
}
}
private static boolean isValidName(String aName) {
return Util.textHasContent(aName);
}
private static boolean isValidTicker(String aName){
if ( aName == null ) return false;
String name = aName.trim();
if ( name.length() == 0 || name.length()>20 ) return false;
boolean result = true;
StringCharacterIterator iterator = new StringCharacterIterator(aName);
char sCharacter = iterator.current();
while (sCharacter != StringCharacterIterator.DONE ){
if (
Character.isLetter(sCharacter) ||
sCharacter == '.' ||
sCharacter == '_' ||
sCharacter == '^'
){
}
else {
result = false;
}
sCharacter = iterator.next();
}
return result;
}
private static boolean isValidExchange(Exchange aExchange) {
return aExchange != null;
}
private static boolean isValidNumShares(Integer aNumShares){
return aNumShares != null;
}
private static boolean isValidAveragePrice( BigDecimal aAvgPrice) {
return aAvgPrice != null && !(aAvgPrice.doubleValue()<0);
}
}