diff options
author | JJ | 2022-12-27 16:36:56 +0000 |
---|---|---|
committer | JJ | 2022-12-27 16:36:56 +0000 |
commit | 69c8bcbda55c4eb8e79b89736568781440484319 (patch) | |
tree | b1db42212037747ff7109b65816839a2047be512 | |
parent | c14e53775591cb4d75b486d21f4849552d5c7c8c (diff) |
Convert project to Java 19 (at long last)
-rw-r--r-- | .idea/misc.xml | 2 | ||||
-rw-r--r-- | README.md | 44 | ||||
-rw-r--r-- | src/main/model/BrowserState.java | 10 | ||||
-rw-r--r-- | src/main/model/css/CssParser.java | 169 | ||||
-rw-r--r-- | src/main/model/html/ElementNode.java | 18 | ||||
-rw-r--r-- | src/main/model/html/HtmlParser.java | 206 | ||||
-rw-r--r-- | src/main/model/html/TextNode.java | 18 | ||||
-rw-r--r-- | src/main/model/layout/BlockLayout.java | 10 | ||||
-rw-r--r-- | src/main/model/layout/DocumentLayout.java | 5 | ||||
-rw-r--r-- | src/main/model/layout/InlineLayout.java | 22 | ||||
-rw-r--r-- | src/main/model/layout/Layout.java | 6 | ||||
-rw-r--r-- | src/main/ui/BrowserApp.java | 57 | ||||
-rw-r--r-- | src/main/ui/BrowserBar.java | 75 | ||||
-rw-r--r-- | src/main/ui/BrowserCanvas.java | 29 | ||||
-rw-r--r-- | src/main/ui/BrowserWindow.java | 23 |
15 files changed, 201 insertions, 493 deletions
diff --git a/.idea/misc.xml b/.idea/misc.xml index f10fac3..ea393a7 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -7,7 +7,7 @@ </map> </option> </component> - <component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK"> + <component name="ProjectRootManager" version="2" languageLevel="JDK_19_PREVIEW" project-jdk-name="19" project-jdk-type="JavaSDK"> <output url="file://$PROJECT_DIR$/out" /> </component> </project>
\ No newline at end of file @@ -1,52 +1,12 @@ -# apus +# apus: a miniature web browser -## a local web browser - -apus (from _Gigantochloa apus_: the most common bamboo on the island of Java) is a local web browser. +apus (from _Gigantochloa apus_: the most common bamboo species on the island of Java) is a local web browser. It will parse and display HTML, Markdown, epub, and potentially other XML-based file formats. -It will not connect to the internet, and instead only parse local files given to it on the command line or eventually uploaded in a user interface. Hopefully nobody outside of this class will use it because it will be a standards-failing web browser written in Java Swing. This project is of interest to me because I enjoy writing parsers and have been meaning to play around with text and image rendering. I've heard that Java Swing has a native HTML rendering component. I hope to entirely disregard this, and instead reimplement it poorly. -## user stories - -- As a user, I want to be able to construct a structural representation of an HTML file. -- As a user, I want to be able to construct a structural representation of a CSS file. -- As a user, I want to be able to add multiple tabs to a list of open tabs. -- As a user, I want to be able to view a rendering of an arbitrary HTML file. - -- As a user, I want to be given the option to save my currently open tabs to disk when quitting. -- As a user, I want to be given the option to restore my previous tabs upon relaunching the application. - -## instructions for grader - -- You can generate the first required event of adding multiple Xs to a Y by creating a new tab by entering a path into the browser bar and pressing "Go". Observe that the tab is added to the tablist. -- You can generate the second required event of adding multiple Xs to a Y by creating a new tab by opening the tab menu, selecting a tab, and closing it. Observe that the tab is removed from the tablist. -- The panel in which all of the Xs added to a Y are displayed is the tablist. -- You can locate my visual component by observing the main browser window, which may render arbitrary paths and open tabs. -- You can save the state of my application by attempting to close it with tabs open. You will be asked if you would like to save your tabs. -- You can load the state of my application by attempting to open it after saving tabs. You will be asked if you would like to restore your tabs. - -## Phase IV: Task 2 - -``` -Fri Dec 02 20:53:00 PST 2022 -Added tab /home/apropos/Projects/website/j-james/index.html to tablist -Fri Dec 02 20:53:01 PST 2022 -Added tab /home/apropos/Projects/website/j-james/index.html to tablist -Fri Dec 02 20:53:07 PST 2022 -Removed tab /home/apropos/Projects/website/j-james/index.html from tablist -``` - -## Phase IV: Task 3 - -I think that the design structure shown by the UML diagram is overall pretty much fine, especially for a student project. -There are definitely some problems with it / things I'd do differently, however: -- The BrowserBar and BrowserCanvas have a mutual dependency with BrowserWindow: this doesn't make too much sense and was done because I needed to access BrowserWindow methods from within them. If I did it again, I would either nest the classes or use the Singleton design principle. -- The CssParser is not connected to anything else, as it went unused. I would like to use that class in the future - but I never fully finished implementing the HTML layout, so it didn't make sense to apply CSS. (The JsonUtils class is also unconnected, but that's just because it consists of only static methods.) - ## credits This project makes extensive use of the Javatuples library ([javatuples.org](https://www.javatuples.org/)). diff --git a/src/main/model/BrowserState.java b/src/main/model/BrowserState.java index da21c41..7b05f8c 100644 --- a/src/main/model/BrowserState.java +++ b/src/main/model/BrowserState.java @@ -2,13 +2,11 @@ package model; import java.util.ArrayDeque; -// This BrowserState function collects the stateful portions of the browser into one modelable class. +// This BrowserState class collects the stateful portions of the browser into one modelable class. public class BrowserState { private ArrayDeque<String> tabs; private String currentTab; - // EFFECTS: constructs a new BrowserState - // MODIFIES: this public BrowserState(ArrayDeque<String> tabs, String currentTab) { this.tabs = tabs; this.currentTab = currentTab; @@ -22,22 +20,16 @@ public class BrowserState { return this.currentTab; } - // MODIFIES: this - // EFFECTS: Sets the current tab public void setCurrentTab(String tab) { this.currentTab = tab; } - // MODIFIES: this - // EFFECTS: add a new tab public void addTab(String added) { if (!this.tabs.contains(added)) { this.tabs.add(added); } } - // MODIFIES: this - // EFFECTS: removes a tab from the tablist public void removeTab(String removed) { this.tabs.remove(removed); } diff --git a/src/main/model/css/CssParser.java b/src/main/model/css/CssParser.java index a382b14..8bf4059 100644 --- a/src/main/model/css/CssParser.java +++ b/src/main/model/css/CssParser.java @@ -60,10 +60,6 @@ public class CssParser { * however we do keep a previousChar value for dealing with (annoying) escaped quotes. * It should be fast - I'd say something about time complexity if I knew anything about time complexity. * No guarantees are made about invalid CSS files. Also, no guarantees are made about valid CSS files, lol. - * <br> - * REQUIRES: A valid CSS file, as a raw String. - * MODIFIES: this - * EFFECTS: Returns a parsed CSS representation as several nested ArrayLists and Pairs of Strings. */ public ArrayList<Pair<String, ArrayList<Pair<String, String>>>> parseCSS(String input) { @@ -71,77 +67,52 @@ public class CssParser { // System.out.print(state); // System.out.println(" " + c); switch (state) { - case SELECTORS: caseSelectors(c); - break; - case MEDIA_SELECTORS: caseMediaSelectors(c); - break; - case ATTRIBUTE: caseAttribute(c); - break; - case VALUE: caseValue(c); - break; - case SINGLE_QUOTES: caseSingleQuotes(c); - break; - case DOUBLE_QUOTES: caseDoubleQuotes(c); - break; + case SELECTORS -> caseSelectors(c); + case MEDIA_SELECTORS -> caseMediaSelectors(c); + case ATTRIBUTE -> caseAttribute(c); + case VALUE -> caseValue(c); + case SINGLE_QUOTES -> caseSingleQuotes(c); + case DOUBLE_QUOTES -> caseDoubleQuotes(c); } } return result; } - /** - * EFFECTS: Handles and updates parser state/buffers for a single character while in the SELECTORS state. - * See also: the (slightly wrong) context-free grammar commented at the start of this file. - * MODIFIES: this - */ + // Handles and updates parser state/buffers for a single character while in the SELECTORS state. + // See also: the (slightly wrong) context-free grammar commented at the start of this file. private void caseSelectors(char c) { switch (c) { - case '@': + case '@' -> { if (currentSelector.equals("")) { state = ParserState.MEDIA_SELECTORS; } else { currentSelector += c; } - break; - case '{': - state = ParserState.ATTRIBUTE; - break; - case ' ': case '\n': - break; + } + case '{' -> state = ParserState.ATTRIBUTE; + case ' ', '\n' -> {} // todo: do better than blindly create a string; pattern match on css selectors - default: - currentSelector += c; - break; + default -> currentSelector += c; } } - /** - * EFFECTS: Handles and updates parser state/buffers for a single character while in the MEDIA_SELECTORS state. - * MODIFIES: this - */ + // Handles and updates parser state/buffers for a single character while in the MEDIA_SELECTORS state. private void caseMediaSelectors(char c) { switch (c) { // todo: don't entirely disregard media queries, also split between @media/@... - case '{': + case '{' -> { state = ParserState.SELECTORS; - // discard currentSelector currentSelector = ""; - break; - default: - currentSelector += c; - break; + } + default -> currentSelector += c; } } - /** - * EFFECTS: Handles and updates parser state/buffers for a single character while in the ATTRIBUTE state. - * MODIFIES: this - */ + // Handles and updates parser state/buffers for a single character while in the ATTRIBUTE state. private void caseAttribute(char c) { switch (c) { - case ':': - state = ParserState.VALUE; - break; - case '}': + case ':' -> state = ParserState.VALUE; + case '}' -> { state = ParserState.SELECTORS; if (!currentValue.equals("") || !currentProperty.equals("")) { // System.out.println("something's wrong"); @@ -151,26 +122,20 @@ public class CssParser { result.add(new Pair<>(currentSelector, currentRule)); currentSelector = ""; currentRule = new ArrayList<>(); - break; - case ' ': case '\n': - break; - default: - currentProperty += c; - break; + } + case ' ', '\n' -> {} + default -> currentProperty += c; } } - /** - * EFFECTS: Handles and updates parser state/buffers for a single character while in the VALUE state. - * MODIFIES: this - */ + // Handles and updates parser state/buffers for a single character while in the VALUE state. private void caseValue(char c) { switch (c) { - case ';': + case ';' -> { state = ParserState.ATTRIBUTE; updateCurrentRule(); - break; - case '}': + } + case '}' -> { state = ParserState.SELECTORS; if (!currentValue.equals("") || !currentProperty.equals("")) { updateCurrentRule(); @@ -178,25 +143,22 @@ public class CssParser { result.add(new Pair<>(currentSelector, currentRule)); currentSelector = ""; currentRule = new ArrayList<>(); - break; + } // todo: handle spaces better: they're actually important inside values - case ' ': case '\n': break; // believe me, i think this is ugly too but it passes checkstyle - case '\'': + case ' ', '\n' -> {} + case '\'' -> { state = ParserState.SINGLE_QUOTES; currentValue += c; - break; - // intentional use of TERRIBLE SMOKING FOOTGUN behavior to check boxes - case '\"': state = ParserState.DOUBLE_QUOTES; - default: currentValue += c; - break; + } + case '\"' -> { + state = ParserState.DOUBLE_QUOTES; + currentValue += c; + } + default -> currentValue += c; } } - /** - * Helper function to check method length boxes. - * EFFECTS: Adds a new property to the current rule. - * MODIFIES: this - */ + // Helper function to check method length boxes. private void updateCurrentRule() { currentRule.add(new Pair<>(currentProperty, currentValue)); currentProperty = ""; @@ -205,13 +167,10 @@ public class CssParser { // todo: handle additional escaped characters, though what we have right now isn't bad - /** - * EFFECTS: Handles and updates parser state/buffers for a single character while in the SINGLE_QUOTES state. - * MODIFIES: this - */ + // Handles and updates parser state/buffers for a single character while in the SINGLE_QUOTES state. private void caseSingleQuotes(char c) { switch (c) { - case '\'': + case '\'' -> { if (previousChar != '\\') { state = ParserState.VALUE; // quotes in css are exclusively? for paths: so we want to include the quotes themselves @@ -223,21 +182,18 @@ public class CssParser { currentValue += c; previousChar = c; } - break; - default: + } + default -> { currentValue += c; previousChar = c; - break; + } } } - /** - * EFFECTS: Handles and updates parser state/buffers for a single character while in the DOUBLE_QUOTES state. - * MODIFIES: this - */ + // Handles and updates parser state/buffers for a single character while in the DOUBLE_QUOTES state. private void caseDoubleQuotes(char c) { switch (c) { - case '\"': + case '\"' -> { if (previousChar != '\\') { state = ParserState.VALUE; currentValue += c; @@ -247,11 +203,11 @@ public class CssParser { currentValue += c; previousChar = c; } - break; - default: + } + default -> { currentValue += c; previousChar = c; - break; + } } } @@ -260,8 +216,8 @@ public class CssParser { * When given an invalid string (i.e. "12p53x"), it will produce an invalid result instead of throwing. * However, it should parse every valid string correctly. * <br> - * REQUIRES: A string of the form [NUMBER][VALIDUNIT] - * EFFECTS: Returns a number, in pixels, that has been converted appropriately + * Takes a string of the form [NUMBER][VALIDUNIT] + * Returns a number, in pixels, that has been converted appropriately */ public static double parseUnits(String input) { String numbers = ""; @@ -286,26 +242,23 @@ public class CssParser { } /** - * REQUIRES: a String that is a unit, otherwise defaults to pixels - * EFFECTS: converts a value in some units to a value in pixels + * Takes a String that is a unit, otherwise defaults to pixels + * Converts the value in some units to a value in pixels */ private static double convertUnits(String units, double value) { - // god case/break is such a fault-provoking design i hate it - // good thing we avoid breaks entirely here lmao - switch (units) { + return switch (units) { // absolute units - case "px": return value; - case "pc": return value * 16; - case "pt": return value * (4.0 / 3.0); - case "cm": return value * 37.8; - case "mm": return value * 378; - case "Q": return value * 1512; - case "in": return value * 96; + case "px" -> value; + case "pc" -> value * 16; + case "pt" -> value * (4.0 / 3.0); + case "cm" -> value * 37.8; + case "mm" -> value * 378; + case "Q" -> value * 1512; + case "in" -> value * 96; // not handled: % em ex ch rem lh rlh vw vh vmin vmax vb vi svw svh lvw lvh dvw dvh - default: + default -> value; // System.out.printf("Unit %s not implemented, defaulting to %s in pixels...%n", units, value); - return value; - } + }; } } diff --git a/src/main/model/html/ElementNode.java b/src/main/model/html/ElementNode.java index 6f0a556..3faf4ae 100644 --- a/src/main/model/html/ElementNode.java +++ b/src/main/model/html/ElementNode.java @@ -8,15 +8,11 @@ import java.util.ArrayList; * This ElementNode class represents an HTML tag and nested tags. */ public class ElementNode implements Node { - private String tag; - private ArrayList<Pair<String,String>> attributes; + private final String tag; + private final ArrayList<Pair<String,String>> attributes; - private ArrayList<Node> children; + private final ArrayList<Node> children; - /** - * EFFECTS: Constructs a new ElementNode from the arguments provided. - * MODIFIES: this - */ public ElementNode(String tag, ArrayList<Pair<String, String>> attributes, ArrayList<Node> children) { this.tag = tag; this.attributes = attributes; @@ -25,8 +21,6 @@ public class ElementNode implements Node { /** * Overloads the constructor for ease of use. We often don't provide children, at first. - * EFFECTS: Constructs a new ElementNode from the arguments provided. - * MODIFIES: this */ public ElementNode(String tag, ArrayList<Pair<String, String>> attributes) { this(tag, attributes, new ArrayList<>()); @@ -34,17 +28,11 @@ public class ElementNode implements Node { /** * Overloads the constructor for ease of use. Should probably only be used for tests. - * EFFECTS: Constructs a new ElementNode from the arguments provided. - * MODIFIES: this */ public ElementNode(String tag) { this(tag, new ArrayList<>(), new ArrayList<>()); } - /** - * EFFECTS: Adds a child to the children ArrayList. - * MODIFIES: this - */ public void addChild(Node child) { this.children.add(child); } diff --git a/src/main/model/html/HtmlParser.java b/src/main/model/html/HtmlParser.java index e50f713..5e5e7ce 100644 --- a/src/main/model/html/HtmlParser.java +++ b/src/main/model/html/HtmlParser.java @@ -43,8 +43,6 @@ public class HtmlParser { private ParserState state; - // EFFECTS: constructs a new HTML parser. - // MODIFIES: this public HtmlParser() { result = new ArrayList<>(); unfinished = new ArrayDeque<>(); @@ -65,118 +63,76 @@ public class HtmlParser { // System.out.print(state); // System.out.println(" " + c + " " + currentText); switch (state) { - case HTML: caseHtml(c); - break; - case UNKNOWN_TAG: caseUnknownTag(c); - break; // FOOTGUN LANGUAGE DESIGN STRIKES AGAIN - case IGNORED: caseIgnored(c); - break; - case OPENING_TAG: caseOpeningTag(c); - break; - case CLOSING_TAG: caseClosingTag(c); - break; - case KEY: caseKey(c); - break; - case VALUE: caseValue(c); - break; - case SINGLE_QUOTES: caseSingleQuotes(c); - break; - case DOUBLE_QUOTES: caseDoubleQuotes(c); - break; + case HTML -> caseHtml(c); + case UNKNOWN_TAG -> caseUnknownTag(c); + case IGNORED -> caseIgnored(c); + case OPENING_TAG -> caseOpeningTag(c); + case CLOSING_TAG -> caseClosingTag(c); + case KEY -> caseKey(c); + case VALUE -> caseValue(c); + case SINGLE_QUOTES -> caseSingleQuotes(c); + case DOUBLE_QUOTES -> caseDoubleQuotes(c); } } return result; } - /** - * EFFECTS: Handles and updates parser state/buffers for a single character while in the HTML state. - * MODIFIES: this - */ private void caseHtml(char c) { switch (c) { - case '<': + case '<' -> { state = ParserState.UNKNOWN_TAG; if (!currentText.equals("")) { addNewTextNode(); } - break; // FOOTGUN LANGUAGE DESIGN - case ' ': case '\n': + } + case ' ', '\n' -> { if (previousChar != ' ') { currentText += ' '; } previousChar = ' '; - break; - default: + } + default -> { currentText += c; previousChar = c; - break; + } } } - /** - * EFFECTS: Handles and updates parser state/buffers for a single character while in the UNKNOWN_TAG state. - * MODIFIES: this - */ private void caseUnknownTag(char c) { switch (c) { - case '/': - state = ParserState.CLOSING_TAG; - break; - case '>': // Why would you put <> in your HTML??? go away + case '/' -> state = ParserState.CLOSING_TAG; + case '>' -> { // Why would you put <> in your HTML??? go away state = ParserState.HTML; currentText += "<>"; - break; - // For now, we'll straight-up ignore anything matching the <!...> syntax: + } + // For now, we'll straight up ignore anything matching the <!...> syntax: // i.e. comments, and <!DOCTYPE html> - case '!': - state = ParserState.IGNORED; - break; - default: + case '!' -> state = ParserState.IGNORED; + default -> { state = ParserState.OPENING_TAG; currentTag += c; - break; + } } } - /** - * EFFECTS: Handles and updates parser state/buffers for a single character while in the IGNORED state. - * MODIFIES: this - */ private void caseIgnored(char c) { switch (c) { - case '>': - state = ParserState.HTML; - break; - default: - break; + case '>' -> state = ParserState.HTML; + default -> {} } } - /** - * EFFECTS: Handles and updates parser state/buffers for a single character while in the OPENING_TAG state. - * MODIFIES: this - */ private void caseOpeningTag(char c) { switch (c) { - case '>': - addNewElementNode(); - break; - case ' ': case '\n': - state = ParserState.KEY; - break; - default: - currentTag += c; - break; + case '>' -> addNewElementNode(); + case ' ', '\n' -> state = ParserState.KEY; + default -> currentTag += c; } } - /** - * EFFECTS: Handles and updates parser state/buffers for a single character while in the CLOSING_TAG state. - * MODIFIES: this - */ private void caseClosingTag(char c) { switch (c) { - case '>': + case '>' -> { state = ParserState.HTML; // IMPORTANT: we don't validate that closing tags correspond to an open tag if (!isSelfClosingTag(currentTag)) { @@ -185,72 +141,46 @@ public class HtmlParser { } } currentTag = ""; - break; - case ' ': case '\n': - break; - default: - currentTag += c; - break; + } + case ' ', '\n' -> {} + default -> currentTag += c; } } - /** - * EFFECTS: Handles and updates parser state/buffers for a single character while in the KEY state. - * MODIFIES: this - */ private void caseKey(char c) { switch (c) { - case '>': - addNewElementNode(); - break; - case '=': - state = ParserState.VALUE; - break; - case ' ': case '\n': - break; - default: - currentKey += c; - break; + case '>' -> addNewElementNode(); + case '=' -> state = ParserState.VALUE; + case ' ', '\n' -> {} + default -> currentKey += c; } } - /** - * EFFECTS: Handles and updates parser state/buffers for a single character while in the VALUE state. - * MODIFIES: this - */ private void caseValue(char c) { switch (c) { - case '\'': state = ParserState.SINGLE_QUOTES; - break; - case '\"': state = ParserState.DOUBLE_QUOTES; - break; - case ' ': case '\n': + case '\'' -> state = ParserState.SINGLE_QUOTES; + case '\"' -> state = ParserState.DOUBLE_QUOTES; + case ' ', '\n' -> { state = ParserState.KEY; currentAttributes.add(new Pair<>(currentKey, currentValue)); currentKey = ""; currentValue = ""; - break; // THE FOOTGUN DESIGN STRIKES AGAIN - case '>': + } + case '>' -> { if (!currentKey.equals("") || !currentValue.equals("")) { currentAttributes.add(new Pair<>(currentKey, currentValue)); currentKey = ""; currentValue = ""; } addNewElementNode(); - break; - default: - currentValue += c; - break; + } + default -> currentValue += c; } } - /** - * EFFECTS: Handles and updates parser state/buffers for a single character while in the SINGLE_QUOTES state. - * MODIFIES: this - */ private void caseSingleQuotes(char c) { switch (c) { - case '\'': + case '\'' -> { if (previousChar != '\\') { state = ParserState.VALUE; previousChar = '\0'; @@ -259,21 +189,17 @@ public class HtmlParser { currentValue += c; previousChar = c; } - break; - default: + } + default -> { currentValue += c; previousChar = c; - break; + } } } - /** - * EFFECTS: Handles and updates parser state/buffers for a single character while in the DOUBLE_QUOTES state. - * MODIFIES: this - */ private void caseDoubleQuotes(char c) { switch (c) { - case '\"': + case '\"' -> { if (previousChar != '\\') { state = ParserState.VALUE; previousChar = '\0'; @@ -282,19 +208,15 @@ public class HtmlParser { currentValue += c; previousChar = c; } - break; // FOOTGUN LANGUAGE DESIGN - default: + } + default -> { currentValue += c; previousChar = c; - break; + } } } - /** - * Helper function to remove code duplication. - * EFFECTS: Creates and adds a new ElementNode from the current buffers to the unfinished and result stacks - * MODIFIES: this - */ + // Helper function to remove code duplication. private void addNewElementNode() { state = ParserState.HTML; ElementNode node = new ElementNode(currentTag, currentAttributes); @@ -313,11 +235,7 @@ public class HtmlParser { currentAttributes = new ArrayList<>(); } - /** - * Helper function to check method length boxes. - * EFFECTS: Creates and adds a new TextNode from the current buffers to the unfinished and result stacks - * MODIFIES: this - */ + // Helper function to check method length boxes. private void addNewTextNode() { if (unfinished.size() != 0) { unfinished.getLast().addChild(new TextNode(currentText)); @@ -328,21 +246,13 @@ public class HtmlParser { previousChar = '\0'; } - /** - * Simple helper function to check if a tag is self-closing. - * EFFECTS: Returns whether a String tag is a self-closing tag. - */ + // Simple helper function to check if a tag is self-closing. private static boolean isSelfClosingTag(String tag) { - switch (tag) { - case "input": case "param": - case "br": case "hr": case "wbr": - case "img": case "embed": case "area": - case "meta": case "base": case "link": - case "source": case "track": case "col": - return true; - default: - return false; - } + return switch (tag) { + case "input", "param", "br", "hr", "wbr", "img", "embed", "area", + "meta", "base", "link", "source", "track", "col" -> true; + default -> false; + }; } } diff --git a/src/main/model/html/TextNode.java b/src/main/model/html/TextNode.java index 464180f..c50ce0f 100644 --- a/src/main/model/html/TextNode.java +++ b/src/main/model/html/TextNode.java @@ -3,23 +3,9 @@ package model.html; /** * This TextNode class represents raw text, with no nested tags. */ -public class TextNode implements Node { - private String text = ""; - - /** - * EFFECTS: Creates a new TextNode from the provided String value. - * MODIFIES: this - */ - public TextNode(String text) { - this.text = text; - } - - public String getText() { - return this.text; - } - +public record TextNode(String text) implements Node { // We implement this method for easy debugging. public String getData() { - return getText(); + return text(); } } diff --git a/src/main/model/layout/BlockLayout.java b/src/main/model/layout/BlockLayout.java index f011847..9204b61 100644 --- a/src/main/model/layout/BlockLayout.java +++ b/src/main/model/layout/BlockLayout.java @@ -4,17 +4,13 @@ import model.html.Node; import java.awt.*; -// A block style layout. public class BlockLayout extends Layout { - // MODIFIES: this - // EFFECTS: constructs a new BlockLayout public BlockLayout(Node node, Layout parent) { super(node, parent); } - // MODIFIES: this - // EFFECTS: recursively constructs the layout tree + // recursively construct the layout tree public void layout() { this.setLocation(this.getParent().getLocation()); this.getPreviousSibling().ifPresent( @@ -28,8 +24,8 @@ public class BlockLayout extends Layout { child.layout(); this.setHeight(this.getHeight() + child.getHeight()); } - System.out.println(this.getAssociatedNode().getData() + this.getLocation()); - System.out.println(System.identityHashCode(this.getLocation())); +// System.out.println(this.getAssociatedNode().getData() + this.getLocation()); +// System.out.println(System.identityHashCode(this.getLocation())); // System.out.println(this.getAssociatedNode().getData() + this.getDimension()); } } diff --git a/src/main/model/layout/DocumentLayout.java b/src/main/model/layout/DocumentLayout.java index 92c0b00..2e9d778 100644 --- a/src/main/model/layout/DocumentLayout.java +++ b/src/main/model/layout/DocumentLayout.java @@ -1,9 +1,7 @@ package model.layout; import java.awt.*; -import java.util.ArrayList; -// root document layout style public class DocumentLayout extends Layout { /* @@ -15,8 +13,7 @@ public class DocumentLayout extends Layout { super(null, null); } - // MODIFIES: this - // EFFECTS: recursively constructs the layout tree + // recursively construct the layout tree public void layout() { this.setLocation(new Point(10, 20)); this.setDimension(new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT)); diff --git a/src/main/model/layout/InlineLayout.java b/src/main/model/layout/InlineLayout.java index 8e375d5..c4c51b5 100644 --- a/src/main/model/layout/InlineLayout.java +++ b/src/main/model/layout/InlineLayout.java @@ -6,19 +6,16 @@ import model.html.TextNode; import java.awt.*; -// Inline layout style public class InlineLayout extends Layout { private Point cursor; - // Constructs a new InlineLayout public InlineLayout(Node node, Layout parent) { super(node, parent); cursor = new Point(); } - // MODIFIES: this - // EFFECTS: recursively constructs the layout tree + // recursively construct the layout tree public void layout() { this.setLocation(this.getParent().getLocation()); this.getPreviousSibling().ifPresent( @@ -28,14 +25,17 @@ public class InlineLayout extends Layout { this.setCursor(this.getX(), this.getY()); Node node = this.getAssociatedNode(); - if (node instanceof TextNode) { - if (node.getData().length() > 5) { - this.setHeight(20); -// this.setWidth(this.getWidth() + node.getData().length()); + switch (node) { + case ElementNode e -> { + if (e.getTag().equals("a")) { + this.setX(this.getX() + this.getParent().getWidth()); + } } - } else if (node instanceof ElementNode) { - if (((ElementNode) node).getTag().equals("a")) { - this.setX(this.getX() + this.getParent().getWidth()); + default -> { + if (node.getData().length() > 5) { + this.setHeight(20); +// this.setWidth(this.getWidth() + node.getData().length()); + } } } diff --git a/src/main/model/layout/Layout.java b/src/main/model/layout/Layout.java index 4d9f48f..5a19302 100644 --- a/src/main/model/layout/Layout.java +++ b/src/main/model/layout/Layout.java @@ -31,8 +31,6 @@ public abstract class Layout { // the big function public abstract void layout(); - // MODIFIES: this - // EFFECTS: recursively constructs the layout tree public Layout(Node node, Layout parent) { this.associatedNode = node; @@ -47,8 +45,6 @@ public abstract class Layout { // eh, probably the best place to put this // parent MAY BE nil: a handy call to Optional.ofNullable allows this - // MODIFIES: this - // EFFECTS: recursively constructs the layout tree public static ArrayList<Layout> constructTree(ArrayList<Node> html, Layout parent) { ArrayDeque<Layout> result = new ArrayDeque<>(); for (Node node : html) { @@ -74,8 +70,6 @@ public abstract class Layout { return new ArrayList<>(result); // haha } - // MODIFIES: this - // EFFECTS: recursively constructs the layout tree public static DocumentLayout constructTree(ArrayList<Node> html) { DocumentLayout result = new DocumentLayout(); result.setChildren(constructTree(html, result)); diff --git a/src/main/ui/BrowserApp.java b/src/main/ui/BrowserApp.java index 520cac8..36f2005 100644 --- a/src/main/ui/BrowserApp.java +++ b/src/main/ui/BrowserApp.java @@ -1,9 +1,6 @@ package ui; -import model.html.ElementNode; -import model.html.HtmlParser; -import model.html.TextNode; -import model.html.Node; +import model.html.*; import java.nio.file.*; import java.util.*; @@ -18,24 +15,15 @@ public class BrowserApp { private ArrayList<Node> parsed; private ArrayDeque<String> tabs; - /** - * EFFECTS: Renders an arbitrary HTML page and arbitrary HTML input. - */ + // Renders an arbitrary HTML page and arbitrary HTML input. public BrowserApp() { println("apus: currently a barebones html/css renderer"); this.input = new Scanner(System.in); this.tabs = new ArrayDeque<>(); - mainLoop(); - } - /** - * EFFECTS: Runs the main loop - */ - private void mainLoop() { while (true) { try { - Path path = Paths.get(pathString); - String file = new String(Files.readAllBytes(path)); + String file = Files.readString(Path.of(pathString)); HtmlParser parser = new HtmlParser(); parsed = parser.parseHtml(file); println(border); @@ -52,43 +40,38 @@ public class BrowserApp { } } - /** - * EFFECTS: Barebones HTML rendering. Iterates through a list of Nodes and their children and prints any text. - */ + // Barebones HTML rendering. Iterates through a list of Nodes and their children and prints any text. private void renderHtml(ArrayList<Node> html) { for (Node node: html) { - if (node instanceof TextNode) { - println(node.getData()); - } else { - renderHtml(((ElementNode) node).getChildren()); + switch (node) { + case ElementNode e -> { + renderHtml(e.getChildren()); + } + default -> { + println(node.getData()); + } } } } - /** - * EFFECTS: Handles user input after rendering an initial site - */ + // Handles user input after rendering an initial site private void handleInput(String input) { switch (input) { - case "newuri": + case "newuri" -> { println("please provide a path to a file (examples located in data/*):"); pathString = this.input.next(); - break; - case "newtab": + } + 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": + } + case "nexttab" -> { this.tabs.add(pathString); pathString = this.tabs.removeFirst(); - break; - case "quit": - System.exit(0); - break; - default: - println("Sorry, I didn't quite get that. Please try again."); - break; + } + case "quit" -> System.exit(0); + default -> println("Sorry, I didn't quite get that. Please try again."); } } diff --git a/src/main/ui/BrowserBar.java b/src/main/ui/BrowserBar.java index e9db3c3..131fe19 100644 --- a/src/main/ui/BrowserBar.java +++ b/src/main/ui/BrowserBar.java @@ -2,19 +2,17 @@ package ui; import javax.swing.*; import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; +import java.awt.event.*; import java.util.*; -// BrowserBar public class BrowserBar extends JToolBar { - private BrowserWindow parent; + private final BrowserWindow parent; - private JPopupMenu tabMenu; - private JToggleButton tabButton; - private JTextField uriInput; -// private JButton saveTabsButton; - private JButton openUriButton; + private final JPopupMenu tabMenu; + private final JToggleButton tabButton; + private final JTextField uriInput; +// private final JButton saveTabsButton; + private final JButton openUriButton; public BrowserBar(BrowserWindow parent) { this.parent = parent; @@ -34,36 +32,28 @@ public class BrowserBar extends JToolBar { } - // EFFECTS: opens the content of the text field in the current tab private ActionListener openTab() { - return new ActionListener() { - @Override - public void actionPerformed(ActionEvent actionEvent) { - String uri = uriInput.getText(); - parent.render(uri); - addTab(uri); - } + return actionEvent -> { + String uri = uriInput.getText(); + parent.render(uri); + addTab(uri); }; } - // EFFECTS: adds a new tab pointing to URI in the background public void addTab(String tab) { JToggleButton tabButton = new JToggleButton(tab); - tabButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent actionEvent) { - int action = JOptionPane.showOptionDialog(null, - "Open or close this tab?", "apus", - JOptionPane.YES_NO_OPTION, JOptionPane.INFORMATION_MESSAGE, - null, new String[]{"Open", "Close"}, "Open"); - if (action == 0) { - parent.render(tab); - } else { - tabMenu.remove(tabButton); - tabMenu.setVisible(false); - parent.getBrowserState().removeTab(tab); - } + tabButton.addActionListener(actionEvent -> { + int action = JOptionPane.showOptionDialog(null, + "Open or close this tab?", "apus", + JOptionPane.YES_NO_OPTION, JOptionPane.INFORMATION_MESSAGE, + null, new String[]{"Open", "Close"}, "Open"); + if (action == 0) { + parent.render(tab); + } else { + tabMenu.remove(tabButton); + tabMenu.setVisible(false); + parent.getBrowserState().removeTab(tab); } }); @@ -71,20 +61,15 @@ public class BrowserBar extends JToolBar { parent.getBrowserState().addTab(tab); } - // MODIFIES: this - // EFFECTS: toggles the tab menu private ActionListener toggleTabMenu() { - return new ActionListener() { - @Override - public void actionPerformed(ActionEvent actionEvent) { - if (tabButton.isSelected()) { - Point location = tabButton.getLocationOnScreen(); - location.translate(0, 30); // fuck this method lol - tabMenu.setLocation(location); - tabMenu.setVisible(true); - } else { - tabMenu.setVisible(false); - } + return actionEvent -> { + if (tabButton.isSelected()) { + Point location = tabButton.getLocationOnScreen(); + location.translate(0, 30); // fuck this method lol + tabMenu.setLocation(location); + tabMenu.setVisible(true); + } else { + tabMenu.setVisible(false); } }; } diff --git a/src/main/ui/BrowserCanvas.java b/src/main/ui/BrowserCanvas.java index a58f6e1..43481ef 100644 --- a/src/main/ui/BrowserCanvas.java +++ b/src/main/ui/BrowserCanvas.java @@ -1,24 +1,17 @@ package ui; -import model.html.ElementNode; -import model.html.TextNode; -import model.html.Node; -import model.layout.DocumentLayout; -import model.layout.Layout; +import model.html.*; +import model.layout.*; import javax.swing.*; import java.awt.*; import java.util.*; public class BrowserCanvas extends JPanel { - private ArrayList<Node> html; - private DocumentLayout currentLayout; + private final DocumentLayout currentLayout; - // MODIFIES: this - // EFFECTS: constructs a BrowserCanvas object public BrowserCanvas(ArrayList<Node> html) { super(); - this.html = html; this.currentLayout = Layout.constructTree(html); printTree(this.currentLayout.getChildren()); } @@ -30,7 +23,6 @@ public class BrowserCanvas extends JPanel { } } - // EFFECTS: paints a component onto a canvas @Override public void paintComponent(Graphics g) { super.paintComponent(g); @@ -38,20 +30,6 @@ public class BrowserCanvas extends JPanel { renderHtml(this.currentLayout.getChildren(), g, location); } - // EFFECTS: naively renders our html file by printing text nodes - /*private void renderHtml(ArrayList<Node> html, Graphics g, Point location) { - for (Node node : html) { - if (node instanceof TextNode) { - if (node.getData().length() > 5) { - g.drawString(node.getData(), location.x, location.y); - location.translate(0, 20); - } - } else { - renderHtml(((ElementNode) node).getChildren(), g, location); - } - } - }*/ - private void renderHtml(ArrayList<Layout> tree, Graphics g, Point location) { for (Layout layout : tree) { // System.out.println(layout.getLocation()); @@ -67,5 +45,4 @@ public class BrowserCanvas extends JPanel { } } } - } diff --git a/src/main/ui/BrowserWindow.java b/src/main/ui/BrowserWindow.java index bd1cf7f..94bd2d8 100644 --- a/src/main/ui/BrowserWindow.java +++ b/src/main/ui/BrowserWindow.java @@ -5,11 +5,8 @@ import model.html.HtmlParser; import javax.swing.*; import java.awt.*; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayDeque; -import java.util.ArrayList; +import java.nio.file.*; +import java.util.*; // Broad JFrame usage taken from here: https://docs.oracle.com/javase/tutorial/uiswing/components/frame.html public class BrowserWindow extends JFrame { @@ -17,43 +14,33 @@ public class BrowserWindow extends JFrame { public static final int HEIGHT = 800; private BrowserCanvas canvas; - private BrowserBar browserBar; + private final BrowserBar browserBar; + private final BrowserState state; - private BrowserState state; - - // MODIFIES: this - // EFFECTS: creates a new BrowserWindow program for rendering pages public BrowserWindow() { super("apus"); state = new BrowserState(new ArrayDeque<>(), ""); canvas = new BrowserCanvas(new ArrayList<>()); -// render("data/example.html"); browserBar = new BrowserBar(this); getContentPane().add(browserBar, BorderLayout.SOUTH); -// pack(); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setSize(WIDTH, HEIGHT); render("/home/apropos/Projects/website/j-james/index.html"); -// render("data/example.hctml"); // browserBar.addTab("/home/apropos/Projects/website/j-james/index.html"); setVisible(true); } - // MODIFIES: this - // EFFECTS: Renders an arbitrary page public void render(String uri) { state.setCurrentTab(uri); remove(canvas); // System.out.println(state.getCurrentTab()); try { - Path path = Paths.get(state.getCurrentTab()); - String file = new String(Files.readAllBytes(path)); + String file = Files.readString(Path.of(state.getCurrentTab())); HtmlParser parser = new HtmlParser(); canvas = new BrowserCanvas(parser.parseHtml(file)); } catch (Exception e) { System.out.println("Could not read file, rendering empty page: " + e.getMessage()); - canvas = new BrowserCanvas(new ArrayList<>()); } add(canvas); |