diff --git a/BasicStockQuoteApplication.java b/BasicStockQuoteApplication.java new file mode 100644 index 0000000..84409c7 --- /dev/null +++ b/BasicStockQuoteApplication.java @@ -0,0 +1,143 @@ +package com.origamisoftware.teach.advanced.apps.stockquote; + +import com.origamisoftware.teach.advanced.model.StockQuery; +import com.origamisoftware.teach.advanced.model.StockQuote; +import com.origamisoftware.teach.advanced.services.StockService; +import com.origamisoftware.teach.advanced.services.StockServiceException; +import com.origamisoftware.teach.advanced.services.StockServiceFactory; + +import java.text.ParseException; +import java.util.List; + +/** + * A simple application that shows the StockService in action. + */ +public class BasicStockQuoteApplication { + + private StockService stockService; + + // an example of how to use enum - not part of assignment 3 but useful for assignment 4 + + /** + * An enumeration that indicates how the program terminates (ends) + */ + private enum ProgramTerminationStatusEnum { + + // for now, we just have normal or abnormal but could more specific ones as needed. + NORMAL(0), + ABNORMAL(-1); + + // when the program exits, this value will be reported to underlying OS + private int statusCode; + + /** + * Create a new ProgramTerminationStatusEnum + * + * @param statusCodeValue the value to return the OS. A value of 0 + * indicates success or normal termination. + * non 0 numbers indicate abnormal termination. + */ + private ProgramTerminationStatusEnum(int statusCodeValue) { + this.statusCode = statusCodeValue; + } + + /** + * @return The value sent to OS when the program ends. + */ + private int getStatusCode() { + return statusCode; + } + } + + /** + * Create a new Application. + * + * @param stockService the StockService this application instance should use for + * stock queries. + *

+ * NOTE: this is a example of Dependency Injection in action. + */ + public BasicStockQuoteApplication(StockService stockService) { + this.stockService = stockService; + } + + /** + * Given a stockQuery get back a the info about the stock to display to th user. + * + * @param stockQuery the stock to get data for. + * @return a String with the stock data in it. + * @throws StockServiceException If data about the stock can't be retrieved. This is a + * fatal error. + */ + public String displayStockQuotes(StockQuery stockQuery) throws StockServiceException { + StringBuilder stringBuilder = new StringBuilder(); + + List stockQuotes = + stockService.getQuote(stockQuery.getSymbol(), stockQuery.getFrom(), stockQuery.getUntil()); + + stringBuilder.append("Stock quotes for: " + stockQuery.getSymbol() + "\n"); + for (StockQuote stockQuote : stockQuotes) { + stringBuilder.append(stockQuote.toString()); + } + + return stringBuilder.toString(); + } + + /** + * Terminate the application. + * + * @param statusCode an enum value that indicates if the program terminated ok or not. + * @param diagnosticMessage A message to display to the user when the program ends. + * This should be an error message in the case of abnormal termination + *

+ * NOTE: This is an example of DRY in action. + * A program should only have one exit point. This makes it easy to do any clean up + * operations before a program quits from just one place in the code. + * It also makes for a consistent user experience. + */ + private static void exit(ProgramTerminationStatusEnum statusCode, String diagnosticMessage) { + if (statusCode == ProgramTerminationStatusEnum.NORMAL) { + System.out.println(diagnosticMessage); + } else if (statusCode == ProgramTerminationStatusEnum.ABNORMAL) { + System.err.println(diagnosticMessage); + } else { + throw new IllegalStateException("Unknown ProgramTerminationStatusEnum."); + } + System.exit(statusCode.getStatusCode()); + } + + /** + * Run the StockTicker application. + *

+ * When invoking the program supply one ore more stock symbols. + * + * @param args one or more stock symbols + */ + public static void main(String[] args) { + // be optimistic init to positive values + ProgramTerminationStatusEnum exitStatus = ProgramTerminationStatusEnum.NORMAL; + String programTerminationMessage = "Normal program termination."; + if (args.length != 3) { + exit(ProgramTerminationStatusEnum.ABNORMAL, + "Please supply 3 arguments a stock symbol, a start date (MM/DD/YYYY) and end date (MM/DD/YYYY)"); + } + try { + + StockQuery stockQuery = new StockQuery(args[0], args[1], args[2]); + StockService stockService = StockServiceFactory.getInstance(); + BasicStockQuoteApplication basicStockQuoteApplication = + new BasicStockQuoteApplication(stockService); + basicStockQuoteApplication.displayStockQuotes(stockQuery); + + } catch (ParseException e) { + exitStatus = ProgramTerminationStatusEnum.ABNORMAL; + programTerminationMessage = "Invalid date data: " + e.getMessage(); + } catch (StockServiceException e) { + exitStatus = ProgramTerminationStatusEnum.ABNORMAL; + programTerminationMessage = "StockService failed: " + e.getMessage(); + } + + exit(exitStatus, programTerminationMessage); + System.out.println("Oops could not parse a date"); + } +} diff --git a/BasicStockQuoteApplicationTest.java b/BasicStockQuoteApplicationTest.java new file mode 100644 index 0000000..fa3e3f2 --- /dev/null +++ b/BasicStockQuoteApplicationTest.java @@ -0,0 +1,68 @@ +package com.origamisoftware.teach.advanced.apps.stockquote; + +import com.origamisoftware.teach.advanced.model.StockQuery; +import com.origamisoftware.teach.advanced.model.StockQuote; +import com.origamisoftware.teach.advanced.services.StockService; +import com.origamisoftware.teach.advanced.services.StockServiceException; +import org.junit.Before; +import org.junit.Test; + +import java.math.BigDecimal; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Tests for BasicStockQuoteApplication + */ +public class BasicStockQuoteApplicationTest { + + private BasicStockQuoteApplication basicStockQuoteApplication; + private StockService stockServiceMock; + + @Before + public void setUp() { + stockServiceMock = mock(StockService.class); + } + + @Test + public void testValidConstruction() { + basicStockQuoteApplication = new BasicStockQuoteApplication(stockServiceMock); + assertNotNull("Basic construction works"); + } + + @Test + public void testDisplayResults() throws ParseException, StockServiceException { + basicStockQuoteApplication = new BasicStockQuoteApplication(stockServiceMock); + String symbol = "APPL"; + String from = "2011/10/29"; + String until = "2011/11/29"; + StockQuery stockQuery = new StockQuery(symbol, from, until); + + List stockQuotes = new ArrayList<>(); + StockQuote stockQuoteFromDate = new StockQuote(new BigDecimal(100), stockQuery.getFrom().getTime(), stockQuery.getSymbol()); + stockQuotes.add(stockQuoteFromDate); + StockQuote stockQuoteUntilDate = new StockQuote(new BigDecimal(100), stockQuery.getUntil().getTime(), stockQuery.getSymbol()); + stockQuotes.add(stockQuoteUntilDate); + + when(stockServiceMock.getQuote(any(String.class), any(Calendar.class), any(Calendar.class))).thenReturn(stockQuotes); + + String output = basicStockQuoteApplication.displayStockQuotes(stockQuery); + assertTrue("make sure symbol appears in output", output.contains(symbol)); + assertTrue("make sure from date appears in output", output.contains(from)); + assertTrue("make sure until date in output", output.contains(until)); + + } + + @Test(expected = NullPointerException.class) + public void testMainNegative() { + BasicStockQuoteApplication.main(null); + } +} diff --git a/SimpleStockService.java b/SimpleStockService.java new file mode 100644 index 0000000..1cc89c6 --- /dev/null +++ b/SimpleStockService.java @@ -0,0 +1,55 @@ +package com.origamisoftware.teach.advanced.services; + +import com.origamisoftware.teach.advanced.model.StockQuote; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; + +/** + * An implementation of the StockService that returns hard coded data. + */ +public class SimpleStockService implements StockService { + + /** + * Return the current price for a share of stock for the given symbol + * + * @param symbol the stock symbol of the company you want a quote for. + * e.g. APPL for APPLE + * @return a BigDecimal instance + * @throws StockServiceException if using the service generates an exception. + * If this happens, trying the service may work, depending on the actual cause of the + * error. + */ + @Override + public StockQuote getQuote(String symbol) { + // a dead simple implementation. + return new StockQuote(new BigDecimal(100), Calendar.getInstance().getTime(), symbol); + } + + /** + * Get a historical list of stock quotes for the provide symbol + * + * @param symbol the stock symbol to search for + * @param from the date of the first stock quote + * @param until the date of the last stock quote + * @return a list of StockQuote instances + * @throws StockServiceException if using the service generates an exception. + * If this happens, trying the service may work, depending on the actual cause of the + * error. + */ + @Override + public List getQuote(String symbol, Calendar from, Calendar until) { + // a dead simple implementation. + List stockQuotes = new ArrayList<>(); + Date aDay = from.getTime(); + while (until.after(aDay)) { + stockQuotes.add(new StockQuote(new BigDecimal(100), aDay, symbol)); + from.add(Calendar.DAY_OF_YEAR, 1); + aDay = from.getTime(); + } + return stockQuotes; + } +} diff --git a/StockQuote.java b/StockQuote.java new file mode 100644 index 0000000..d856e67 --- /dev/null +++ b/StockQuote.java @@ -0,0 +1,58 @@ +package com.origamisoftware.teach.advanced.model; + +import java.math.BigDecimal; +import java.util.Calendar; +import java.util.Date; + +/** + * A container class that contains stock data. + */ +public class StockQuote extends StockData { + + private BigDecimal price; + private Date date; + private String symbol; + /** + * Create a new instance of a StockQuote. + * + * @param price the share price for the given date + * @param date the date of the share price + * @param symbol the stock symbol. + */ + public StockQuote(BigDecimal price, Date date, String symbol) { + super(); + this.price = price; + this.date = date; + this.symbol = symbol; + } + /** + * @return Get the share price for the given date. + */ + public BigDecimal getPrice() { + return price; + } + + /** + * @return The date of the share price + */ + public Date getDate() { + return date; + } + + /** + * @return The stock symbol. + */ + public String getSymbol() { + return symbol; + } + + @Override + public String toString() { + String dateString = simpleDateFormat.format(date); + return "StockQuote{" + + "price=" + price + + ", date=" + dateString + + ", symbol='" + symbol + '\'' + + '}'; + } +} diff --git a/StockQuoteTest.java b/StockQuoteTest.java new file mode 100644 index 0000000..ad47fb1 --- /dev/null +++ b/StockQuoteTest.java @@ -0,0 +1,44 @@ +package com.origamisoftware.teach.advanced.model; + +import org.junit.Before; +import org.junit.Test; + +import java.math.BigDecimal; +import java.util.Calendar; +import java.util.Date; + +import static org.junit.Assert.assertEquals; + +/** + * JUnit test for StockQuote class + */ +public class StockQuoteTest { + + private BigDecimal price; + private Date date; + private String symbol; + private StockQuote stockQuote; + + @Before + public void setUp() { + price = new BigDecimal(100); + date = Calendar.getInstance().getTime(); + symbol = "APPL"; + stockQuote = new StockQuote(price, date, symbol); + } + + @Test + public void testGetPrice() { + assertEquals("Share price is correct", price, stockQuote.getPrice()); + } + + @Test + public void testGetDate() { + assertEquals("Share date is correct", date, stockQuote.getDate()); + } + + @Test + public void testGetSymbol() { + assertEquals("Symbol is correct", symbol, stockQuote.getSymbol()); + } +} diff --git a/StockService.java b/StockService.java new file mode 100644 index 0000000..d702731 --- /dev/null +++ b/StockService.java @@ -0,0 +1,40 @@ +package com.origamisoftware.teach.advanced.services; + +import com.origamisoftware.teach.advanced.model.StockQuote; + +import java.util.Calendar; +import java.util.List; + +/** + * This API describes how to get stock data from an external resource. + */ +public interface StockService { + + + /** + * Return the current price for a share of stock for the given symbol + * + * @param symbol the stock symbol of the company you want a quote for. + * e.g. APPL for APPLE + * @return a BigDecimal instance + * @throws StockServiceException if using the service generates an exception. + * If this happens, trying the service may work, depending on the actual cause of the + * error. + */ + StockQuote getQuote(String symbol) throws StockServiceException; + + /** + * Get a historical list of stock quotes for the provide symbol + * + * @param symbol the stock symbol to search for + * @param from the date of the first stock quote + * @param until the date of the last stock quote + * @return a list of StockQuote instances + * @throws StockServiceException if using the service generates an exception. + * If this happens, trying the service may work, depending on the actual cause of the + * error. + */ + List getQuote(String symbol, Calendar from, Calendar until) throws StockServiceException; + +} + diff --git a/StockServiceFactory.java b/StockServiceFactory.java new file mode 100644 index 0000000..1bc4e47 --- /dev/null +++ b/StockServiceFactory.java @@ -0,0 +1,45 @@ +package com.origamisoftware.teach.advanced.services; + +import com.origamisoftware.teach.advanced.model.StockQuote; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; + +/** + * A factory that returns a StockService instance. + */ +public class StockServiceFactory { + + /** + * Prevent instantiations + */ + private StockServiceFactory() {} + + /** + * + * @return get a StockService instance + */ + public static DatabaseStockService getInstance() { + return new DatabaseStockService() { + @Override + public StockQuote getQuote(String symbol) throws StockServiceException { + return new StockQuote(new BigDecimal(100), Calendar.getInstance().getTime(), symbol); + } + + @Override + public List getQuote(String symbol, Calendar from, Calendar until) throws StockServiceException { + List stockQuotes = new ArrayList<>(); + Date aDay = from.getTime(); + while (until.after(aDay)) { + stockQuotes.add(new StockQuote(new BigDecimal(100),aDay,symbol)); + from.add(Calendar.DAY_OF_YEAR, 1); + aDay = from.getTime(); + } + return stockQuotes; } + }; + } + +} diff --git a/StockServiceFactoryTest.java b/StockServiceFactoryTest.java new file mode 100644 index 0000000..fd605e9 --- /dev/null +++ b/StockServiceFactoryTest.java @@ -0,0 +1,17 @@ +package com.origamisoftware.teach.advanced.services; + +import org.junit.Test; + +import static org.junit.Assert.assertNotNull; + +/** + * JUnit test for StockServiceFactory + */ +public class StockServiceFactoryTest { + + @Test + public void testGetInstance() { + StockService stockService = StockServiceFactory.getInstance(); + assertNotNull(stockService); + } +}