From d0d2db70a47e0baddac4e449da10f2893b7ef885 Mon Sep 17 00:00:00 2001 From: Claudio Maggioni Date: Tue, 22 Nov 2022 13:43:15 +0100 Subject: [PATCH] Parser refactor complete --- .../java/com/github/dtschust/zork/Zork.java | 13 +-- .../dtschust/zork/ZorkConditionHas.java | 4 +- .../com/github/dtschust/zork/ZorkGame.java | 17 ++- .../github/dtschust/zork/parser/DOMUtils.java | 70 ------------ .../dtschust/zork/parser/ParserIOC.java | 28 +++++ .../github/dtschust/zork/parser/Property.java | 91 ++++++++++++++++ .../zork/parser/PropertyParseStrategy.java | 5 + .../parser/TriggerPropertyParseStrategy.java | 19 ++++ .../dtschust/zork/parser/ZorkParser.java | 43 ++++++++ .../dtschust/zork/parser/ZorkReader.java | 103 ------------------ .../dtschust/zork/parser/ZorkXMLParser.java | 47 ++++++++ .../zork/parser/builders/Parsers.java | 23 ---- .../builders/ZorkConditionParseStrategy.java | 25 ----- .../builders/ZorkContainerParseStrategy.java | 37 ------- .../builders/ZorkCreatureParseStrategy.java | 52 --------- .../builders/ZorkItemParseStrategy.java | 45 -------- .../parser/builders/ZorkParseStrategy.java | 7 -- .../builders/ZorkRoomParseStrategy.java | 47 -------- .../ZorkTriggerListParseStrategy.java | 39 ------- .../dtschust/zork/parser/dom/DOMElement.java | 83 ++++++++++++++ .../zork/parser/dom/DOMElementList.java | 35 ++++++ .../dtschust/zork/parser/dom/Elements.java | 44 -------- .../ZorkConditionParseStrategy.java | 25 +++++ .../ZorkContainerParseStrategy.java | 38 +++++++ .../strategies/ZorkCreatureParseStrategy.java | 54 +++++++++ .../strategies/ZorkItemParseStrategy.java | 46 ++++++++ .../strategies/ZorkRoomParseStrategy.java | 50 +++++++++ .../strategies/ZorkTriggerParseStrategy.java | 39 +++++++ .../dtschust/zork/repl/actions/AddAction.java | 6 +- .../zork/repl/actions/AttackAction.java | 2 +- .../zork/repl/actions/DeleteAction.java | 6 +- .../zork/repl/actions/DropItemAction.java | 2 +- .../zork/repl/actions/OpenAction.java | 2 +- .../dtschust/zork/repl/actions/PutAction.java | 2 +- .../zork/repl/actions/ReadAction.java | 2 +- .../zork/repl/actions/TakeAction.java | 4 +- .../zork/repl/actions/TurnOnAction.java | 2 +- .../zork/types/HasSetOfCollectable.java | 4 +- .../dtschust/zork/types/ZorkContainer.java | 5 +- .../zork/types/ZorkGameStatusType.java | 21 ++++ .../github/dtschust/zork/types/ZorkRoom.java | 3 +- 41 files changed, 656 insertions(+), 534 deletions(-) delete mode 100644 src/main/java/com/github/dtschust/zork/parser/DOMUtils.java create mode 100644 src/main/java/com/github/dtschust/zork/parser/ParserIOC.java create mode 100644 src/main/java/com/github/dtschust/zork/parser/Property.java create mode 100644 src/main/java/com/github/dtschust/zork/parser/PropertyParseStrategy.java create mode 100644 src/main/java/com/github/dtschust/zork/parser/TriggerPropertyParseStrategy.java create mode 100644 src/main/java/com/github/dtschust/zork/parser/ZorkParser.java delete mode 100644 src/main/java/com/github/dtschust/zork/parser/ZorkReader.java create mode 100644 src/main/java/com/github/dtschust/zork/parser/ZorkXMLParser.java delete mode 100644 src/main/java/com/github/dtschust/zork/parser/builders/Parsers.java delete mode 100644 src/main/java/com/github/dtschust/zork/parser/builders/ZorkConditionParseStrategy.java delete mode 100644 src/main/java/com/github/dtschust/zork/parser/builders/ZorkContainerParseStrategy.java delete mode 100644 src/main/java/com/github/dtschust/zork/parser/builders/ZorkCreatureParseStrategy.java delete mode 100644 src/main/java/com/github/dtschust/zork/parser/builders/ZorkItemParseStrategy.java delete mode 100644 src/main/java/com/github/dtschust/zork/parser/builders/ZorkParseStrategy.java delete mode 100644 src/main/java/com/github/dtschust/zork/parser/builders/ZorkRoomParseStrategy.java delete mode 100644 src/main/java/com/github/dtschust/zork/parser/builders/ZorkTriggerListParseStrategy.java create mode 100644 src/main/java/com/github/dtschust/zork/parser/dom/DOMElement.java create mode 100644 src/main/java/com/github/dtschust/zork/parser/dom/DOMElementList.java delete mode 100644 src/main/java/com/github/dtschust/zork/parser/dom/Elements.java create mode 100644 src/main/java/com/github/dtschust/zork/parser/strategies/ZorkConditionParseStrategy.java create mode 100644 src/main/java/com/github/dtschust/zork/parser/strategies/ZorkContainerParseStrategy.java create mode 100644 src/main/java/com/github/dtschust/zork/parser/strategies/ZorkCreatureParseStrategy.java create mode 100644 src/main/java/com/github/dtschust/zork/parser/strategies/ZorkItemParseStrategy.java create mode 100644 src/main/java/com/github/dtschust/zork/parser/strategies/ZorkRoomParseStrategy.java create mode 100644 src/main/java/com/github/dtschust/zork/parser/strategies/ZorkTriggerParseStrategy.java create mode 100644 src/main/java/com/github/dtschust/zork/types/ZorkGameStatusType.java diff --git a/src/main/java/com/github/dtschust/zork/Zork.java b/src/main/java/com/github/dtschust/zork/Zork.java index 0a8a4c3..58f7bb5 100644 --- a/src/main/java/com/github/dtschust/zork/Zork.java +++ b/src/main/java/com/github/dtschust/zork/Zork.java @@ -5,7 +5,7 @@ package com.github.dtschust.zork; -import com.github.dtschust.zork.parser.ZorkReader; +import com.github.dtschust.zork.parser.ParserIOC; import com.github.dtschust.zork.repl.ActionDispatcher; import com.github.dtschust.zork.types.ZorkContainer; import com.github.dtschust.zork.types.ZorkObject; @@ -13,16 +13,15 @@ import com.github.dtschust.zork.types.ZorkObject; import java.util.Iterator; import java.util.Scanner; -import static com.github.dtschust.zork.Zork.Type.*; +import static com.github.dtschust.zork.types.ZorkGameStatusType.*; /* And away we go*/ public class Zork { ZorkGame game; Scanner source = new Scanner(System.in); - public Zork(String filename) { - - game = new ZorkReader(filename).build(); + public Zork(final String filename) { + game = ParserIOC.xmlParser().parse(filename); game.changeRoom("Entrance"); /* Print out the first entrance description, starting the game!*/ @@ -147,8 +146,4 @@ public class Zork { } return skip; } - - - public enum Type {ROOM, ITEM, CONTAINER, CREATURE} - } diff --git a/src/main/java/com/github/dtschust/zork/ZorkConditionHas.java b/src/main/java/com/github/dtschust/zork/ZorkConditionHas.java index c616ee0..b389779 100644 --- a/src/main/java/com/github/dtschust/zork/ZorkConditionHas.java +++ b/src/main/java/com/github/dtschust/zork/ZorkConditionHas.java @@ -3,8 +3,8 @@ package com.github.dtschust.zork; import com.github.dtschust.zork.types.ZorkContainer; import com.github.dtschust.zork.types.ZorkRoom; -import static com.github.dtschust.zork.Zork.Type.CONTAINER; -import static com.github.dtschust.zork.Zork.Type.ROOM; +import static com.github.dtschust.zork.types.ZorkGameStatusType.CONTAINER; +import static com.github.dtschust.zork.types.ZorkGameStatusType.ROOM; /* Has conditions*/ diff --git a/src/main/java/com/github/dtschust/zork/ZorkGame.java b/src/main/java/com/github/dtschust/zork/ZorkGame.java index b0acdf9..428c2b3 100644 --- a/src/main/java/com/github/dtschust/zork/ZorkGame.java +++ b/src/main/java/com/github/dtschust/zork/ZorkGame.java @@ -1,6 +1,5 @@ package com.github.dtschust.zork; -import com.github.dtschust.zork.Zork.Type; import com.github.dtschust.zork.types.*; import java.util.HashMap; @@ -14,7 +13,7 @@ public class ZorkGame { private final ZorkMap items = new ZorkMap<>(); private final ZorkMap containers = new ZorkMap<>(); private final ZorkMap creatures = new ZorkMap<>(); - private final HashMap objectLookup = new HashMap<>(); + private final HashMap objectLookup = new HashMap<>(); private boolean running = false; private String currentRoom; @@ -39,11 +38,11 @@ public class ZorkGame { running = false; } - public Type getTypeFromLookup(String object) { + public ZorkGameStatusType getTypeFromLookup(String object) { return objectLookup.get(object); } - public void addObjectThroughLookup(Type type, ZorkObject object) { + public void addObjectThroughLookup(ZorkGameStatusType type, ZorkObject object) { putInMapGivenType(type, object); objectLookup.put(object.getName(), type); } @@ -52,20 +51,20 @@ public class ZorkGame { return values(cast, objectLookup.get(name)); } - public ZorkMap values(Class cast, Type type) { + public ZorkMap values(Class cast, ZorkGameStatusType type) { return (ZorkMap) getMapFromType(type); } - public ZorkObject get(Type type, String key) { + public ZorkObject get(ZorkGameStatusType type, String key) { return getMapFromType(type).get(key); } - public void put(Type type, ZorkObject object) { + public void put(ZorkGameStatusType type, ZorkObject object) { putInMapGivenType(type, object); } - private ZorkMap getMapFromType(Type type) { + private ZorkMap getMapFromType(ZorkGameStatusType type) { switch (type) { case ROOM: return rooms; @@ -80,7 +79,7 @@ public class ZorkGame { } } - private void putInMapGivenType(Type type, ZorkObject object) { + private void putInMapGivenType(ZorkGameStatusType type, ZorkObject object) { switch (type) { case ROOM: rooms.put((ZorkRoom) object); diff --git a/src/main/java/com/github/dtschust/zork/parser/DOMUtils.java b/src/main/java/com/github/dtschust/zork/parser/DOMUtils.java deleted file mode 100644 index 867e71a..0000000 --- a/src/main/java/com/github/dtschust/zork/parser/DOMUtils.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.github.dtschust.zork.parser; - -import org.w3c.dom.CharacterData; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -public final class DOMUtils { - private DOMUtils() { - } - - - /** - * Given a DOM element with one and only one child of text node type, returns the text as a string. If there is no - * such node, '?' is returned - * Get a string from an element (XML parsing stuff) - * - * @param e the element - * @return the text as string, or '?' - */ - public static String getInnerText(final Element e) { - final Node child = e.getFirstChild(); - return child instanceof CharacterData ? ((CharacterData) child).getData() : "?"; - } - - public static String getInnerTextByTagName(final Element element, final String elementName) { - return getInnerTextByTagName(element, elementName, Optional.empty()); - } - - public static String getInnerTextByTagName(final Element element, - final String name, - final String defaultValue) { - return getInnerTextByTagName(element, name, Optional.of(defaultValue)); - } - - public static String getInnerTextByTagName(final Element element, - final String elementName, - final Optional defaultValue) { - final NodeList field = element.getElementsByTagName(elementName); - - if (field.getLength() == 0) { - return defaultValue.orElseThrow(() -> - new IllegalArgumentException(elementName + " element count in container is not 1")); - } - - final Node first = field.item(0); - if (!(first instanceof Element)) { - // the contract of getElementsByTagName states that it returns a list of Element objects - throw new IllegalStateException("unreachable"); - } - - return getInnerText((Element) first); - } - - public static List childrenElements(final Element parent) { - final List elements = new ArrayList<>(); - final NodeList children = parent.getChildNodes(); - for (int i = 0; i < children.getLength(); i++) { - final Node item = children.item(i); - if (item instanceof Element) { - elements.add((Element) item); - } - } - return elements; - } -} diff --git a/src/main/java/com/github/dtschust/zork/parser/ParserIOC.java b/src/main/java/com/github/dtschust/zork/parser/ParserIOC.java new file mode 100644 index 0000000..c414ded --- /dev/null +++ b/src/main/java/com/github/dtschust/zork/parser/ParserIOC.java @@ -0,0 +1,28 @@ +package com.github.dtschust.zork.parser; + +import com.github.dtschust.zork.ZorkCondition; +import com.github.dtschust.zork.parser.strategies.*; +import com.github.dtschust.zork.types.ZorkContainer; +import com.github.dtschust.zork.types.ZorkCreature; +import com.github.dtschust.zork.types.ZorkItem; +import com.github.dtschust.zork.types.ZorkRoom; + +/** + * Inversion of control for Zork parse strategies + */ +public final class ParserIOC { + private static final PropertyParseStrategy condition = new ZorkConditionParseStrategy(); + private static final TriggerPropertyParseStrategy trigger = new ZorkTriggerParseStrategy(condition); + private static final PropertyParseStrategy container = new ZorkContainerParseStrategy(trigger); + private static final PropertyParseStrategy item = new ZorkItemParseStrategy(trigger); + private static final PropertyParseStrategy room = new ZorkRoomParseStrategy(trigger); + private static final PropertyParseStrategy creature = new ZorkCreatureParseStrategy(condition, trigger); + private static final ZorkParser xmlParser = new ZorkXMLParser(creature, container, item, room); + + private ParserIOC() { + } + + public static ZorkParser xmlParser() { + return xmlParser; + } +} diff --git a/src/main/java/com/github/dtschust/zork/parser/Property.java b/src/main/java/com/github/dtschust/zork/parser/Property.java new file mode 100644 index 0000000..6588092 --- /dev/null +++ b/src/main/java/com/github/dtschust/zork/parser/Property.java @@ -0,0 +1,91 @@ +package com.github.dtschust.zork.parser; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * A property is an encoding-agnostic representation of a 3-tuple made of: + * - A property name (string); + * - A property value (string); + * - An (optional) list of sub-properties. + */ +public interface Property { + + /** + * Returns the property name + * + * @return the property name + */ + String name(); + + /** + * Returns the property value + * + * @return the property value + */ + String value(); + + /** + * Returns a list of all sub-properties + * + * @return a list af all sub-properties + */ + List subProperties(); + + /** + * Returns a list of the sub-properties matching the given name + * + * @param name the name of the sub-properties + * @return a list of sub-properties + */ + List subPropertiesByName(String name); + + /** + * Given a sub-property name and an optional default value, returns: + * - The sub-property value for the first sub-property with the given name, or; + * - If no such sub-properties are found and if the default value is given, the default value, or; + * - nothing, throwing an exception, if no default value is given + * + * @param elementName the sub-property name + * @param defaultValue the default value or Optional.empty() + * @return the sub-property value + * @throws IllegalStateException when the default value is not given and no sub-property is found + */ + String subPropertyValue(String elementName, Optional defaultValue); + + /** + * Returns whether at least one sub-property with the given name is found + * + * @param name the sub-property name + * @return whether at least one sub-property with the given name is found + */ + boolean hasSubProperty(String name); + + /** + * Overload of {@link #subPropertyValue(String, Optional)} with empty default value + */ + default String subPropertyValue(String name) { + return subPropertyValue(name, Optional.empty()); + } + + /** + * Overload of {@link #subPropertyValue(String, Optional)} with the given default value + * boxed as an {@link Optional} + */ + default String subPropertyValue(String name, String defaultValue) { + return subPropertyValue(name, Optional.of(defaultValue)); + } + + /** + * Returns a list of property values for the sub-properties matching the given name + * + * @param propertyName the name of the sub-properties + * @return a list of sub-property values + */ + default List subPropertyValues(final String propertyName) { + return subPropertiesByName(propertyName).stream() + .map(Property::value) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/com/github/dtschust/zork/parser/PropertyParseStrategy.java b/src/main/java/com/github/dtschust/zork/parser/PropertyParseStrategy.java new file mode 100644 index 0000000..9b619fb --- /dev/null +++ b/src/main/java/com/github/dtschust/zork/parser/PropertyParseStrategy.java @@ -0,0 +1,5 @@ +package com.github.dtschust.zork.parser; + +public interface PropertyParseStrategy { + T parse(final Property source); +} diff --git a/src/main/java/com/github/dtschust/zork/parser/TriggerPropertyParseStrategy.java b/src/main/java/com/github/dtschust/zork/parser/TriggerPropertyParseStrategy.java new file mode 100644 index 0000000..0eab3c0 --- /dev/null +++ b/src/main/java/com/github/dtschust/zork/parser/TriggerPropertyParseStrategy.java @@ -0,0 +1,19 @@ +package com.github.dtschust.zork.parser; + +import com.github.dtschust.zork.ZorkTrigger; + +import java.util.function.Function; + +public interface TriggerPropertyParseStrategy { + ZorkTrigger parseTrigger(final Property source, final Property parent); + + /** + * Partial function application for the parseTrigger method able to define the parent element first + * + * @param parent the parent element + * @return a lambda mapping a source, a child element of `parent`, to ZorkTrigger objects + */ + default Function parse(final Property parent) { + return (source) -> this.parseTrigger(source, parent); + } +} diff --git a/src/main/java/com/github/dtschust/zork/parser/ZorkParser.java b/src/main/java/com/github/dtschust/zork/parser/ZorkParser.java new file mode 100644 index 0000000..e8d7ef4 --- /dev/null +++ b/src/main/java/com/github/dtschust/zork/parser/ZorkParser.java @@ -0,0 +1,43 @@ +package com.github.dtschust.zork.parser; + +import com.github.dtschust.zork.ZorkGame; +import com.github.dtschust.zork.types.*; + +import java.util.Map; + +import static com.github.dtschust.zork.types.ZorkGameStatusType.*; + +public abstract class ZorkParser { + private final Map> strategies; + + public ZorkParser(final PropertyParseStrategy creatureStrategy, + final PropertyParseStrategy containerStrategy, + final PropertyParseStrategy itemStrategy, + final PropertyParseStrategy roomStrategy) { + this.strategies = Map.ofEntries( + Map.entry(CREATURE, creatureStrategy), + Map.entry(CONTAINER, containerStrategy), + Map.entry(ITEM, itemStrategy), + Map.entry(ROOM, roomStrategy) + ); + } + + protected abstract Property getRootProperty(final String filename); + + public ZorkGame parse(final String filename) { + ZorkGame data = new ZorkGame(); + + final Property rootElement = getRootProperty(filename); + + // Every single first generation child is a room, container, creature, or item. So load them in + for (final Property element : rootElement.subProperties()) { + final String name = element.name(); + final ZorkGameStatusType t = ZorkGameStatusType.fromPropertyName(name) + .orElseThrow(() -> new IllegalStateException("Unexpected value: " + name)); + final ZorkObject built = strategies.get(t).parse(element); + data.addObjectThroughLookup(t, built); + } + + return data; + } +} diff --git a/src/main/java/com/github/dtschust/zork/parser/ZorkReader.java b/src/main/java/com/github/dtschust/zork/parser/ZorkReader.java deleted file mode 100644 index 5bc266f..0000000 --- a/src/main/java/com/github/dtschust/zork/parser/ZorkReader.java +++ /dev/null @@ -1,103 +0,0 @@ -package com.github.dtschust.zork.parser; - -import com.github.dtschust.zork.ZorkGame; -import com.github.dtschust.zork.parser.builders.Parsers; -import com.github.dtschust.zork.types.ZorkContainer; -import com.github.dtschust.zork.types.ZorkCreature; -import com.github.dtschust.zork.types.ZorkItem; -import com.github.dtschust.zork.types.ZorkRoom; -import org.w3c.dom.Element; - -import javax.xml.parsers.DocumentBuilderFactory; -import java.io.File; -import java.nio.channels.NonReadableChannelException; - -import static com.github.dtschust.zork.Zork.Type.*; - -public class ZorkReader { - - private final String filename; - - public ZorkReader(String filename) { - this.filename = filename; - } - - private static void addCreature(ZorkGame data, Element element) { - final ZorkCreature tempCreature = Parsers.creature.parse(element); - - /* Put each creature in the creatures hashmap, the generic object hashmap, and the object lookup hashmap*/ - data.addObjectThroughLookup(CREATURE, tempCreature); - } - - private static void addContainer(ZorkGame data, Element element) { - final ZorkContainer tempCont = Parsers.container.parse(element); - - /* Put each container in the containers hashmap, the generic object hashmap, and the objectlookup hashmap*/ - data.addObjectThroughLookup(CONTAINER, tempCont); - } - - private static void addItem(ZorkGame data, Element element) { - final ZorkItem tempItem = Parsers.item.parse(element); - - /* Put each item in the items hashmap, the generic objects hashmap, and store its type in object lookup */ - data.addObjectThroughLookup(ITEM, tempItem); - } - - private static void addRoom(ZorkGame data, Element element) { - final ZorkRoom tempRoom = Parsers.room.parse(element); - - /*Add this room to the rooms hashmap, put it in the generic objects hashmap, and store it's type in the objectlookup hashmap*/ - data.addObjectThroughLookup(ROOM, tempRoom); - } - - public ZorkGame build() { - ZorkGame data = new ZorkGame(); - - File file = new File(filename); - if (!file.canRead()) { - System.out.println("Error opening file. Exiting..."); - throw new NonReadableChannelException(); - } - - try { - /* Open the xml file*/ - DocumentBuilderFactory builder = DocumentBuilderFactory.newInstance(); - // Limit XML features to mitigate vulnerabilities - builder.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); - builder.setFeature("http://xml.org/sax/features/external-general-entities", false); - builder.setFeature("http://xml.org/sax/features/external-parameter-entities", false); - - Element rootElement = builder.newDocumentBuilder().parse(file).getDocumentElement(); - - /* Every single first generation child is a room, container, creature, or item. So load them in*/ - for (Element element : DOMUtils.childrenElements(rootElement)) { - switch (element.getTagName()) { - /* If it's a room ... */ - case "room": - addRoom(data, element); - break; - /* If it's an item... */ - case "item": - addItem(data, element); - break; - /* If it's a container... */ - case "container": - addContainer(data, element); - break; - /* And finally, if it's a creature...*/ - case "creature": - addCreature(data, element); - break; - default: - throw new IllegalStateException("Unexpected value: " + element.getTagName()); - } - } - } catch (Exception e) { - // e.printStackTrace(); - System.out.println("Invalid XML file, exiting"); - System.exit(-1); - } - - return data; - } -} diff --git a/src/main/java/com/github/dtschust/zork/parser/ZorkXMLParser.java b/src/main/java/com/github/dtschust/zork/parser/ZorkXMLParser.java new file mode 100644 index 0000000..3252d21 --- /dev/null +++ b/src/main/java/com/github/dtschust/zork/parser/ZorkXMLParser.java @@ -0,0 +1,47 @@ +package com.github.dtschust.zork.parser; + +import com.github.dtschust.zork.parser.dom.DOMElement; +import com.github.dtschust.zork.types.ZorkContainer; +import com.github.dtschust.zork.types.ZorkCreature; +import com.github.dtschust.zork.types.ZorkItem; +import com.github.dtschust.zork.types.ZorkRoom; +import org.w3c.dom.Element; + +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.File; +import java.nio.channels.NonReadableChannelException; + +public class ZorkXMLParser extends ZorkParser { + public ZorkXMLParser(final PropertyParseStrategy creatureStrategy, + final PropertyParseStrategy containerStrategy, + final PropertyParseStrategy itemStrategy, + final PropertyParseStrategy roomStrategy) { + super(creatureStrategy, containerStrategy, itemStrategy, roomStrategy); + } + + @Override + protected Property getRootProperty(String filename) { + File file = new File(filename); + if (!file.canRead()) { + System.out.println("Error opening file. Exiting..."); + throw new NonReadableChannelException(); + } + + try { + // Open the xml file + final DocumentBuilderFactory builder = DocumentBuilderFactory.newInstance(); + // Limit XML features to mitigate vulnerabilities + builder.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + builder.setFeature("http://xml.org/sax/features/external-general-entities", false); + builder.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + + final Element rootElement = builder.newDocumentBuilder().parse(file).getDocumentElement(); + return DOMElement.of(rootElement); + } catch (final Exception e) { + System.out.println("Invalid XML file, exiting"); + e.printStackTrace(); + System.exit(-1); + return null; // never reached + } + } +} diff --git a/src/main/java/com/github/dtschust/zork/parser/builders/Parsers.java b/src/main/java/com/github/dtschust/zork/parser/builders/Parsers.java deleted file mode 100644 index 12e200e..0000000 --- a/src/main/java/com/github/dtschust/zork/parser/builders/Parsers.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.github.dtschust.zork.parser.builders; - -import com.github.dtschust.zork.ZorkCondition; -import com.github.dtschust.zork.ZorkTrigger; -import com.github.dtschust.zork.types.ZorkContainer; -import com.github.dtschust.zork.types.ZorkCreature; -import com.github.dtschust.zork.types.ZorkItem; -import com.github.dtschust.zork.types.ZorkRoom; - -/** - * Inversion of control for Zork parse strategies - */ -public final class Parsers { - private static final ZorkParseStrategy condition = new ZorkConditionParseStrategy(); - private static final ZorkParseStrategy trigger = new ZorkTriggerListParseStrategy(condition); - public static final ZorkParseStrategy container = new ZorkContainerParseStrategy(trigger); - public static final ZorkParseStrategy item = new ZorkItemParseStrategy(trigger); - public static final ZorkParseStrategy room = new ZorkRoomParseStrategy(trigger); - public static final ZorkParseStrategy creature = new ZorkCreatureParseStrategy(condition, trigger); - - private Parsers() { - } -} diff --git a/src/main/java/com/github/dtschust/zork/parser/builders/ZorkConditionParseStrategy.java b/src/main/java/com/github/dtschust/zork/parser/builders/ZorkConditionParseStrategy.java deleted file mode 100644 index b8ec048..0000000 --- a/src/main/java/com/github/dtschust/zork/parser/builders/ZorkConditionParseStrategy.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.github.dtschust.zork.parser.builders; - -import com.github.dtschust.zork.ZorkCondition; -import com.github.dtschust.zork.ZorkConditionHas; -import com.github.dtschust.zork.ZorkConditionStatus; -import com.github.dtschust.zork.parser.DOMUtils; -import org.w3c.dom.Element; - -public class ZorkConditionParseStrategy implements ZorkParseStrategy { - @Override - public ZorkCondition parse(final Element conditionElement) { - if (conditionElement.getElementsByTagName("has").getLength() > 0) { - return new ZorkConditionHas( - DOMUtils.getInnerTextByTagName(conditionElement, "has"), - DOMUtils.getInnerTextByTagName(conditionElement, "object"), - DOMUtils.getInnerTextByTagName(conditionElement, "owner") - ); - } else { - return new ZorkConditionStatus( - DOMUtils.getInnerTextByTagName(conditionElement, "status"), - DOMUtils.getInnerTextByTagName(conditionElement, "object") - ); - } - } -} diff --git a/src/main/java/com/github/dtschust/zork/parser/builders/ZorkContainerParseStrategy.java b/src/main/java/com/github/dtschust/zork/parser/builders/ZorkContainerParseStrategy.java deleted file mode 100644 index 79d344c..0000000 --- a/src/main/java/com/github/dtschust/zork/parser/builders/ZorkContainerParseStrategy.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.github.dtschust.zork.parser.builders; - -import com.github.dtschust.zork.ZorkTrigger; -import com.github.dtschust.zork.parser.DOMUtils; -import com.github.dtschust.zork.parser.dom.Elements; -import com.github.dtschust.zork.types.ZorkContainer; -import org.w3c.dom.Element; - -import java.util.List; -import java.util.stream.Collectors; - -public class ZorkContainerParseStrategy implements ZorkParseStrategy { - private final ZorkParseStrategy triggerStrategy; - - public ZorkContainerParseStrategy(ZorkParseStrategy triggerStrategy) { - this.triggerStrategy = triggerStrategy; - } - - @Override - public ZorkContainer parse(final Element element) { - final String name = DOMUtils.getInnerTextByTagName(element, "name", ""); - - final String description = DOMUtils.getInnerTextByTagName(element, "description", ""); - - final List triggers = Elements.byTagName(element, "trigger").stream() - .map(triggerStrategy::parse) - .collect(Collectors.toList()); - - final String status = DOMUtils.getInnerTextByTagName(element, "status", ""); - - final List accepts = Elements.innerTextByTagName(element, "accept"); - - final List items = Elements.innerTextByTagName(element, "item"); - - return new ZorkContainer(name, description, status, items, accepts, triggers); - } -} diff --git a/src/main/java/com/github/dtschust/zork/parser/builders/ZorkCreatureParseStrategy.java b/src/main/java/com/github/dtschust/zork/parser/builders/ZorkCreatureParseStrategy.java deleted file mode 100644 index 54a8a32..0000000 --- a/src/main/java/com/github/dtschust/zork/parser/builders/ZorkCreatureParseStrategy.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.github.dtschust.zork.parser.builders; - -import com.github.dtschust.zork.ZorkCondition; -import com.github.dtschust.zork.ZorkTrigger; -import com.github.dtschust.zork.parser.DOMUtils; -import com.github.dtschust.zork.parser.dom.Elements; -import com.github.dtschust.zork.types.ZorkCreature; -import org.w3c.dom.Element; - -import java.util.List; -import java.util.stream.Collectors; - -public class ZorkCreatureParseStrategy implements ZorkParseStrategy { - - private final ZorkParseStrategy conditionStrategy; - private final ZorkParseStrategy triggerStrategy; - - public ZorkCreatureParseStrategy(final ZorkParseStrategy conditionStrategy, - final ZorkParseStrategy triggerStrategy) { - this.conditionStrategy = conditionStrategy; - this.triggerStrategy = triggerStrategy; - } - - @Override - public ZorkCreature parse(final Element source) { - // Get all possible creature attributes - final List conditions = Elements.byTagName(source, "attack").stream() - .flatMap(e -> Elements.byTagName(e, "condition").stream()) - .map(conditionStrategy::parse) - .collect(Collectors.toList()); - - final List prints = Elements.byTagName(source, "attack").stream() - .flatMap(e -> Elements.innerTextByTagName(e, "print").stream()) - .collect(Collectors.toList()); - - final List actions = Elements.byTagName(source, "attack").stream() - .flatMap(e -> Elements.innerTextByTagName(e, "action").stream()) - .collect(Collectors.toList()); - - final List vulnerabilities = Elements.innerTextByTagName(source, "vulnerability"); - - final List triggers = Elements.byTagName(source, "trigger").stream() - .map(triggerStrategy::parse) - .collect(Collectors.toList()); - - final String name = DOMUtils.getInnerTextByTagName(source, "name", ""); - final String description = DOMUtils.getInnerTextByTagName(source, "description", ""); - final String status = DOMUtils.getInnerTextByTagName(source, "status", ""); - - return new ZorkCreature(name, description, status, triggers, vulnerabilities, conditions, prints, actions); - } -} diff --git a/src/main/java/com/github/dtschust/zork/parser/builders/ZorkItemParseStrategy.java b/src/main/java/com/github/dtschust/zork/parser/builders/ZorkItemParseStrategy.java deleted file mode 100644 index 9af4bab..0000000 --- a/src/main/java/com/github/dtschust/zork/parser/builders/ZorkItemParseStrategy.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.github.dtschust.zork.parser.builders; - -import com.github.dtschust.zork.ZorkTrigger; -import com.github.dtschust.zork.parser.DOMUtils; -import com.github.dtschust.zork.parser.dom.Elements; -import com.github.dtschust.zork.types.ZorkItem; -import org.w3c.dom.Element; - -import java.util.List; -import java.util.stream.Collectors; - -public class ZorkItemParseStrategy implements ZorkParseStrategy { - - private final ZorkParseStrategy triggerStrategy; - - public ZorkItemParseStrategy(final ZorkParseStrategy triggerStrategy) { - this.triggerStrategy = triggerStrategy; - } - - @Override - public ZorkItem parse(final Element source) { - final List prints = Elements.byTagName(source, "turnon").stream() - .flatMap(e -> Elements.innerTextByTagName(e, "print").stream()) - .collect(Collectors.toList()); - - final List actions = Elements.byTagName(source, "turnon").stream() - .flatMap(e -> Elements.innerTextByTagName(e, "action").stream()) - .collect(Collectors.toList()); - - final List triggers = Elements.byTagName(source, "trigger").stream() - .map(triggerStrategy::parse) - .collect(Collectors.toList()); - - /* Get all possible item attributes*/ - return new ZorkItem( - DOMUtils.getInnerTextByTagName(source, "name", ""), - DOMUtils.getInnerTextByTagName(source, "description", ""), - DOMUtils.getInnerTextByTagName(source, "status", ""), - DOMUtils.getInnerTextByTagName(source, "writing", ""), - triggers, - prints, - actions - ); - } -} diff --git a/src/main/java/com/github/dtschust/zork/parser/builders/ZorkParseStrategy.java b/src/main/java/com/github/dtschust/zork/parser/builders/ZorkParseStrategy.java deleted file mode 100644 index d25aa6b..0000000 --- a/src/main/java/com/github/dtschust/zork/parser/builders/ZorkParseStrategy.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.github.dtschust.zork.parser.builders; - -import org.w3c.dom.Element; - -public interface ZorkParseStrategy { - T parse(Element source); -} diff --git a/src/main/java/com/github/dtschust/zork/parser/builders/ZorkRoomParseStrategy.java b/src/main/java/com/github/dtschust/zork/parser/builders/ZorkRoomParseStrategy.java deleted file mode 100644 index 90f8aaf..0000000 --- a/src/main/java/com/github/dtschust/zork/parser/builders/ZorkRoomParseStrategy.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.github.dtschust.zork.parser.builders; - -import com.github.dtschust.zork.ZorkTrigger; -import com.github.dtschust.zork.parser.DOMUtils; -import com.github.dtschust.zork.parser.dom.Elements; -import com.github.dtschust.zork.types.ZorkDirection; -import com.github.dtschust.zork.types.ZorkRoom; -import org.w3c.dom.Element; - -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -public class ZorkRoomParseStrategy implements ZorkParseStrategy { - - private final ZorkParseStrategy triggerStrategy; - - public ZorkRoomParseStrategy(final ZorkParseStrategy triggerStrategy) { - this.triggerStrategy = triggerStrategy; - } - - @Override - public ZorkRoom parse(final Element source) { - // Get all possible Room attributes - final String name = DOMUtils.getInnerTextByTagName(source, "name", ""); - final String description = DOMUtils.getInnerTextByTagName(source, "description", ""); - final String type = DOMUtils.getInnerTextByTagName(source, "type", "regular"); - - final List triggers = Elements.byTagName(source, "trigger").stream() - .map(triggerStrategy::parse) - .collect(Collectors.toList()); - - final String status = DOMUtils.getInnerTextByTagName(source, "status", ""); - - final List items = Elements.innerTextByTagName(source, "item"); - final List creatures = Elements.innerTextByTagName(source, "creature"); - final List containers = Elements.innerTextByTagName(source, "container"); - - final Map borders = Elements.byTagName(source, "border").stream() - .collect(Collectors.toMap( - e -> ZorkDirection.fromLong(DOMUtils.getInnerTextByTagName(e, "direction")), - e -> DOMUtils.getInnerTextByTagName(e, "name") - )); - - return new ZorkRoom(name, description, type, status, triggers, borders, containers, items, creatures); - } -} diff --git a/src/main/java/com/github/dtschust/zork/parser/builders/ZorkTriggerListParseStrategy.java b/src/main/java/com/github/dtschust/zork/parser/builders/ZorkTriggerListParseStrategy.java deleted file mode 100644 index 6e81326..0000000 --- a/src/main/java/com/github/dtschust/zork/parser/builders/ZorkTriggerListParseStrategy.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.github.dtschust.zork.parser.builders; - -import com.github.dtschust.zork.ZorkCommand; -import com.github.dtschust.zork.ZorkCondition; -import com.github.dtschust.zork.ZorkTrigger; -import com.github.dtschust.zork.parser.DOMUtils; -import com.github.dtschust.zork.parser.dom.Elements; -import org.w3c.dom.Element; - -import java.util.List; -import java.util.stream.Collectors; - -public class ZorkTriggerListParseStrategy implements ZorkParseStrategy { - - private final ZorkParseStrategy conditionStrategy; - - public ZorkTriggerListParseStrategy(final ZorkParseStrategy conditionStrategy) { - this.conditionStrategy = conditionStrategy; - } - - @Override - public ZorkTrigger parse(final Element trigger) { - final String type = DOMUtils.getInnerTextByTagName((Element) trigger.getParentNode(), "type", "single"); - - final List commands = Elements.byTagName(trigger, "command").stream() - .map(DOMUtils::getInnerText) - .map(ZorkCommand::new) - .collect(Collectors.toList()); - - final List conditions = Elements.byTagName(trigger, "condition").stream() - .map(conditionStrategy::parse) - .collect(Collectors.toList()); - - final List prints = Elements.innerTextByTagName(trigger, "print"); - final List actions = Elements.innerTextByTagName(trigger, "action"); - - return new ZorkTrigger(type, conditions, commands, prints, actions); - } -} diff --git a/src/main/java/com/github/dtschust/zork/parser/dom/DOMElement.java b/src/main/java/com/github/dtschust/zork/parser/dom/DOMElement.java new file mode 100644 index 0000000..0d31c4f --- /dev/null +++ b/src/main/java/com/github/dtschust/zork/parser/dom/DOMElement.java @@ -0,0 +1,83 @@ +package com.github.dtschust.zork.parser.dom; + +import com.github.dtschust.zork.parser.Property; +import org.w3c.dom.CharacterData; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class DOMElement implements Property { + private final Element backing; + + public DOMElement(final Element backing) { + this.backing = backing; + } + + public static DOMElement of(final Element backing) { + return new DOMElement(backing); + } + + /** + * Given a DOM element with one and only one child of text node type, returns the text as a string. If there is no + * such node, '?' is returned + * Get a string from an element (XML parsing stuff) + * + * @return the text as string, or '?' + */ + @Override + public String value() { + final Node child = backing.getFirstChild(); + return child instanceof CharacterData ? ((CharacterData) child).getData() : "?"; + } + + @Override + public List subPropertiesByName(final String name) { + return DOMElementList.byTagName(backing, name); + } + + @Override + public String subPropertyValue(final String elementName, + final Optional defaultValue) { + final NodeList field = backing.getElementsByTagName(elementName); + + if (field.getLength() == 0) { + return defaultValue.orElseThrow(() -> + new IllegalArgumentException(elementName + " element count in container is not 1")); + } + + final Node first = field.item(0); + if (!(first instanceof Element)) { + // the contract of getElementsByTagName states that it returns a list of Element objects + throw new IllegalStateException("unreachable"); + } + + return DOMElement.of((Element) first).value(); + } + + @Override + public String name() { + return backing.getTagName(); + } + + @Override + public boolean hasSubProperty(final String name) { + return backing.getElementsByTagName(name).getLength() > 0; + } + + @Override + public List subProperties() { + final List elements = new ArrayList<>(); + final NodeList children = backing.getChildNodes(); + for (int i = 0; i < children.getLength(); i++) { + final Node item = children.item(i); + if (item instanceof Element) { + elements.add(DOMElement.of((Element) item)); + } + } + return elements; + } +} diff --git a/src/main/java/com/github/dtschust/zork/parser/dom/DOMElementList.java b/src/main/java/com/github/dtschust/zork/parser/dom/DOMElementList.java new file mode 100644 index 0000000..57bbc85 --- /dev/null +++ b/src/main/java/com/github/dtschust/zork/parser/dom/DOMElementList.java @@ -0,0 +1,35 @@ +package com.github.dtschust.zork.parser.dom; + +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import java.util.AbstractList; +import java.util.RandomAccess; + +public class DOMElementList extends AbstractList implements RandomAccess { + private final NodeList list; + + private DOMElementList(final NodeList l) { + list = l; + } + + static DOMElementList byTagName(final Element parent, final String name) { + return new DOMElementList(parent.getElementsByTagName(name)); + } + + @Override + public DOMElement get(int index) { + final Node e = list.item(index); + if (!(e instanceof Element)) { + // the contract of getElementsByTagName states that it returns a list of Element objects + throw new IllegalStateException("unreachable"); + } + return DOMElement.of((Element) e); + } + + @Override + public int size() { + return list.getLength(); + } +} diff --git a/src/main/java/com/github/dtschust/zork/parser/dom/Elements.java b/src/main/java/com/github/dtschust/zork/parser/dom/Elements.java deleted file mode 100644 index a233edb..0000000 --- a/src/main/java/com/github/dtschust/zork/parser/dom/Elements.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.github.dtschust.zork.parser.dom; - -import com.github.dtschust.zork.parser.DOMUtils; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - -import java.util.AbstractList; -import java.util.List; -import java.util.RandomAccess; -import java.util.stream.Collectors; - -public class Elements extends AbstractList implements RandomAccess { - private final NodeList list; - - private Elements(final NodeList l) { - list = l; - } - - public static Elements byTagName(final Element parent, final String name) { - return new Elements(parent.getElementsByTagName(name)); - } - - public static List innerTextByTagName(final Element parent, final String name) { - return Elements.byTagName(parent, name).stream() - .map(DOMUtils::getInnerText) - .collect(Collectors.toList()); - } - - @Override - public Element get(int index) { - final Node e = list.item(index); - if (!(e instanceof Element)) { - // the contract of getElementsByTagName states that it returns a list of Element objects - throw new IllegalStateException("unreachable"); - } - return (Element) e; - } - - @Override - public int size() { - return list.getLength(); - } -} diff --git a/src/main/java/com/github/dtschust/zork/parser/strategies/ZorkConditionParseStrategy.java b/src/main/java/com/github/dtschust/zork/parser/strategies/ZorkConditionParseStrategy.java new file mode 100644 index 0000000..4bc033c --- /dev/null +++ b/src/main/java/com/github/dtschust/zork/parser/strategies/ZorkConditionParseStrategy.java @@ -0,0 +1,25 @@ +package com.github.dtschust.zork.parser.strategies; + +import com.github.dtschust.zork.ZorkCondition; +import com.github.dtschust.zork.ZorkConditionHas; +import com.github.dtschust.zork.ZorkConditionStatus; +import com.github.dtschust.zork.parser.Property; +import com.github.dtschust.zork.parser.PropertyParseStrategy; + +public class ZorkConditionParseStrategy implements PropertyParseStrategy { + @Override + public ZorkCondition parse(final Property source) { + if (source.hasSubProperty("has")) { + return new ZorkConditionHas( + source.subPropertyValue("has"), + source.subPropertyValue("object"), + source.subPropertyValue("owner") + ); + } else { + return new ZorkConditionStatus( + source.subPropertyValue("status"), + source.subPropertyValue("object") + ); + } + } +} diff --git a/src/main/java/com/github/dtschust/zork/parser/strategies/ZorkContainerParseStrategy.java b/src/main/java/com/github/dtschust/zork/parser/strategies/ZorkContainerParseStrategy.java new file mode 100644 index 0000000..0bd449e --- /dev/null +++ b/src/main/java/com/github/dtschust/zork/parser/strategies/ZorkContainerParseStrategy.java @@ -0,0 +1,38 @@ +package com.github.dtschust.zork.parser.strategies; + +import com.github.dtschust.zork.ZorkTrigger; +import com.github.dtschust.zork.parser.Property; +import com.github.dtschust.zork.parser.PropertyParseStrategy; +import com.github.dtschust.zork.parser.TriggerPropertyParseStrategy; +import com.github.dtschust.zork.types.ZorkContainer; + +import java.util.List; +import java.util.stream.Collectors; + +public class ZorkContainerParseStrategy implements PropertyParseStrategy { + private final TriggerPropertyParseStrategy triggerStrategy; + + public ZorkContainerParseStrategy(TriggerPropertyParseStrategy triggerStrategy) { + this.triggerStrategy = triggerStrategy; + } + + @Override + public ZorkContainer parse(final Property element) { + + final String name = element.subPropertyValue("name", ""); + + final String description = element.subPropertyValue("description", ""); + + final List triggers = element.subPropertiesByName("trigger").stream() + .map(triggerStrategy.parse(element)) + .collect(Collectors.toList()); + + final String status = element.subPropertyValue("status", ""); + + final List accepts = element.subPropertyValues("accept"); + + final List items = element.subPropertyValues("item"); + + return new ZorkContainer(name, description, status, items, accepts, triggers); + } +} diff --git a/src/main/java/com/github/dtschust/zork/parser/strategies/ZorkCreatureParseStrategy.java b/src/main/java/com/github/dtschust/zork/parser/strategies/ZorkCreatureParseStrategy.java new file mode 100644 index 0000000..d2a9ca7 --- /dev/null +++ b/src/main/java/com/github/dtschust/zork/parser/strategies/ZorkCreatureParseStrategy.java @@ -0,0 +1,54 @@ +package com.github.dtschust.zork.parser.strategies; + +import com.github.dtschust.zork.ZorkCondition; +import com.github.dtschust.zork.ZorkTrigger; +import com.github.dtschust.zork.parser.Property; +import com.github.dtschust.zork.parser.PropertyParseStrategy; +import com.github.dtschust.zork.parser.TriggerPropertyParseStrategy; +import com.github.dtschust.zork.types.ZorkCreature; + +import java.util.List; +import java.util.stream.Collectors; + +public class ZorkCreatureParseStrategy implements PropertyParseStrategy { + + private final PropertyParseStrategy conditionStrategy; + private final TriggerPropertyParseStrategy triggerStrategy; + + public ZorkCreatureParseStrategy(final PropertyParseStrategy conditionStrategy, + final TriggerPropertyParseStrategy triggerStrategy) { + this.conditionStrategy = conditionStrategy; + this.triggerStrategy = triggerStrategy; + } + + @Override + public ZorkCreature parse(final Property source) { + // Get all possible creature attributes + final List conditions = source.subPropertiesByName("attack").stream() + .flatMap(e -> e.subPropertiesByName("condition").stream()) + .map(conditionStrategy::parse) + .collect(Collectors.toList()); + + final List prints = source.subPropertiesByName("attack").stream() + .flatMap(e -> e.subPropertyValues("print").stream()) + .collect(Collectors.toList()); + + final List actions = source.subPropertiesByName("attack").stream() + .flatMap(e -> e.subPropertyValues("action").stream()) + .collect(Collectors.toList()); + + final List vulnerabilities = source.subPropertyValues("vulnerability"); + + final List triggers = source.subPropertiesByName("trigger").stream() + .map(triggerStrategy.parse(source)) + .collect(Collectors.toList()); + + final String name = source.subPropertyValue("name", ""); + + final String description = source.subPropertyValue("description", ""); + + final String status = source.subPropertyValue("status", ""); + + return new ZorkCreature(name, description, status, triggers, vulnerabilities, conditions, prints, actions); + } +} diff --git a/src/main/java/com/github/dtschust/zork/parser/strategies/ZorkItemParseStrategy.java b/src/main/java/com/github/dtschust/zork/parser/strategies/ZorkItemParseStrategy.java new file mode 100644 index 0000000..b0d2f58 --- /dev/null +++ b/src/main/java/com/github/dtschust/zork/parser/strategies/ZorkItemParseStrategy.java @@ -0,0 +1,46 @@ +package com.github.dtschust.zork.parser.strategies; + +import com.github.dtschust.zork.ZorkTrigger; +import com.github.dtschust.zork.parser.Property; +import com.github.dtschust.zork.parser.PropertyParseStrategy; +import com.github.dtschust.zork.parser.TriggerPropertyParseStrategy; +import com.github.dtschust.zork.types.ZorkItem; + +import java.util.List; +import java.util.stream.Collectors; + +public class ZorkItemParseStrategy implements PropertyParseStrategy { + + private final TriggerPropertyParseStrategy triggerStrategy; + + public ZorkItemParseStrategy(final TriggerPropertyParseStrategy triggerStrategy) { + this.triggerStrategy = triggerStrategy; + } + + @Override + public ZorkItem parse(final Property source) { + final List prints = source.subPropertiesByName("turnon").stream() + .flatMap(e -> e.subPropertyValues("print").stream()) + .collect(Collectors.toList()); + + final List actions = source.subPropertiesByName("turnon").stream() + .flatMap(e -> e.subPropertyValues("action").stream()) + .collect(Collectors.toList()); + + final List triggers = source.subPropertiesByName("trigger").stream() + .map(triggerStrategy.parse(source)) + .collect(Collectors.toList()); + + /* Get all possible item attributes*/ + + return new ZorkItem( + source.subPropertyValue("name", ""), + source.subPropertyValue("description", ""), + source.subPropertyValue("status", ""), + source.subPropertyValue("writing", ""), + triggers, + prints, + actions + ); + } +} diff --git a/src/main/java/com/github/dtschust/zork/parser/strategies/ZorkRoomParseStrategy.java b/src/main/java/com/github/dtschust/zork/parser/strategies/ZorkRoomParseStrategy.java new file mode 100644 index 0000000..662ada7 --- /dev/null +++ b/src/main/java/com/github/dtschust/zork/parser/strategies/ZorkRoomParseStrategy.java @@ -0,0 +1,50 @@ +package com.github.dtschust.zork.parser.strategies; + +import com.github.dtschust.zork.ZorkTrigger; +import com.github.dtschust.zork.parser.Property; +import com.github.dtschust.zork.parser.PropertyParseStrategy; +import com.github.dtschust.zork.parser.TriggerPropertyParseStrategy; +import com.github.dtschust.zork.types.ZorkDirection; +import com.github.dtschust.zork.types.ZorkRoom; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class ZorkRoomParseStrategy implements PropertyParseStrategy { + + private final TriggerPropertyParseStrategy triggerStrategy; + + public ZorkRoomParseStrategy(final TriggerPropertyParseStrategy triggerStrategy) { + this.triggerStrategy = triggerStrategy; + } + + @Override + public ZorkRoom parse(final Property source) { + // Get all possible Room attributes + + final String name = source.subPropertyValue("name", ""); + + final String description = source.subPropertyValue("description", ""); + + final String type = source.subPropertyValue("type", "regular"); + + final List triggers = source.subPropertiesByName("trigger").stream() + .map(triggerStrategy.parse(source)) + .collect(Collectors.toList()); + + final String status = source.subPropertyValue("status", ""); + + final List items = source.subPropertyValues("item"); + final List creatures = source.subPropertyValues("creature"); + final List containers = source.subPropertyValues("container"); + + final Map borders = source.subPropertiesByName("border").stream() + .collect(Collectors.toMap( + e -> ZorkDirection.fromLong(e.subPropertyValue("direction")), + e -> e.subPropertyValue("name") + )); + + return new ZorkRoom(name, description, type, status, triggers, borders, containers, items, creatures); + } +} diff --git a/src/main/java/com/github/dtschust/zork/parser/strategies/ZorkTriggerParseStrategy.java b/src/main/java/com/github/dtschust/zork/parser/strategies/ZorkTriggerParseStrategy.java new file mode 100644 index 0000000..53c5caf --- /dev/null +++ b/src/main/java/com/github/dtschust/zork/parser/strategies/ZorkTriggerParseStrategy.java @@ -0,0 +1,39 @@ +package com.github.dtschust.zork.parser.strategies; + +import com.github.dtschust.zork.ZorkCommand; +import com.github.dtschust.zork.ZorkCondition; +import com.github.dtschust.zork.ZorkTrigger; +import com.github.dtschust.zork.parser.Property; +import com.github.dtschust.zork.parser.PropertyParseStrategy; +import com.github.dtschust.zork.parser.TriggerPropertyParseStrategy; + +import java.util.List; +import java.util.stream.Collectors; + +public class ZorkTriggerParseStrategy implements TriggerPropertyParseStrategy { + + private final PropertyParseStrategy conditionStrategy; + + public ZorkTriggerParseStrategy(final PropertyParseStrategy conditionStrategy) { + this.conditionStrategy = conditionStrategy; + } + + @Override + public ZorkTrigger parseTrigger(final Property source, final Property parent) { + final String type = parent.subPropertyValue("type", "single"); + + final List commands = source.subPropertiesByName("command").stream() + .map(Property::value) + .map(ZorkCommand::new) + .collect(Collectors.toList()); + + final List conditions = source.subPropertiesByName("condition").stream() + .map(conditionStrategy::parse) + .collect(Collectors.toList()); + + final List prints = source.subPropertyValues("print"); + final List actions = source.subPropertyValues("action"); + + return new ZorkTrigger(type, conditions, commands, prints, actions); + } +} diff --git a/src/main/java/com/github/dtschust/zork/repl/actions/AddAction.java b/src/main/java/com/github/dtschust/zork/repl/actions/AddAction.java index 5224c93..f2ca77b 100644 --- a/src/main/java/com/github/dtschust/zork/repl/actions/AddAction.java +++ b/src/main/java/com/github/dtschust/zork/repl/actions/AddAction.java @@ -1,9 +1,9 @@ package com.github.dtschust.zork.repl.actions; -import com.github.dtschust.zork.Zork.Type; import com.github.dtschust.zork.ZorkGame; import com.github.dtschust.zork.repl.Action; import com.github.dtschust.zork.types.HasSetOfCollectable; +import com.github.dtschust.zork.types.ZorkGameStatusType; import com.github.dtschust.zork.types.ZorkObject; import java.util.List; @@ -29,8 +29,8 @@ public class AddAction implements Action { final String destination = arguments.get(3); try { - Type destType = game.getTypeFromLookup(destination); - Type objType = game.getTypeFromLookup(object); + ZorkGameStatusType destType = game.getTypeFromLookup(destination); + ZorkGameStatusType objType = game.getTypeFromLookup(object); ZorkObject tempObject = game.get(destType, destination); ((HasSetOfCollectable) tempObject).getSetFromType(objType).add(object); game.put(destType, tempObject); diff --git a/src/main/java/com/github/dtschust/zork/repl/actions/AttackAction.java b/src/main/java/com/github/dtschust/zork/repl/actions/AttackAction.java index d377ad8..3c942fa 100644 --- a/src/main/java/com/github/dtschust/zork/repl/actions/AttackAction.java +++ b/src/main/java/com/github/dtschust/zork/repl/actions/AttackAction.java @@ -6,7 +6,7 @@ import com.github.dtschust.zork.types.ZorkCreature; import java.util.List; -import static com.github.dtschust.zork.Zork.Type.CREATURE; +import static com.github.dtschust.zork.types.ZorkGameStatusType.CREATURE; /** * Attempt an attack, do you feel lucky? diff --git a/src/main/java/com/github/dtschust/zork/repl/actions/DeleteAction.java b/src/main/java/com/github/dtschust/zork/repl/actions/DeleteAction.java index d677ce1..3b86517 100644 --- a/src/main/java/com/github/dtschust/zork/repl/actions/DeleteAction.java +++ b/src/main/java/com/github/dtschust/zork/repl/actions/DeleteAction.java @@ -1,22 +1,22 @@ package com.github.dtschust.zork.repl.actions; -import com.github.dtschust.zork.Zork.Type; import com.github.dtschust.zork.ZorkGame; import com.github.dtschust.zork.repl.Action; import com.github.dtschust.zork.types.HasSetOfCollectable; +import com.github.dtschust.zork.types.ZorkGameStatusType; import com.github.dtschust.zork.types.ZorkObject; import com.github.dtschust.zork.types.ZorkRoom; import java.util.List; import java.util.Set; -import static com.github.dtschust.zork.Zork.Type.*; +import static com.github.dtschust.zork.types.ZorkGameStatusType.*; /** * Delete: figure out what object it is and delete it accordingly. Rooms are especially tricky */ public class DeleteAction implements Action { - private static void deleteElementFromSpace(ZorkGame game, Type space, Type element, String object) { + private static void deleteElementFromSpace(ZorkGame game, ZorkGameStatusType space, ZorkGameStatusType element, String object) { for (ZorkObject tempObject : game.values(ZorkObject.class, space)) { Set set = ((HasSetOfCollectable) tempObject).getSetFromType(element); if (set.contains(object)) { diff --git a/src/main/java/com/github/dtschust/zork/repl/actions/DropItemAction.java b/src/main/java/com/github/dtschust/zork/repl/actions/DropItemAction.java index 2cfa580..c96c76a 100644 --- a/src/main/java/com/github/dtschust/zork/repl/actions/DropItemAction.java +++ b/src/main/java/com/github/dtschust/zork/repl/actions/DropItemAction.java @@ -6,7 +6,7 @@ import com.github.dtschust.zork.types.ZorkRoom; import java.util.List; -import static com.github.dtschust.zork.Zork.Type.ROOM; +import static com.github.dtschust.zork.types.ZorkGameStatusType.ROOM; public class DropItemAction implements Action { @Override diff --git a/src/main/java/com/github/dtschust/zork/repl/actions/OpenAction.java b/src/main/java/com/github/dtschust/zork/repl/actions/OpenAction.java index 7ebc2d6..8c23d9b 100644 --- a/src/main/java/com/github/dtschust/zork/repl/actions/OpenAction.java +++ b/src/main/java/com/github/dtschust/zork/repl/actions/OpenAction.java @@ -6,7 +6,7 @@ import com.github.dtschust.zork.types.ZorkContainer; import java.util.List; -import static com.github.dtschust.zork.Zork.Type.CONTAINER; +import static com.github.dtschust.zork.types.ZorkGameStatusType.CONTAINER; public class OpenAction implements Action { @Override diff --git a/src/main/java/com/github/dtschust/zork/repl/actions/PutAction.java b/src/main/java/com/github/dtschust/zork/repl/actions/PutAction.java index aac6b69..f8dcdd1 100644 --- a/src/main/java/com/github/dtschust/zork/repl/actions/PutAction.java +++ b/src/main/java/com/github/dtschust/zork/repl/actions/PutAction.java @@ -6,7 +6,7 @@ import com.github.dtschust.zork.types.ZorkContainer; import java.util.List; -import static com.github.dtschust.zork.Zork.Type.CONTAINER; +import static com.github.dtschust.zork.types.ZorkGameStatusType.CONTAINER; public class PutAction implements Action { @Override diff --git a/src/main/java/com/github/dtschust/zork/repl/actions/ReadAction.java b/src/main/java/com/github/dtschust/zork/repl/actions/ReadAction.java index dcf9db3..588c965 100644 --- a/src/main/java/com/github/dtschust/zork/repl/actions/ReadAction.java +++ b/src/main/java/com/github/dtschust/zork/repl/actions/ReadAction.java @@ -6,7 +6,7 @@ import com.github.dtschust.zork.types.ZorkItem; import java.util.List; -import static com.github.dtschust.zork.Zork.Type.ITEM; +import static com.github.dtschust.zork.types.ZorkGameStatusType.ITEM; public class ReadAction implements Action { @Override diff --git a/src/main/java/com/github/dtschust/zork/repl/actions/TakeAction.java b/src/main/java/com/github/dtschust/zork/repl/actions/TakeAction.java index 90cc64b..ac0ee33 100644 --- a/src/main/java/com/github/dtschust/zork/repl/actions/TakeAction.java +++ b/src/main/java/com/github/dtschust/zork/repl/actions/TakeAction.java @@ -7,8 +7,8 @@ import com.github.dtschust.zork.types.ZorkRoom; import java.util.List; -import static com.github.dtschust.zork.Zork.Type.CONTAINER; -import static com.github.dtschust.zork.Zork.Type.ROOM; +import static com.github.dtschust.zork.types.ZorkGameStatusType.CONTAINER; +import static com.github.dtschust.zork.types.ZorkGameStatusType.ROOM; public class TakeAction implements Action { @Override diff --git a/src/main/java/com/github/dtschust/zork/repl/actions/TurnOnAction.java b/src/main/java/com/github/dtschust/zork/repl/actions/TurnOnAction.java index f1b343a..58fbd6f 100644 --- a/src/main/java/com/github/dtschust/zork/repl/actions/TurnOnAction.java +++ b/src/main/java/com/github/dtschust/zork/repl/actions/TurnOnAction.java @@ -6,7 +6,7 @@ import com.github.dtschust.zork.types.ZorkItem; import java.util.List; -import static com.github.dtschust.zork.Zork.Type.ITEM; +import static com.github.dtschust.zork.types.ZorkGameStatusType.ITEM; /** * Turn on an item diff --git a/src/main/java/com/github/dtschust/zork/types/HasSetOfCollectable.java b/src/main/java/com/github/dtschust/zork/types/HasSetOfCollectable.java index 85a8d85..e4b4a08 100644 --- a/src/main/java/com/github/dtschust/zork/types/HasSetOfCollectable.java +++ b/src/main/java/com/github/dtschust/zork/types/HasSetOfCollectable.java @@ -1,12 +1,10 @@ package com.github.dtschust.zork.types; -import com.github.dtschust.zork.Zork; - import java.util.Set; public interface HasSetOfCollectable { - default Set getSetFromType(Zork.Type type) { + default Set getSetFromType(ZorkGameStatusType type) { throw new UnsupportedOperationException(); } } diff --git a/src/main/java/com/github/dtschust/zork/types/ZorkContainer.java b/src/main/java/com/github/dtschust/zork/types/ZorkContainer.java index 17b0106..b85ee62 100644 --- a/src/main/java/com/github/dtschust/zork/types/ZorkContainer.java +++ b/src/main/java/com/github/dtschust/zork/types/ZorkContainer.java @@ -1,11 +1,10 @@ package com.github.dtschust.zork.types; -import com.github.dtschust.zork.Zork; import com.github.dtschust.zork.ZorkTrigger; import java.util.*; -import static com.github.dtschust.zork.Zork.Type.ITEM; +import static com.github.dtschust.zork.types.ZorkGameStatusType.ITEM; /* Container*/ public class ZorkContainer extends ZorkObject implements HasSetOfCollectable { @@ -59,7 +58,7 @@ public class ZorkContainer extends ZorkObject implements HasSetOfCollectable { } @Override - public Set getSetFromType(Zork.Type type) { + public Set getSetFromType(ZorkGameStatusType type) { if (type.equals(ITEM)) return items; throw new IllegalStateException("Unexpected value: " + type); diff --git a/src/main/java/com/github/dtschust/zork/types/ZorkGameStatusType.java b/src/main/java/com/github/dtschust/zork/types/ZorkGameStatusType.java new file mode 100644 index 0000000..d195fc5 --- /dev/null +++ b/src/main/java/com/github/dtschust/zork/types/ZorkGameStatusType.java @@ -0,0 +1,21 @@ +package com.github.dtschust.zork.types; + +import java.util.EnumSet; +import java.util.Optional; + +public enum ZorkGameStatusType { + ROOM("room"), + ITEM("item"), + CONTAINER("container"), + CREATURE("creature"); + + private final String propertyName; + + ZorkGameStatusType(final String propertyName) { + this.propertyName = propertyName; + } + + public static Optional fromPropertyName(final String propertyName) { + return EnumSet.allOf(ZorkGameStatusType.class).stream().filter(e -> e.propertyName.equals(propertyName)).findFirst(); + } +} diff --git a/src/main/java/com/github/dtschust/zork/types/ZorkRoom.java b/src/main/java/com/github/dtschust/zork/types/ZorkRoom.java index 99c6692..540d9b4 100644 --- a/src/main/java/com/github/dtschust/zork/types/ZorkRoom.java +++ b/src/main/java/com/github/dtschust/zork/types/ZorkRoom.java @@ -1,7 +1,6 @@ package com.github.dtschust.zork.types; -import com.github.dtschust.zork.Zork; import com.github.dtschust.zork.ZorkTrigger; import java.util.*; @@ -32,7 +31,7 @@ public class ZorkRoom extends ZorkObject implements HasSetOfCollectable { } @Override - public Set getSetFromType(Zork.Type type) { + public Set getSetFromType(ZorkGameStatusType type) { switch (type) { case CONTAINER: return getContainer();