Compare commits

...

24 Commits

@ -8,6 +8,16 @@
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module-library" exported="">
<library name="javax-json">
<CLASSES>
<root url="jar://$MODULE_DIR$/lib/javax.json-1.1.4.jar!/" />
<root url="jar://$MODULE_DIR$/lib/javax.json-api-1.1.4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
<orderEntry type="module-library" scope="TEST">
<library name="JUnit5.4">
<CLASSES>
@ -25,4 +35,4 @@
</library>
</orderEntry>
</component>
</module>
</module>

@ -4,6 +4,9 @@ Author: Benny Leung
J-WatchList is a simple stock market watch list tracker.
## Dependencies
External libarary:
- [javax.json]("https://docs.oracle.com/javaee/7/api/javax/json/Json.html")
Build deps:
- gradle
@ -11,15 +14,27 @@ Build deps:
### .class
In a terminal:
```
git clone https://github.students.cs.ubc.ca/cpsc210-2019w-t1/project_h9u2b
cd project_h9u2b
gradle clean build test checkstyleMain
git clone https://git.leung.rocks/benny/J-WatchList.git
cd J-WatchList
gradle clean build
```
Class files are in build/classes, to run with plain java:
```
cd project_h9u2b
java -cp build/classes/java/main/:build/classes/java/main/ui ui.Iface
cd J-WatchList
java -cp build/classes/java/main/:build/classes/java/main/ui ui.Main
```
Or run with gradle:
```
cd J-WatchList
gradle run
```
To debug:
```
cd J-WatchList
gradle debug
```
## Tags
@ -29,4 +44,3 @@ Rebuild tags with:
```
gradle tags
```

