package hirondelle.stocks.quotes;
import java.util.*;
import java.util.logging.*;
import java.io.*;
import java.net.*;
import java.math.BigDecimal;
import javax.swing.ProgressMonitorInputStream;
import hirondelle.stocks.util.Consts;
import hirondelle.stocks.util.Util;
import hirondelle.stocks.util.Args;
import hirondelle.stocks.util.DataAccessException;
public final class QuotesDAO {
public QuotesDAO(UseMonitor aUseMonitor, Collection<Stock> aStocks){
Args.checkForNull(aStocks);
fStocks = aStocks;
fUseMonitor = aUseMonitor.getValue();
}
public enum UseMonitor {
TRUE(true),
FALSE(false);
boolean getValue() {
return fToggle;
}
private final boolean fToggle;
private UseMonitor (boolean aToggle) {
fToggle = aToggle;
}
}
public List<Quote> getQuotes() throws DataAccessException {
if (OFF_LINE) {
System.out.println("HARD - fixed quote prices");
return getStaticQuotes(); }
if (fStocks.size() == 0) return Collections.emptyList();
URL yahooUrl = null;
try {
yahooUrl = new URL(getYahooUrlText());
}
catch (MalformedURLException ex){
fLogger.severe("Cannot create Yahoo Url using: " + getYahooUrlText());
}
List<Quote> queryResult = getQueryResult(yahooUrl);
return queryResult;
}
private static final boolean OFF_LINE = Boolean.getBoolean("offline");
private Collection<Stock> fStocks;
private boolean fUseMonitor;
private static final Logger fLogger =
Logger.getLogger(QuotesDAO.class.getPackage().getName())
;
private static final String fYAHOO_URL_START = "http://quote.yahoo.com/d/quotes.csv?s=";
private static final String fYAHOO_URL_END = "&f=sl1d1t1c1ohgv&e=.csv";
private BigDecimal ZERO = Consts.ZERO_MONEY_WITH_DECIMAL;
private int ROUND_MODE = Consts.MONEY_ROUNDING_STYLE;
private int DECIMALS = Consts.MONEY_DECIMAL_PLACES;
private String getYahooUrlText(){
StringBuilder result = new StringBuilder(fYAHOO_URL_START);
Iterator<Stock> stocksIter = fStocks.iterator();
while (stocksIter.hasNext()){
Stock stock = stocksIter.next();
result.append(getTickerForYahooUrl(stock));
if ( stocksIter.hasNext() ) {
result.append(Consts.COMMA);
}
}
result.append(fYAHOO_URL_END);
return result.toString();
}
private String getTickerForYahooUrl( Stock aStock ) {
StringBuilder result = new StringBuilder(aStock.getTicker ());
String exchangeSuffix = aStock.getExchange().getTickerSuffix();
if (Util.textHasContent(exchangeSuffix)){
result.append(".");
result.append(exchangeSuffix);
}
return result.toString();
}
private List<Quote> getQueryResult(URL aHttpRequest) throws DataAccessException {
List<Quote> result = new ArrayList<>();
List<String> lines = getLines(aHttpRequest);
int rowIdx = 0;
for(Stock stock : fStocks){
result.add(getQuote(stock, lines.get(rowIdx)));
++rowIdx;
}
return result;
}
private List<String> getLines(URL aHttpRequest) throws DataAccessException {
List<String> result = new ArrayList<>();
try {
InputStream input = null;
LineNumberReader htmlReader = null;
try {
if ( fUseMonitor ){
input = new ProgressMonitorInputStream(
null, "Fetching.", aHttpRequest.openStream()
);
}
else {
input = aHttpRequest.openStream();
}
htmlReader = new LineNumberReader(new InputStreamReader(input));
String line = null;
while ( (line = htmlReader.readLine())!= null ) {
result.add(line);
}
}
finally {
if (htmlReader != null) htmlReader.close();
if (input != null) input.close();
}
}
catch(IOException ex) {
throw new DataAccessException("Please connect to the Web. Cannot access Yahoo.", ex);
}
return result;
}
private Quote getQuote(Stock aStock, String aQueryResultLine) {
StringTokenizer parser = new StringTokenizer(aQueryResultLine, Consts.COMMA);
String ticker = parseTicker(parser.nextToken());
if ( !ticker.startsWith(aStock.getTicker()) ) {
fLogger.severe(
"Invalid ticker-exchange? Expected line for " + aStock.getTicker() +
", but received: "+ aQueryResultLine
);
}
BigDecimal roundedPrice = parsePrice(parser.nextToken());
roundedPrice = roundedPrice.setScale(
Consts.MONEY_DECIMAL_PLACES, ROUND_MODE
);
parser.nextToken();
parser.nextToken();
BigDecimal roundedChange = parsePriceChange(parser.nextToken());
roundedChange = roundedChange.setScale(
Consts.MONEY_DECIMAL_PLACES, Consts.MONEY_ROUNDING_STYLE
);
return new Quote(aStock, roundedPrice, roundedChange);
}
private BigDecimal parsePrice(String aPrice) {
BigDecimal result = ZERO;
BigDecimal dollars = ZERO;
BigDecimal numerator = ZERO;
BigDecimal denominator = ZERO;
aPrice = aPrice.trim();
if (aPrice.indexOf('/') != -1) {
StringTokenizer parser = new StringTokenizer(aPrice);
while (parser.hasMoreElements()) {
String token = parser.nextToken();
if (token.indexOf('/') == -1) {
dollars = new BigDecimal(token);
}
else {
StringTokenizer fractionParser = new StringTokenizer(token,"/");
numerator = new BigDecimal(fractionParser.nextToken());
denominator = new BigDecimal(fractionParser.nextToken());
}
}
if (!denominator.equals(ZERO)){
BigDecimal cents =
numerator.divide(denominator, ROUND_MODE).setScale(DECIMALS, ROUND_MODE)
;
result = dollars.add(cents);
}
else {
result = dollars;
}
}
else {
result = new BigDecimal(aPrice);
}
return result;
}
private BigDecimal parsePriceChange(String aPriceChange) {
BigDecimal result = ZERO;
BigDecimal sign = ZERO;
aPriceChange = aPriceChange.trim();
if ( aPriceChange.startsWith(Consts.PLUS_SIGN) ) {
sign = new BigDecimal("1");
}
else if ( aPriceChange.startsWith(Consts.NEGATIVE_SIGN) ) {
sign = new BigDecimal("-1");
}
else {
return result;
}
String magnitudeOfPriceChange = aPriceChange.substring(1);
result = sign.multiply(parsePrice(magnitudeOfPriceChange));
return result;
}
private String parseTicker(String aTicker){
return aTicker.replaceAll(Consts.DOUBLE_QUOTE, Consts.EMPTY_STRING);
}
private List<Quote> getStaticQuotes(){
java.util.List<Quote> result = new ArrayList<>();
for(Stock stock : fStocks){
Quote quote = new Quote(stock, new BigDecimal("10.00"), new BigDecimal("-0.75"));
result.add(quote );
}
return result;
}
}