From 106a0fe85effd27f8da28d17cf1053d5c50cd5fc Mon Sep 17 00:00:00 2001 From: j-james Date: Fri, 28 Oct 2022 21:22:06 -0700 Subject: Implement functionality for P2 --- src/main/model/html/ElementNode.java | 9 +- src/main/model/html/HtmlParser.java | 8 +- src/main/model/html/TextNode.java | 9 +- src/main/model/util/Node.java | 4 + src/main/persistance/JsonAble.java | 7 ++ src/main/persistance/JsonUtils.java | 44 +++++++++ src/main/ui/BrowserApp.java | 184 +++++++++++++++++++++++++++++++---- 7 files changed, 243 insertions(+), 22 deletions(-) create mode 100644 src/main/persistance/JsonAble.java create mode 100644 src/main/persistance/JsonUtils.java (limited to 'src') diff --git a/src/main/model/html/ElementNode.java b/src/main/model/html/ElementNode.java index 16438f9..5ff4a0f 100644 --- a/src/main/model/html/ElementNode.java +++ b/src/main/model/html/ElementNode.java @@ -2,6 +2,8 @@ package model.html; import model.util.Node; import org.javatuples.Pair; +import org.json.JSONObject; +import persistance.JsonAble; import java.util.ArrayList; import java.util.Optional; @@ -9,7 +11,7 @@ import java.util.Optional; /** * This ElementNode class represents an HTML tag and nested tags. */ -public class ElementNode implements Node { +public class ElementNode implements Node, JsonAble { private String tag; private ArrayList> attributes; @@ -67,4 +69,9 @@ public class ElementNode implements Node { public String getData() { return getTag() + " " + getAttributes().toString(); } + + @Override + public JSONObject serialize() { + return new JSONObject(this); + } } diff --git a/src/main/model/html/HtmlParser.java b/src/main/model/html/HtmlParser.java index d07a2ff..f0829f4 100644 --- a/src/main/model/html/HtmlParser.java +++ b/src/main/model/html/HtmlParser.java @@ -4,6 +4,8 @@ import java.util.*; import model.util.Node; import org.javatuples.*; +import org.json.JSONObject; +import persistance.JsonAble; /** * This class represents the state of and implements an LL(1) HTML parser. @@ -18,7 +20,7 @@ import org.javatuples.*; * SELF_CLOSING_TAG ::= 'img' | ... * (note that \forall T \in SELF_CLOSING_TAG, T \notin TAG) */ -public class HtmlParser { +public class HtmlParser implements JsonAble { /** * HTML is not nice to parse. We manage to get away with a relatively small number of parser states regardless. @@ -343,6 +345,10 @@ public class HtmlParser { return false; } } + + public JSONObject serialize() { + return new JSONObject(this); + } } /* diff --git a/src/main/model/html/TextNode.java b/src/main/model/html/TextNode.java index f6d3ce1..2e89326 100644 --- a/src/main/model/html/TextNode.java +++ b/src/main/model/html/TextNode.java @@ -1,11 +1,13 @@ package model.html; import model.util.Node; +import org.json.JSONObject; +import persistance.JsonAble; /** * This TextNode class represents raw text, with no nested tags. */ -public class TextNode implements Node { +public class TextNode implements Node, JsonAble { private String text = ""; /** @@ -24,4 +26,9 @@ public class TextNode implements Node { public String getData() { return getText(); } + + @Override + public JSONObject serialize() { + return new JSONObject(this); + } } diff --git a/src/main/model/util/Node.java b/src/main/model/util/Node.java index 4057fab..a6fedaf 100644 --- a/src/main/model/util/Node.java +++ b/src/main/model/util/Node.java @@ -1,5 +1,7 @@ package model.util; +import org.json.JSONObject; + /** * This Node represents an abstract relationship between ElementNode and TextNode. * It's extremely helpful / necessary for Lists of arbitrary ElementNodes/TextNodes. @@ -7,4 +9,6 @@ package model.util; public interface Node { // Return a representation of the Node. Useful for debugging. public String getData(); + + public JSONObject serialize(); } diff --git a/src/main/persistance/JsonAble.java b/src/main/persistance/JsonAble.java new file mode 100644 index 0000000..408cb06 --- /dev/null +++ b/src/main/persistance/JsonAble.java @@ -0,0 +1,7 @@ +package persistance; + +import org.json.JSONObject; + +public interface JsonAble { + public JSONObject serialize(); +} diff --git a/src/main/persistance/JsonUtils.java b/src/main/persistance/JsonUtils.java new file mode 100644 index 0000000..4e11a18 --- /dev/null +++ b/src/main/persistance/JsonUtils.java @@ -0,0 +1,44 @@ +package persistance; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class JsonUtils { + + /** + * REQUIRES: A valid filepath path, a writeable JSONObject json + * EFFECTS: writes a String to a file + */ + public static void writeToFile(JSONArray json, String path) { + PrintWriter writer; + try { + writer = new PrintWriter(path); + writer.print(json); + writer.close(); + } catch (Exception e) { + System.out.printf("Write to file failed with %s", e.toString()); + } + } + + /** + * REQUIRES: a path to a valid file containing JSONObject-serialized data + * EFFECTS: reads a serialized String into a JSONObject + */ + public static JSONArray readFromFile(String path) { + String content; + JSONArray deread; + try { + content = Files.readString(Paths.get(path)); + deread = new JSONArray(content); + } catch (Exception e) { + System.out.println("Read from file failed with %s"); + deread = new JSONArray(); + } + return deread; + } +} diff --git a/src/main/ui/BrowserApp.java b/src/main/ui/BrowserApp.java index 8a50ea4..194f9e1 100644 --- a/src/main/ui/BrowserApp.java +++ b/src/main/ui/BrowserApp.java @@ -4,7 +4,11 @@ import model.html.ElementNode; import model.html.HtmlParser; import model.html.TextNode; import model.util.Node; +import org.json.JSONArray; +import org.json.JSONObject; +import persistance.JsonUtils; +import java.io.File; import java.nio.file.*; import java.util.*; @@ -14,37 +18,93 @@ import java.util.*; public class BrowserApp { private Scanner input; private static final String border = "==============================================="; + private static final String storagePath = "data/apus.cache"; + private String pathString; + private ArrayList parsed; + private ArrayDeque tabs; /** * EFFECTS: Renders an arbitrary HTML page and arbitrary HTML input. */ public BrowserApp() { println("apus: currently a barebones html/css renderer"); - println("please provide a path to a file (examples located in data/*):"); + this.input = new Scanner(System.in); + this.tabs = new ArrayDeque<>(); - input = new Scanner(System.in); - String pathString = input.next(); - Path path = Paths.get(pathString); - try { - String file = new String(Files.readAllBytes(path)); - HtmlParser parser = new HtmlParser(); - println(border); - renderHtml(parser.parseHtml(file)); - println(border); - ArrayList rawHtml = new ArrayList<>(); - rawHtml.add(file); - mainLoop(rawHtml, border, parser); - } catch (Exception e) { - println("Reading from the file failed with " + e.toString()); - println("Please try again."); - } + askToRestoreTabs(); + mainLoop(); } + /** + * EFFECTS: Asks the user if they'd like to restore previously closed tabs. + */ + private void askToRestoreTabs() { + if (new File(storagePath).length() > 2) { + println("Would you like to restore your previously closed tabs? (Y/N)"); + String answer; + while (true) { + answer = this.input.next(); + if (answer.equalsIgnoreCase("y")) { + restoreClosedTabs(); + break; + } else if (answer.equalsIgnoreCase("n")) { + JsonUtils.writeToFile(new JSONArray(), storagePath); + println("please provide a path to a file (examples located in data/*):"); + pathString = this.input.next(); + break; + } else { + println("Sorry, I didn't quite get that. Please try again."); + } + } + } else { + println("please provide a path to a file (examples located in data/*):"); + pathString = this.input.next(); + } + } /** * EFFECTS: Runs the main loop */ - private void mainLoop(ArrayList rawHtml, String border, HtmlParser parser) { + private void mainLoop() { + while (true) { + try { + Path path = Paths.get(pathString); + String file = new String(Files.readAllBytes(path)); + HtmlParser parser = new HtmlParser(); + parsed = parser.parseHtml(file); + println(border); + renderHtml(parsed); + println(border); + println("Page rendered. Input additional commands if desired."); + println("Impemented commands: newuri, newtab, nexttab, quit"); + handleInput(this.input.next()); + println(border); + } catch (Exception e) { + println("Reading from the file failed with " + e.toString()); + println("Please try again."); + } + } + } + + /** + * EFFECTS: restores previous closed tabs from a cache file. + */ + private void restoreClosedTabs() { + try { + JSONArray state = JsonUtils.readFromFile(storagePath); + for (int i = 0; i < state.length(); i++) { + println(state.get(i).getClass().getName()); + tabs.add((String) state.get(i)); + } + pathString = tabs.removeLast(); + } catch (Exception e) { + println("Restoring state from disk failed with " + e.toString()); + System.exit(0); + } + } + + /* + private void mainLoopII(ArrayList rawHtml, String border, HtmlParser parser) { while (true) { println("Page rendered. Input additional raw HTML if desired."); rawHtml.add(input.next()); @@ -55,7 +115,7 @@ public class BrowserApp { } println(border); } - } + }*/ /** * EFFECTS: Barebones HTML rendering. Iterates through a list of Nodes and their children and prints any text. @@ -70,6 +130,92 @@ public class BrowserApp { } } + /** + * EFFECTS: Handles user input after rendering an initial site + */ + private void handleInput(String input) { + switch (input) { + case "newuri": + println("please provide a path to a file (examples located in data/*):"); + pathString = this.input.next(); + break; + case "newtab": + this.tabs.add(pathString); + println("please provide a path to a file (examples located in data/*):"); + pathString = this.input.next(); + break; + case "nexttab": + this.tabs.add(pathString); + pathString = this.tabs.removeFirst(); + break; + case "quit": + handleQuit(); + System.exit(0); + break; + default: + println("Sorry, I didn't quite get that. Please try again."); + break; + } + } + + /** + * Helper function for the quit() case. + * EFFECTS: Asks a user whether they'd like to save their tabs, and exists the program. + */ + private void handleQuit() { + println("Would you like to save your currently opened tabs to disk? (Y/N)"); + String answer; + while (true) { + answer = this.input.next(); + if (answer.equalsIgnoreCase("y")) { + this.tabs.add(pathString); + JsonUtils.writeToFile(new JSONArray(tabs), storagePath); + break; + } else if (answer.equalsIgnoreCase("n")) { + JsonUtils.writeToFile(new JSONArray(), storagePath); + break; + } else { + println("Sorry, I didn't quite get that. Please try again."); + } + } + } + + /** + * EFFECTS: writes the current program configuration to the disk + */ + private void writeToDisk() { + ArrayList> jsonArray = new ArrayList<>(); + for (String p : tabs) { + ArrayList jsonArrayII = new ArrayList<>(); + try { + Path path = Paths.get(pathString); + String file = new String(Files.readAllBytes(path)); + HtmlParser parser = new HtmlParser(); + for (Node n : parser.parseHtml(file)) { + jsonArrayII.add(n.serialize()); + } + } catch (Exception e) { + System.out.printf("Failed to write to disk with %s", e); + } + jsonArray.add(jsonArrayII); + } + JsonUtils.writeToFile(new JSONArray(jsonArray), storagePath); + } + + /** + * EFFECTS: restores program state from a last written to state + */ + private void restoreFromDisk(JSONArray state) { + for (int i = 0; i < state.length(); i++) { + Object tab = state.get(i); + if (tab instanceof JSONArray) { + for (int j = 0; j < ((JSONArray) tab).length(); j++) { + tabs.add(((JSONArray) tab).toString()); + } + } + } + } + private void print(String toPrint) { System.out.print(toPrint); } -- cgit v1.2.3-70-g09d2