@ -2,6 +2,10 @@ apply plugin: 'java'
apply plugin: 'checkstyle'
dependencies {
/*
compile 'javax.json:javax.json-api:1.1.4'
compile 'org.glassfish:javax.json:1.1.4'
*/
compile fileTree(include: ['*.jar'], dir:'lib')
testCompile fileTree(include: ['*.jar'], dir:'lib')
testImplementation fileTree(include: ['*.jar'], dir:'lib')
@ -14,6 +18,12 @@ checkstyle {
configFile file('checkstyle.xml')
}
/*
repositories {
mavenCentral()
}
*/
sourceSets {
main {
java {

Binary file not shown.

Binary file not shown.

@ -4,26 +4,27 @@ import java.util.Set;
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Objects;
import java.io.IOException;
public class DataSource {
private HashMap<String, StockType> targets;
private String name;
private String url;
private String apiKey;
private String defapi;
public abstract class DataSource {
protected HashMap<String, StockType> targets;
protected String name;
protected String url;
protected String apiKey;
public DataSource(String name, String url, String apiKey, String defapi) {
public DataSource(String name, String url, String apiKey) {
this.targets = new HashMap<String, StockType>();
this.name = name;
this.url = url;
this.apiKey = apiKey;
this.defapi = defapi;
}
//Effect: add target to this
// add this to target if not already done
//Modifies: this, stype
public void addStype(StockType stype) {
String sname = stype.getClass().getName();
String sname = stype.getName();
if (!this.targets.containsKey(sname)) {
targets.put(sname,stype);
stype.addSource(this);
@ -34,10 +35,37 @@ public class DataSource {
// del this to target if not already done
//Modifies: this, stype
public void delStype(StockType stype) {
String sname = stype.getClass().getName();
String sname = stype.getName();
if (this.targets.containsKey(sname)) {
targets.remove(sname,stype);
stype.delSource(this);
}
}
public String getName() {
return name;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof DataSource)) {
return false;
}
DataSource temp = (DataSource) obj;
if (temp.getName().equals(name)) {
return true;
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(name);
}
//Effect: returns updated data in the form: double[2], [0] is price, [1] is %change
public abstract double[] update(String stype, String idstring) throws IOException;
}

@ -0,0 +1,36 @@
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<WatchList> wlists;
private ListOfWatchList() {
wlists = new ArrayList<WatchList>();
}
public static ListOfWatchList getList() {
return lowl;
}
//Effect: add a watchlist to this list, if it isn't already in the list
//Modifies: this
public void addWatchList(WatchList wlist) {
if (!(wlists.contains(wlist))) {
wlists.add(wlist);
}
}
//Effect: remove a watchlist
//Modifies: this
public void delWatchList(WatchList wlist) {
wlists.remove(wlist);
}
//Effect: return a watchlist by index
public WatchList getWatchList(int index) {
return (WatchList) wlists.get(index);
}
}

@ -2,10 +2,12 @@ package data;
import java.util.*;
import data.StockType;
import network.AlphaVantage;
public class Nyse extends StockType {
public Nyse() {
super();
name = "NYSE";
addSource(new AlphaVantage());
}
}

@ -1,20 +1,41 @@
package data;
import java.util.Objects;
import java.io.IOException;
public class StockEntry {
private String identifier;
private StockType stype;
private double price;
private double change;
private boolean updating = false;
public StockEntry(StockType stype, String idstring) {
identifier = idstring;
this.stype = stype;
}
public void update() {
double[] result = this.stype.update(this.identifier);
//Effect: return true if price or change is/are new
// update the price and %change by the stype update
//Modifies: this
public boolean update() {
boolean changed = false;
updating = true;
double[] result;
try {
result = this.stype.update(this.identifier);
} catch (IOException e) {
System.out.println("Error getting update");
e.printStackTrace();
return false;
}
if ((result[0] != price) || (result[1] != change)) {
changed = true;
}
this.price = result[0];
this.change = result[1];
updating = false;
return changed;
}
public String getTypeName() {
@ -24,4 +45,37 @@ public class StockEntry {
public String getID() {
return this.identifier;
}
public double getPrice() {
return price;
}
public double getChange() {
return change;
}
public boolean isUpdating() {
return updating;
}
@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());
}
}

@ -1,26 +1,29 @@
package data;
import java.util.*;
import java.io.IOException;
public abstract class StockType {
protected String name;
protected HashSet sources;
protected HashSet<DataSource> sources;
// Sources is empty
public StockType() {
sources = new HashSet();
sources = new HashSet<DataSource>();
}
//Effects: return current price[0] and %change[1]
// (2 element array)
//Require: working sources
public double[] update(String idstring) {
Iterator iterator = sources.iterator();
public double[] update(String idstring) throws IOException {
double[] result = {0.0, 0.0};
while (iterator.hasNext()) {
//XXX
//DataSource source = (DataSource)iterator.next();
for (DataSource source : sources) {
double[] sourceRes = source.update(name, idstring);
result[0] += sourceRes[0];
result[1] += sourceRes[1];
}
result[0] /= sources.size();
result[1] /= sources.size();
return result;
}
@ -45,7 +48,6 @@ public abstract class StockType {
}
//Effects: return name of type
//Require: working sources
public String getName() {
return this.name;
}

@ -13,23 +13,42 @@ 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<String, StockType> smap;
private static HashMap<String, StockType> smap;
private static StypeMap stypemap = new StypeMap();
// Effects: smap is initialized
// with supported StockType
public StypeMap() {
private StypeMap() {
smap = new HashMap<String, StockType>();
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);
// note: this is a shortcut to getMap and then getInstStype
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;
}
}

@ -5,35 +5,36 @@ import java.io.*;
import data.StockEntry;
import ui.Main;
import data.exceptions.*;
import java.util.Observable;
public class WatchList implements Load,Save {
public class WatchList extends Observable implements Load,Save {
private LinkedHashMap<String, StockEntry> listdata;
private Main mainObj;
public static final String DEFAULT_SAVEFILE = ".jwatch.list";
public enum Etype {
UPDATE, ADD, DEL
}
// Effects: List is empty
// or loaded with save values
public WatchList(Main mainObj) {
public WatchList() {
listdata = new LinkedHashMap<String, StockEntry>();
this.mainObj = mainObj;
if (fileExists(DEFAULT_SAVEFILE)) {
load("");
}
}
// Debug constructor
public WatchList(Main mainObj, boolean debug) {
listdata = new LinkedHashMap<String, StockEntry>();
this.mainObj = mainObj;
ListOfWatchList.getList().addWatchList(this);
}
// 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));
setChanged();
notifyObservers(new Wevent(Etype.ADD, size()));
}
// Effects: Delete an entry with key==target
@ -43,7 +44,15 @@ public class WatchList implements Load,Save {
if (!listdata.containsKey(target)) {
throw new StockNotExistsException();
}
int index = new ArrayList(listdata.keySet()).indexOf(target);
this.listdata.remove(target);
setChanged();
notifyObservers(new Wevent(Etype.DEL, index));
}
// Effects: return an array of all Stock names in list
public String[] getNames() {
return listdata.keySet().toArray(new String[0]);
}
// Effects: Return an iterator of the list
@ -52,12 +61,19 @@ public class WatchList implements Load,Save {
Set entryset = listdata.entrySet();
return entryset.iterator();
}
//Effects: return a stockentry given its index
public StockEntry getStock(int index) {
String key = (String) listdata.keySet().toArray()[index];
return (StockEntry) listdata.get(key);
}
// Effects: Return the size of list
public int size() {
return listdata.size();
}
//Effect: save list to a file, save to DEFAULT_SAVEFILE if filename is empty
@Override
public void save(String filename) {
if (filename.equals("")) {
@ -70,7 +86,7 @@ public class WatchList implements Load,Save {
while (iterate.hasNext()) {
Map.Entry entry = (Map.Entry)iterate.next();
String outString = (String)entry.getKey();
System.out.println("Exported: " + outString);
System.out.println("Exported: " + outString);
bwriter.write(outString);
bwriter.newLine();
}
@ -80,12 +96,15 @@ public class WatchList implements Load,Save {
}
}
//Effect: return true if filename exists and is a file
@Override
public boolean fileExists(String filename) {
File fileObj = new File(filename);
return fileObj.isFile();
}
//Effect: load list from file, load from DEFAULT_SAVEFILE if filename is empty
//Modifies: this
@Override
public void load(String filename) {
if (filename.equals("")) {
@ -96,7 +115,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();
@ -106,4 +125,43 @@ public class WatchList implements Load,Save {
System.out.println("IO Error when reading: " + filename);
}
}
//Effect: perform price and %update for @ stockentry in list
//Modifies: this, stockentries
public void updateList() {
Iterator watchit = iterator();
boolean changed = false;
while (watchit.hasNext()) {
Map.Entry entry = (Map.Entry)watchit.next();
StockEntry sentry = (StockEntry)entry.getValue();
if (!changed) {
changed = changed || sentry.update();
}
sentry.update();
}
setChanged();
notifyObservers(new Wevent(Etype.UPDATE));
}
public class Wevent {
private Etype type;
private int data;
public Wevent(Etype type) {
this(type, 0);
}
public Wevent(Etype type, int num) {
this.type = type;
this.data = num;
}
public Etype getType() {
return type;
}
public int getData() {
return data;
}
}
}

@ -0,0 +1,41 @@
package network;
import data.StypeMap;
import java.io.IOException;
import javax.json.JsonObject;
import data.DataSource;
import network.exceptions.*;
public class AlphaVantage extends DataSource {
public AlphaVantage() {
super("AlphaVantage", "https://www.alphavantage.co/query", "4MC2LL0HOQ2TFQL1");
}
//Effect: get intraday price and %change through JSON given the stock ticker
@Override
public double[] update(String stype, String idstring) throws IOException {
double[] result = {0.0, 0.0};
try {
String urlString = Net.urlStringBuilder(url, "function", "TIME_SERIES_INTRADAY", "symbol", idstring,
"interval", "5min",
"apikey", apiKey);
JsonObject response = StockJson.urlToJson(urlString);
JsonObject preJson = StockJson.jsonInJson(response, "Time Series (5min)");
if (preJson == null) {
throw new IOException("Error getting data from " + name);
}
JsonObject mainJson = StockJson.timeSeriesElement(preJson, 0);
//System.out.print(mainJson);
result[0] = Double.parseDouble(StockJson.stringGetter(mainJson, "4. close"));
Double open = Double.parseDouble(StockJson.stringGetter(mainJson, "1. open"));
result[1] = (result[0] - open) / open;
} catch (ParaMismatchException e) {
e.printStackTrace();
} //catch (IOException e) {
// System.out.println("Error getting data from: " + name);
//e.printStackTrace();
//}
return result;
}
}

@ -0,0 +1,43 @@
package network;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import network.exceptions.*;
//Ref: https://stackoverflow.com/questions/2793150/how-to-use-java-net-urlconnection-to-fire-and-handle-http-requests
public class Net {
//Effect: returns a String of url built with given url and paras
public static String urlStringBuilder(String url, String... paras) throws ParaMismatchException {
String urlString = url;
String charset = "UTF-8";
if ((paras.length % 2) != 0) {
throw new ParaMismatchException("Except even paras, but have " + paras.length);
}
if (paras.length != 0) {
urlString += "?";
try {
for (int i = 0; i < paras.length - 3; i = i + 2) {
urlString += paras[i] + "=" + URLEncoder.encode(paras[i + 1], charset) + "&";
}
urlString += paras[paras.length - 2] + "=" + URLEncoder.encode(paras[paras.length - 1], charset);
} catch (UnsupportedEncodingException e) {
System.out.println("Error during encoding url: Unsupported encoding");
e.printStackTrace();
}
}
return urlString;
}
//Effect: Open connection, fires a http GET and returns the InputStream of result
public static InputStream urlToInputStream(String url) throws IOException {
try {
return (new URL(url).openStream());
} catch (IOException e) {
throw e;
}
}
}

@ -0,0 +1,57 @@
package network;
import javax.json.Json;
import javax.json.JsonObject;
import javax.json.JsonReader;
import javax.json.JsonNumber;
import javax.json.JsonArray;
import javax.json.JsonValue;
import java.io.InputStream;
import java.io.IOException;
import java.util.Iterator;
//Json library from https://docs.oracle.com/javaee/7/api/javax/json/Json.html
public class StockJson {
public static JsonObject inputStreamToJson(InputStream istream) {
JsonReader jreader = Json.createReader(istream);
JsonObject jobj = jreader.readObject();
jreader.close();
return jobj;
}
public static JsonObject urlToJson(String url) throws IOException {
try {
return inputStreamToJson(Net.urlToInputStream(url));
} catch (IOException e) {
throw e;
}
}
// Effect: return double from jsonnumber
public static double doubleGetter(JsonObject jobj, String name) {
return jobj.getJsonNumber(name).doubleValue();
}
// Effect: return jsonobject from jsonobject
public static JsonObject jsonInJson(JsonObject jobj, String name) {
return jobj.getJsonObject(name);
}
// Effect: return string from jsonstring
public static String stringGetter(JsonObject jobj, String name) {
return jobj.getString(name);
}
// Effect: return double from percentage string
public static double doublePercent(JsonObject jobj, String name) {
String temp = stringGetter(jobj, name);
return Double.parseDouble(temp.split("%")[0]);
}
// From https://stackoverflow.com/questions/33531041/jsonobject-get-value-of-first-node-regardless-of-name
// Effect: extract the jsonobject from jsonobject by index
public static JsonObject timeSeriesElement(JsonObject jobj, int index) {
String name = (String) jobj.keySet().toArray()[index];
return jsonInJson(jobj, name);
}
}

@ -0,0 +1,7 @@
package network.exceptions;
public class ParaMismatchException extends Exception {
public ParaMismatchException(String sss) {
super(sss);
}
}

@ -1,18 +1,83 @@
package ui;
import javax.swing.*;
import javax.swing.SwingUtilities;
import javax.swing.JFrame;
import javax.swing.JComponent;
import javax.swing.JButton;
import javax.swing.GroupLayout;
import java.awt.Container;
import java.awt.LayoutManager;
import ui.guicomp.*;
import data.WatchList;
import data.ListOfWatchList;
import static data.Const.PROGRAM_NAME;
import javax.swing.GroupLayout.Alignment;
public class Gui {
//public Gui() {
// JLabel label = new JLabel("Hello World");
// based on http://zetcode.com/tutorials/javaswingtutorial/firstprograms/
public class Gui extends JFrame implements Iface {
private JButton addButton;
private JButton delButton;
private JButton upButton;
private WatchTablePane wtable;
private WatchList wlist;
private Runnable init = new Runnable() {
//Effect: Init gui all components
//Modifies: this
public void run() {
addComponents();
createLayout();
setTitle(PROGRAM_NAME);
setSize(300, 200);
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
// JFrame.setDefaultLookAndFeelDecorated(true);
// JFrame f = new JFrame("Hello World");
// f.setSize(300,150);
// f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
private void addComponents() {
addButton = new AddButt(wlist);
delButton = new DelButt(wlist);
upButton = new UpButt(wlist);
wtable = new WatchTablePane(wlist);
}
// f.add(label);
private void createLayout() {
Container pane = getContentPane();
GroupLayout lman = new GroupLayout(pane);
pane.setLayout(lman);
lman.setAutoCreateContainerGaps(true);
lman.setHorizontalGroup(lman.createParallelGroup(GroupLayout.Alignment.CENTER)
.addComponent(wtable.getPane())
.addGroup(lman.createSequentialGroup()
.addComponent(addButton)
.addComponent(delButton)
.addComponent(upButton)
));
lman.setVerticalGroup(lman.createSequentialGroup()
.addComponent(wtable.getPane())
.addGroup(lman.createParallelGroup(GroupLayout.Alignment.CENTER)
.addComponent(addButton)
.addComponent(delButton)
.addComponent(upButton)
));
}
};
// f.setVisible(true);
//}
public Gui() {
wlist = ListOfWatchList.getList().getWatchList(0);
SwingUtilities.invokeLater(init);
setVisible(true);
}
//Effect: nothing
//not enough time to do something useful for this
@Override
public void redraw() {
//Nothing for now
}
//Effect: nothing
//not enough time to do something useful for this
@Override
public void destory() {
//Nothing for now
}
}

@ -1,7 +1,5 @@
package ui;
import ui.Options;
public interface Iface {
//private Options ifaceOpts;

@ -1,17 +1,13 @@
package ui;
import ui.Options;
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();
//Test Gui
return new Gui();
}
//public static Iface getIface(Options iOpts) {
// //XXX iOpts not ready
// //uses Tui for now
// return new Tui(iOpts);
//}
}

@ -1,7 +1,6 @@
package ui;
import data.Const;
import ui.Options;
import data.StypeMap;
import data.StockType;
import data.WatchList;
@ -10,71 +9,15 @@ 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) {
//init options, it will load defaults
//from resource xml
allOptions = new Options();
//parse args, uses
allOptions.parseArgs(args);
stypeMap = new StypeMap();
mainList = new WatchList(this);
//initalize UI thread, options not ready
//this.Iface = IfaceFactory.getIface(allOptions.getSection("ui"));
iface = IfaceFactory.getIface(this);
}
//Constructor for testing
public Main(boolean debug) {
//init options, it will load defaults
//from resource xml
allOptions = new Options();
stypeMap = new StypeMap();
mainList = new WatchList(this, true);
WatchList mainList = new WatchList();
iface = IfaceFactory.getIface();
}
// 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);
}
}

@ -1,150 +0,0 @@
package ui;
import java.util.prefs.Preferences;
import java.util.prefs.BackingStoreException;
import java.util.prefs.InvalidPreferencesFormatException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.FileNotFoundException;
import java.io.InputStream;
import data.Const;
public class Options {
private Preferences store;
private boolean wasEmpty;
private boolean noSave;
private static final String DEF_OPTS_PATH = "";
private static final String DEF_BACKUP = ".jwatch-backup.xml";
private static final String RES_DEF = "defPref.xml";
public Options() {
store = Preferences.userRoot();
try {
wasEmpty = store.nodeExists(Const.PROGRAM_NAME);
store = store.node(Const.PROGRAM_NAME);
if (wasEmpty) {
//load default opts from resource
//XXX
//importDefPreferences();
}
} catch (BackingStoreException e) {
System.out.println("Critical Error: preference backing cannot be initialized");
throw new RuntimeException();
}
}
public Options(String pathname) {
store = Preferences.userRoot().node(pathname);
}
public Options getSection(String section) {
//String path = Const.PROGRAM_NAME + "/" + section;
//Options result = new Options(path);
//return result;
return null;
}
public void importPreferences(String xmlpath) {
try {
File tarFile = new File(xmlpath);
FileInputStream tarStream = new FileInputStream(tarFile);
store.importPreferences(tarStream);
tarStream.close();
} catch (FileNotFoundException e) {
System.out.println("File not found: " + xmlpath);
} catch (IOException e) {
System.out.println("IO Error when reading: " + xmlpath);
e.printStackTrace();
} catch (InvalidPreferencesFormatException e) {
System.out.println("Import format error, possible file corruption");
}
}
public void importDefPreferences() {
try {
InputStream istream = getClass().getResourceAsStream(RES_DEF);
store.importPreferences(istream);
istream.close();
} catch (IOException e) {
System.out.println("IO Error while reading def pref: "
+ RES_DEF);
} catch (InvalidPreferencesFormatException e) {
System.out.println("Def resPref format error, possible program corruption");
}
}
public void exportPreferences(String xmlpath) {
try {
File tarFile = new File(xmlpath);
if (tarFile.isFile()) {
tarFile.delete();
} else {
tarFile.createNewFile();
}
FileOutputStream tarStream = new FileOutputStream(tarFile);
store.exportSubtree(tarStream);
tarStream.flush();
tarStream.close();
System.out.println("Exported preference to: " + xmlpath);
} catch (IOException e) {
System.out.println("IO Error when writing: " + xmlpath);
} catch (BackingStoreException e) {
System.out.println("Error retriving pref from store");
e.printStackTrace();
}
}
public void destroy() {
// Clear config if requested or config was empty
// and is not saving
if (wasEmpty && noSave) {
try {
this.store.clear();
} catch (BackingStoreException e) {
System.out.println("Error clearing pref store");
e.printStackTrace();
}
} else if (noSave) {
restorePrevious();
}
delBackup();
}
public void backupPref() {
exportPreferences(DEF_BACKUP);
System.out.println("User Pref Backup created");
}
public void delBackup() {
try {
File tarFile = new File(DEF_BACKUP);
tarFile.delete();
} catch (Exception e) {
System.out.println("Error deleting backup user pref");
e.printStackTrace();
}
}
public void restorePrevious() {
importPreferences(DEF_BACKUP);
}
// Default all false bool pref to be safe
// Default should be imported by constructor regardless
public boolean getBool(String key) {
return this.store.getBoolean(key, false);
}
// Default all empty string pref to be safe
// Default should be imported by constructor regardless
public String getString(String key) {
return this.store.get(key, "");
}
public void parseArgs(String[] args) {
//XXX
}
}

@ -7,25 +7,29 @@ import ui.Main;
import data.Const;
import data.WatchList;
import data.StockEntry;
import data.ListOfWatchList;
import data.exceptions.*;
import java.util.Observer;
import java.util.Observable;
public class Tui implements Iface {
public class Tui implements Iface, Observer {
private static final String SAVE_CURSOR = "\u001b[s";
private static final String RESTORE_CURSOR = "\u001b[s";
private static final String REQUEST_CURSOR = "\u001b[6n";
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;
WatchList watch = ListOfWatchList.getList().getWatchList(0);
watch.addObserver(this);
//XXX Start ui thread
demo();
}
//Effect: wait for user input and get a line (pressed Enter)
public String getInputLine() {
String result;
try {
@ -42,6 +46,8 @@ public class Tui implements Iface {
return "";
}
//Effect: capture user input, constantly
//Unused, for future dev uses
public String readUntil(char end) {
String result = "";
char c;
@ -85,31 +91,36 @@ public class Tui implements Iface {
maxcol = maxCoord[1];
}
//Effect: nothing has to be done
@Override
public void destory() {
//Nothing has to be done
return;
}
//Effect: nothing
//will be used when Tui is redesigned
@Override
public void redraw() {
//XXX Do nothing for now
return;
}
//Effect: Show a menu of functions
public void demomenu() {
System.out.println("Function select:");
System.out.println("1: View watchlist");
System.out.println("1: Update watchlist");
System.out.println("2: Add stock");
System.out.println("3: Remove stock");
System.out.println("q: Quit");
System.out.println("Enter a number or q, then press enter.\n");
}
//Effect: execute functions basing on user input from menu
public boolean demoinput() {
switch (getInputLine()) {
case "1":
printWatchList();
updateWatch();
break;
case "2":
addWatch();
@ -126,6 +137,7 @@ public class Tui implements Iface {
return true;
}
//Effect: main loop of program, displays menu, get input and execute functions
public void demo() {
System.out.println(" Welcome to " + Const.PROGRAM_NAME + "!");
boolean cont = true;
@ -133,35 +145,55 @@ 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 updateWatch() {
WatchList watch = ListOfWatchList.getList().getWatchList(0);
watch.updateList();
}
private void printWatchList() {
WatchList watch = ListOfWatchList.getList().getWatchList(0);
Iterator watchit = watch.iterator();
while (watchit.hasNext()) {
Map.Entry entry = (Map.Entry)watchit.next();
StockEntry sentry = (StockEntry)entry.getValue();
System.out.println("Type: " + sentry.getTypeName()
+ " Identifier: " + sentry.getID());
System.out.println("Type: " + sentry.getTypeName() + "\t"
+ "Identifier: " + sentry.getID() + "\t"
+ "Price: " + sentry.getPrice() + "\t"
+ "Change %" + sentry.getChange());
}
}
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);
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);
}
}
public void saveWatch() {
main.saveWatch("");
//Effect: reprint the list when the list is updated, or if stocks are added or deleted
@Override
public void update(Observable obsed, Object event) {
printWatchList();
}
}

@ -0,0 +1,37 @@
package ui.guicomp;
import javax.swing.JButton;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import javax.swing.JOptionPane;
import data.WatchList;
public class AddButt extends JButton {
private WatchList wlist;
public AddButt(WatchList wlist) {
super("Add Stock...");
//https://coderanch.com/t/580497/java/JButton-clicked-action
if (getActionListeners().length < 1) {
addActionListener(new AddButtList());
}
this.wlist = wlist;
}
private class AddButtList implements ActionListener {
public void actionPerformed(ActionEvent e) {
//https://docs.oracle.com/javase/tutorial/uiswing/components/dialog.html#input
//https://docs.oracle.com/javase/7/docs/api/javax/swing/JOptionPane.html
String userin = (String)JOptionPane.showInputDialog("Enter the identifier:");
if ((userin != null) && (userin.length() > 0)) {
new Thread(new Runnable() {
@Override
public void run() {
wlist.addStock(userin);
wlist.updateList();
}
}).start();
}
}
}
}

@ -0,0 +1,45 @@
package ui.guicomp;
import javax.swing.JButton;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import javax.swing.JOptionPane;
import data.WatchList;
import data.exceptions.StockNotExistsException;
public class DelButt extends JButton {
private WatchList wlist;
public DelButt(WatchList wlist) {
super("Remove Stock...");
if (getActionListeners().length < 1) {
addActionListener(new DelButtList());
}
this.wlist = wlist;
}
private class DelButtList implements ActionListener {
public void actionPerformed(ActionEvent e) {
//https://docs.oracle.com/javase/tutorial/uiswing/components/dialog.html#input
//https://docs.oracle.com/javase/7/docs/api/javax/swing/JOptionPane.html
String[] names = wlist.getNames();
String userin = (String)JOptionPane.showInputDialog(
null, "Choose a stock to delete:", "Delete Stock",
JOptionPane.PLAIN_MESSAGE, null, names, null);
if ((userin != null) && (userin.length() > 0)) {
//https://stackoverflow.com/questions/12771500/best-way-of-creating-and-using-an-anonymous-runnable-class
new Thread(new Runnable() {
@Override
public void run() {
try {
wlist.delStock(userin);
} catch (StockNotExistsException e) {
//impossible
e.printStackTrace();
}
}
}).start();
}
}
}
}

@ -0,0 +1,29 @@
package ui.guicomp;
import javax.swing.JButton;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import data.WatchList;
public class UpButt extends JButton {
private WatchList wlist;
public UpButt(WatchList wlist) {
super("Update");
if (getActionListeners().length < 1) {
addActionListener(new UpButtList());
}
this.wlist = wlist;
}
private class UpButtList implements ActionListener {
public void actionPerformed(ActionEvent e) {
new Thread(new Runnable() {
@Override
public void run() {
wlist.updateList();
}
}).start();
}
}
}

@ -0,0 +1,80 @@
package ui.guicomp;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableModel;
import javax.swing.event.*;
import data.WatchList;
import data.WatchList.Wevent;
import data.StockEntry;
import java.util.Observer;
import java.util.Observable;
import javax.swing.SwingUtilities;
// Ref: https://docs.oracle.com/javase/tutorial/uiswing/components/table.html
// Ref2: https://docs.oracle.com/javase/8/docs/api/javax/swing/table/AbstractTableModel.html
public class WatchTableModel extends AbstractTableModel implements Observer {
private final String[] colnames = {"Identifier", "Price", "% change", "Status"};
private final Object[] example = {"asdf", 0.0, "asdf", "asdf"};
private WatchList watch;
public WatchTableModel(WatchList wlist) {
watch = wlist;
watch.addObserver(this);
}
public int getColumnCount() {
return colnames.length;
}
public int getRowCount() {
return watch.size();
}
public String getColumnName(int num) {
return colnames[num];
}
//Effect: return generated table data for whatever asked
public Object getValueAt(int stock, int field) {
StockEntry entry = watch.getStock(stock);
switch (field) {
case 0: //Name
return entry.getID();
case 1: //Price
return entry.getPrice();
case 2: //% change
return entry.getChange() + " %";
case 3:
return entry.isUpdating() ? "Updating" : "Ok";
default:
return false;
}
}
public Class getColumnClass(int col) {
return example[col].getClass();
}
//Effect: updates table for different events, ran by event dispatch thread as recommanded
//Modifies: Table displayed
@Override
public void update(Observable obsed, Object event) {
Wevent weve = (Wevent) event;
switch (weve.getType()) {
case UPDATE:
SwingUtilities.invokeLater(() -> {
fireTableDataChanged(); });
break;
case ADD:
SwingUtilities.invokeLater(() -> {
fireTableRowsInserted(weve.getData(), weve.getData()); });
break;
case DEL:
SwingUtilities.invokeLater(() -> {
fireTableRowsDeleted(weve.getData(), weve.getData()); });
break;
default:
break;
}
}
}

@ -0,0 +1,23 @@
package ui.guicomp;
import javax.swing.JTable;
import javax.swing.JScrollPane;
import javax.swing.table.TableModel;
import data.WatchList;
//https://docs.oracle.com/javase/7/docs/api/javax/swing/JTable.html
public class WatchTablePane {
private JTable table;
private TableModel tmodel;
private JScrollPane spane;
public WatchTablePane(WatchList wlist) {
tmodel = new WatchTableModel(wlist);
table = new JTable(tmodel);
spane = new JScrollPane(table);
}
public JScrollPane getPane() {
return spane;
}
}

@ -0,0 +1,47 @@
package data;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import network.AlphaVantage;
import java.io.IOException;
public class DataSourceTest {
private StockType testStype;
private DataSource testSource;
@BeforeEach
public void runBefore() {
testStype = StypeMap.getStype("NYSE");
testSource = new AlphaVantage();
}
//Effect: test add and double deleting for many to many relationship
@Test
public void testAddDel() {
testStype.addSource(testSource);
testSource.delStype(testStype);
testSource.delStype(testStype);
}
//Effect: test overridden equals and hashcode
@Test
public void testEqualsHash() {
DataSource testSource2 = new AlphaVantage();
DataSource testSource3 = new DataSource("test source", "asdf", "asdf") {
@Override
public double[] update(String asdf, String asdf2) throws IOException {
double[] asdfjkl = {0.0, 0.0};
return asdfjkl;
}
};
String asdf = "asdf";
assertEquals(testSource, testSource);
assertEquals(testSource, testSource2);
assertFalse(testSource.equals(asdf));
assertFalse(testSource.equals(testSource3));
assertEquals(testSource.hashCode(), testSource2.hashCode());
}
}

@ -0,0 +1,47 @@
package data;
import data.ListOfWatchList;
import data.WatchList;
import data.exceptions.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
public class ListOfWatchListTest {
private WatchList watchlist;
@BeforeEach
public void runBefore() {
watchlist = new WatchList();
}
//Effect: check if only one instance is returned
@Test
public void singletonCheck() {
assertTrue(ListOfWatchList.getList() == ListOfWatchList.getList());
}
//Effect: check double adding and deleting watchlists
@Test
public void addDelWatchList() {
ListOfWatchList lowl = ListOfWatchList.getList();
lowl.addWatchList(watchlist);
assertEquals(watchlist, lowl.getWatchList(0));
lowl.addWatchList(watchlist);
assertEquals(watchlist, lowl.getWatchList(0));
try {
lowl.getWatchList(1);
} catch (IndexOutOfBoundsException e) {
// expected fail
}
lowl.delWatchList(watchlist);
try {
lowl.getWatchList(0);
} catch (IndexOutOfBoundsException e) {
// expected fail
}
}
}

@ -8,26 +8,30 @@ import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
public class NasdaqTest {
private StypeMap smap;
private StockType naasdaq;
@BeforeEach
// Effect: Initialize Stype
public void runBefore() {
smap = new StypeMap();
naasdaq = smap.getStype("Nasdaq");
naasdaq = StypeMap.getStype("NASDAQ");
}
@Test
//Effect: test if name matches
public void testName() {
assertTrue(naasdaq.getName().equals("NASDAQ"));
}
@Test
//Effect: check (indirectly) if update works, require internet to AlphaVantage
public void testUpdate() {
double[] farray = naasdaq.update("1");
assertEquals(farray[0],0.0);
assertEquals(farray[1],0.0);
try {
double[] farray = naasdaq.update("MSFT");
} catch (Exception e) {
fail();
}
}
}

@ -8,26 +8,44 @@ import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
public class NyseTest {
private StypeMap smap;
private StockType nyyyse;
@BeforeEach
public void runBefore() {
smap = new StypeMap();
nyyyse = smap.getStype("Nyse");
nyyyse = StypeMap.getStype("NYSE");
}
//Effect: test constructor and if name matches
@Test
public void testName() {
public void testNameInit() {
assertTrue(nyyyse.getName().equals("NYSE"));
}
//Effect: will connect to the internet and test update
//Require: internet connection to AlphaVantage.co
@Test
public void testUpdate() {
double[] farray = nyyyse.update("1");
assertEquals(farray[0],0.0);
assertEquals(farray[1],0.0);
try {
double[] farray = nyyyse.update("MSFT");
} catch (Exception e) {
fail();
}
}
//Effect: test overridden equals and hashcode
@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());
}
}

@ -14,19 +14,37 @@ 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");
}
//Effect: test constructor, two getters and default 0 price
@Test
public void testTypeAndName() {
entry = new StockEntry(nyyyse, "test");
assertTrue(entry.getTypeName().equals("NYSE"));
assertTrue(entry.getID().equals("test"));
assertFalse(entry.isUpdating());
assertEquals(0, entry.getPrice());
assertEquals(0, entry.getChange());
}
//Effect: test overridden equals and hashcode
@Test
public void testEqualsHash() {
entry = new StockEntry(nyyyse, "test");
StockEntry entry2 = new StockEntry(nyyyse, "test");
StockEntry entry3 = new StockEntry(StypeMap.getStype("NASDAQ"),
"test");
String asdf = "asdf";
assertEquals(entry, entry);
assertEquals(entry, entry2);
assertFalse(entry.equals(entry3));
assertFalse(entry.equals(asdf));
assertEquals(entry.hashCode(), entry2.hashCode());
assertFalse(entry.hashCode() == entry3.hashCode());
}
}

@ -10,16 +10,11 @@ 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
//Effect: Test if Stype NYSE exists in StypeMap
public void testGetStype() {
StockType stype = stypemap.getStype("Nyse");
StockType stype = StypeMap.getStype("NYSE");
assertEquals(stype.getName(), "NYSE");
}
}

@ -16,39 +16,45 @@ 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();
}
//Effect: add stock named 1 to 100 to watchlist, test if index is keep
//counting up and if getNames() works
@Test
public void testAddStock() {
StockType nyyyse = smap.getStype("Nyse");
public void testAddStockNames() {
String[] testarray = new String[100];
String[] testgetName = new String[100];
for (int i = 0; i < 100; i++) {
watchlist.addStock(Integer.toString(i), nyyyse);
watchlist.addStock(Integer.toString(i));
testarray[i] = Integer.toString(i);
}
testgetName = watchlist.getNames();
for (int i = 0; i < 100; i++) {
assertEquals(testarray[i], watchlist.getStock(i).getID());
assertEquals(Integer.toString(i), watchlist.getStock(i).getID());
assertEquals(testarray[i], testgetName[i]);
assertEquals(Integer.toString(i), testgetName[i]);
}
}
//Effect: test size function for 100 stocks
@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 +68,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 +81,45 @@ 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);
}
@Test
public void testUpdateEmptyList() {
watchlist.updateList();
watchlist.addStock("MSFT");
watchlist.updateList();
//https://stackoverflow.com/questions/24104313/how-do-i-make-a-delay-in-java
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
fail();
}
watchlist.updateList();
}
}

Loading…
Cancel
Save