diff --git a/README.md b/README.md index e161ff2..2fea49d 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ Author: Benny Leung J-WatchList is a simple stock market watch list tracker. ## Dependencies +External libarary: + Build deps: - gradle diff --git a/src/main/data/ListOfWatchList.java b/src/main/data/ListOfWatchList.java new file mode 100644 index 0000000..387a40e --- /dev/null +++ b/src/main/data/ListOfWatchList.java @@ -0,0 +1,31 @@ +package data; + +import java.util.ArrayList; + +//Singleton pattern from https://www.tutorialspoint.com/java/java_using_singleton.htm +public class ListOfWatchList { + private static ListOfWatchList lowl = new ListOfWatchList(); + private ArrayList wlists; + + private ListOfWatchList() { + wlists = new ArrayList(); + } + + public static ListOfWatchList getList() { + return lowl; + } + + public void addWatchList(WatchList wlist) { + if (!(wlists.contains(wlist))) { + wlists.add(wlist); + } + } + + public void delWatchList(WatchList wlist) { + wlists.remove(wlist); + } + + public WatchList getWatchList(int index) { + return (WatchList) wlists.get(index); + } +} diff --git a/src/main/data/StockEntry.java b/src/main/data/StockEntry.java index 69d49bc..714266b 100644 --- a/src/main/data/StockEntry.java +++ b/src/main/data/StockEntry.java @@ -1,5 +1,7 @@ package data; +import java.util.Objects; + public class StockEntry { private String identifier; private StockType stype; @@ -24,4 +26,25 @@ public class StockEntry { public String getID() { return this.identifier; } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof StockEntry)) { + return false; + } + StockEntry tmp = (StockEntry) obj; + if (tmp.getID().equals(identifier) + && tmp.getTypeName().equals(stype.getName())) { + return true; + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(identifier, stype.getName()); + } } diff --git a/src/main/data/StockType.java b/src/main/data/StockType.java index 2605945..6cf1ec4 100644 --- a/src/main/data/StockType.java +++ b/src/main/data/StockType.java @@ -45,7 +45,6 @@ public abstract class StockType { } //Effects: return name of type - //Require: working sources public String getName() { return this.name; } diff --git a/src/main/data/StypeMap.java b/src/main/data/StypeMap.java index 287adde..53fac39 100644 --- a/src/main/data/StypeMap.java +++ b/src/main/data/StypeMap.java @@ -13,23 +13,41 @@ import data.Nasdaq; //Contains one instance of all supported //StockType +//Singleton from https://www.tutorialspoint.com/java/java_using_singleton.htm public class StypeMap { - private HashMap smap; + private static HashMap smap; + private static StypeMap stypemap = new StypeMap(); // Effects: smap is initialized // with supported StockType - public StypeMap() { + private StypeMap() { smap = new HashMap(); - smap.put("Nyse", new Nyse()); - smap.put("Nasdaq", new Nasdaq()); - //smap.put("Jpx", new Jpx()); - //smap.put("Sse", new Sse()); - //smap.put("Hkse", new Hkse()); + addStype(new Nyse()); + addStype(new Nasdaq()); + //addStype(new Jpx()); + //addStype(new Sse()); + //addStype(new Hkse()); } // Effects: Returns the StockType // Require: Valid typeString - public StockType getStype(String typeString) { - return smap.get(typeString); + public static StockType getStype(String typeString) { + return stypemap.getInstStype(typeString); + } + + private void addStype(StockType stype) { + smap.put(stype.getName(), stype); + } + + protected StockType getInstStype(String typeString) { + return this.smap.get(typeString); + } + + public static StypeMap getMap() { + return stypemap; + } + + protected HashMap getHMap() { + return smap; } } diff --git a/src/main/data/WatchList.java b/src/main/data/WatchList.java index 83c3893..b52fb44 100644 --- a/src/main/data/WatchList.java +++ b/src/main/data/WatchList.java @@ -8,31 +8,30 @@ import data.exceptions.*; public class WatchList implements Load,Save { private LinkedHashMap listdata; - private Main mainObj; public static final String DEFAULT_SAVEFILE = ".jwatch.list"; // Effects: List is empty // or loaded with save values - public WatchList(Main mainObj) { + public WatchList() { listdata = new LinkedHashMap(); - this.mainObj = mainObj; if (fileExists(DEFAULT_SAVEFILE)) { load(""); } + ListOfWatchList.getList().addWatchList(this); } // Debug constructor - public WatchList(Main mainObj, boolean debug) { + public WatchList(boolean debug) { listdata = new LinkedHashMap(); - this.mainObj = mainObj; } // Effects: Add an entry with key==target // (XXX For now, only Nyse) // Modifies: this.listdata // Requires: target doesn't already exists - public void addStock(String target, StockType stype) { + public void addStock(String target) { //The only implementation yet + StockType stype = StypeMap.getStype("NYSE"); this.listdata.put(target, new StockEntry(stype, target)); } @@ -96,7 +95,7 @@ public class WatchList implements Load,Save { FileReader freader = new FileReader(filename); BufferedReader breader = new BufferedReader(freader); while ((sss = breader.readLine()) != null) { - mainObj.addWatchStock(sss); + addStock(sss); System.out.println("Imported: " + sss); } breader.close(); diff --git a/src/main/ui/IfaceFactory.java b/src/main/ui/IfaceFactory.java index 54e555f..f5e8933 100644 --- a/src/main/ui/IfaceFactory.java +++ b/src/main/ui/IfaceFactory.java @@ -5,9 +5,9 @@ import ui.Tui; public class IfaceFactory { // Produce Iface given the config - public static Iface getIface(Main mainobj) { + public static Iface getIface() { //uses Tui for now - return new Tui(mainobj); + return new Tui(); } //public static Iface getIface(Options iOpts) { // //XXX iOpts not ready diff --git a/src/main/ui/Main.java b/src/main/ui/Main.java index 74f159c..deab031 100644 --- a/src/main/ui/Main.java +++ b/src/main/ui/Main.java @@ -11,8 +11,6 @@ import data.exceptions.*; public class Main { private Iface iface; private Options allOptions; - private WatchList mainList; - private StypeMap stypeMap; //Constructor, not the java main public Main(String[] args) { @@ -21,11 +19,10 @@ public class Main { allOptions = new Options(); //parse args, uses allOptions.parseArgs(args); - stypeMap = new StypeMap(); - mainList = new WatchList(this); + WatchList mainList = new WatchList(); //initalize UI thread, options not ready //this.Iface = IfaceFactory.getIface(allOptions.getSection("ui")); - iface = IfaceFactory.getIface(this); + iface = IfaceFactory.getIface(); } //Constructor for testing @@ -33,48 +30,11 @@ public class Main { //init options, it will load defaults //from resource xml allOptions = new Options(); - stypeMap = new StypeMap(); - mainList = new WatchList(this, true); + WatchList mainList = new WatchList(); } // java main public static void main(String[] args) { new Main(args); } - - public WatchList getWatchList() { - //XXX volatile, threaded consideration - //instant/cached? - return this.mainList; - } - - // Consider moving all to watchlist - public void addWatchStock(String target) { - //XXX Concurrency not ready - // Should add runnable to executor - // nyse only for now - StockType stype = this.stypeMap.getStype("Nyse"); - mainList.addStock(target, stype); - } - - // Consider moving all to watchlist - public void delWatchStock(String target) { - //XXX Concurrency not ready - // Should add runnable to executor - boolean success = true; - try { - mainList.delStock(target); - success = true; - } catch (StockNotExistsException e) { - System.out.println("Stock: " + target + " doesn't exists"); - success = false; - } finally { - System.out.println(success ? "Deleted: " + target : "Failed to delete: " + target); - } - } - - // Consider moving all to watchlist - public void saveWatch(String file) { - mainList.save(file); - } } diff --git a/src/main/ui/Tui.java b/src/main/ui/Tui.java index f3cf217..dbbd7ca 100644 --- a/src/main/ui/Tui.java +++ b/src/main/ui/Tui.java @@ -7,7 +7,8 @@ import ui.Main; import data.Const; import data.WatchList; import data.StockEntry; - +import data.ListOfWatchList; +import data.exceptions.*; public class Tui implements Iface { private static final String SAVE_CURSOR = "\u001b[s"; @@ -16,12 +17,10 @@ public class Tui implements Iface { private int maxrow; private int maxcol; private BufferedReader stdin; - private Main main; - public Tui(Main main) { + public Tui() { stdin = new BufferedReader(new InputStreamReader(System.in)); getMax(); - this.main = main; //XXX Start ui thread demo(); } @@ -133,13 +132,14 @@ public class Tui implements Iface { demomenu(); cont = demoinput(); } - saveWatch(); + WatchList watch = ListOfWatchList.getList().getWatchList(0); + watch.save(""); System.out.println("Thank you for using " + Const.PROGRAM_NAME + "!"); } - public void printWatchList() { - WatchList watch = main.getWatchList(); + private void printWatchList() { + WatchList watch = ListOfWatchList.getList().getWatchList(0); Iterator watchit = watch.iterator(); while (watchit.hasNext()) { Map.Entry entry = (Map.Entry)watchit.next(); @@ -149,19 +149,25 @@ public class Tui implements Iface { } } - public void addWatch() { + private void addWatch() { System.out.print("Enter your stock number and press enter: "); String userin = getInputLine(); - main.addWatchStock(userin); + WatchList watch = ListOfWatchList.getList().getWatchList(0); + watch.addStock(userin); } - public void delWatch() { + private void delWatch() { + boolean success = false; System.out.print("Enter your stock number and press enter: "); String userin = getInputLine(); - main.delWatchStock(userin); - } - - public void saveWatch() { - main.saveWatch(""); + WatchList watch = ListOfWatchList.getList().getWatchList(0); + try { + watch.delStock(userin); + success = true; + } catch (StockNotExistsException e) { + System.out.println("Stock: " + userin + " doesn't exists"); + } finally { + System.out.println(success ? "Deleted: " + userin : "Failed to delete: " + userin); + } } } diff --git a/src/test/data/NasdaqTest.java b/src/test/data/NasdaqTest.java index cb57871..b3a834b 100644 --- a/src/test/data/NasdaqTest.java +++ b/src/test/data/NasdaqTest.java @@ -10,13 +10,11 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; public class NasdaqTest { - private StypeMap smap; private StockType naasdaq; @BeforeEach public void runBefore() { - smap = new StypeMap(); - naasdaq = smap.getStype("Nasdaq"); + naasdaq = StypeMap.getStype("NASDAQ"); } @Test diff --git a/src/test/data/NyseTest.java b/src/test/data/NyseTest.java index e85744f..bb6345e 100644 --- a/src/test/data/NyseTest.java +++ b/src/test/data/NyseTest.java @@ -10,13 +10,11 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; public class NyseTest { - private StypeMap smap; private StockType nyyyse; @BeforeEach public void runBefore() { - smap = new StypeMap(); - nyyyse = smap.getStype("Nyse"); + nyyyse = StypeMap.getStype("NYSE"); } @Test @@ -30,4 +28,17 @@ public class NyseTest { assertEquals(farray[0],0.0); assertEquals(farray[1],0.0); } + + @Test + public void testEqualsHash() { + StockType testst1 = new Nyse(); + StockType testst2 = new Nasdaq(); + String asdf = "asdf"; + assertEquals(nyyyse, nyyyse); + assertEquals(nyyyse, testst1); + assertFalse(nyyyse.equals(testst2)); + assertFalse(nyyyse.equals(asdf)); + assertEquals(nyyyse.hashCode(), testst1.hashCode()); + assertFalse(nyyyse.hashCode() == testst2.hashCode()); + } } diff --git a/src/test/data/StockEntryTest.java b/src/test/data/StockEntryTest.java index f257bdc..44a48a1 100644 --- a/src/test/data/StockEntryTest.java +++ b/src/test/data/StockEntryTest.java @@ -14,13 +14,11 @@ import static org.junit.jupiter.api.Assertions.assertTrue; public class StockEntryTest { private StockEntry entry; - private StypeMap smap; private StockType nyyyse; @BeforeEach public void runBefore() { - smap = new StypeMap(); - nyyyse = smap.getStype("Nyse"); + nyyyse = StypeMap.getStype("NYSE"); } @Test diff --git a/src/test/data/StypeMapTest.java b/src/test/data/StypeMapTest.java index 68f194e..58736a4 100644 --- a/src/test/data/StypeMapTest.java +++ b/src/test/data/StypeMapTest.java @@ -10,16 +10,14 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; public class StypeMapTest { - private StypeMap stypemap; @BeforeEach public void runBefore() { - stypemap = new StypeMap(); } @Test public void testGetStype() { - StockType stype = stypemap.getStype("Nyse"); + StockType stype = StypeMap.getStype("NYSE"); assertEquals(stype.getName(), "NYSE"); } } diff --git a/src/test/data/WatchListTest.java b/src/test/data/WatchListTest.java index 815a44a..f57f0c9 100644 --- a/src/test/data/WatchListTest.java +++ b/src/test/data/WatchListTest.java @@ -16,39 +16,32 @@ import static org.junit.jupiter.api.Assertions.fail; public class WatchListTest { private WatchList watchlist; - private Main mainObj; - private StypeMap smap; @BeforeEach public void runBefore() { - mainObj = new Main(true); - watchlist = mainObj.getWatchList(); - smap = new StypeMap(); + watchlist = new WatchList(); } @Test public void testAddStock() { - StockType nyyyse = smap.getStype("Nyse"); for (int i = 0; i < 100; i++) { - watchlist.addStock(Integer.toString(i), nyyyse); + watchlist.addStock(Integer.toString(i)); } } @Test public void testSize() { assertEquals(watchlist.size(), 0); - StockType nyyyse = smap.getStype("Nyse"); for (int i = 0; i < 100; i++) { - watchlist.addStock(Integer.toString(i), nyyyse); + watchlist.addStock(Integer.toString(i)); } assertEquals(watchlist.size(), 100); } @Test public void testDelStockNoThrow() { - StockType nyyyse = smap.getStype("Nyse"); for (int i = 0; i < 100; i++) { - watchlist.addStock(Integer.toString(i), nyyyse); + watchlist.addStock(Integer.toString(i)); } for (int i = 0; i < 50; i++) { try { @@ -62,9 +55,8 @@ public class WatchListTest { @Test public void testDelNotExistsStock() { - StockType nyyyse = smap.getStype("Nyse"); for (int i = 0; i < 100; i++) { - watchlist.addStock(Integer.toString(i), nyyyse); + watchlist.addStock(Integer.toString(i)); } try { watchlist.delStock(Integer.toString(-1)); @@ -76,17 +68,30 @@ public class WatchListTest { @Test public void testSaveLoad() { - StockType nyyyse = smap.getStype("Nyse"); for (int i = 0; i < 100; i++) { - watchlist.addStock(Integer.toString(i), nyyyse); + watchlist.addStock(Integer.toString(i)); } watchlist.save(""); assertTrue(watchlist.fileExists(watchlist.DEFAULT_SAVEFILE)); - Main testMain = new Main(true); - WatchList testlist = testMain.getWatchList(); + WatchList testlist = new WatchList(); testlist.load(""); assertEquals(watchlist.size(), testlist.size()); File testFile = new File(watchlist.DEFAULT_SAVEFILE); testFile.delete(); } + + @Test + public void testFileNotFound() { + //redirect stdout: https://stackoverflow.com/questions/1119385/junit-test-for-system-out-println + ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + ByteArrayOutputStream errContent = new ByteArrayOutputStream(); + PrintStream originalOut = System.out; + PrintStream originalErr = System.err; + System.setOut(new PrintStream(outContent)); + System.setErr(new PrintStream(errContent)); + watchlist.load("thisfiledoesnotexist-andwillneverexists"); + assertEquals("File not found: thisfiledoesnotexist-andwillneverexists\n", outContent.toString()); + System.setOut(originalOut); + System.setErr(originalErr); + } }