Parser refactor complete

This commit is contained in:
Claudio Maggioni 2022-11-22 13:43:15 +01:00
parent 9564a205dc
commit d0d2db70a4
41 changed files with 656 additions and 534 deletions

View File

@ -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}
}

View File

@ -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*/

View File

@ -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<ZorkItem> items = new ZorkMap<>();
private final ZorkMap<ZorkContainer> containers = new ZorkMap<>();
private final ZorkMap<ZorkCreature> creatures = new ZorkMap<>();
private final HashMap<String, Type> objectLookup = new HashMap<>();
private final HashMap<String, ZorkGameStatusType> 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 <T extends ZorkObject> ZorkMap<T> values(Class<T> cast, Type type) {
public <T extends ZorkObject> ZorkMap<T> values(Class<T> cast, ZorkGameStatusType type) {
return (ZorkMap<T>) 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<? extends ZorkObject> getMapFromType(Type type) {
private ZorkMap<? extends ZorkObject> 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);

View File

@ -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<String> 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<Element> childrenElements(final Element parent) {
final List<Element> 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;
}
}

View File

@ -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<ZorkCondition> condition = new ZorkConditionParseStrategy();
private static final TriggerPropertyParseStrategy trigger = new ZorkTriggerParseStrategy(condition);
private static final PropertyParseStrategy<ZorkContainer> container = new ZorkContainerParseStrategy(trigger);
private static final PropertyParseStrategy<ZorkItem> item = new ZorkItemParseStrategy(trigger);
private static final PropertyParseStrategy<ZorkRoom> room = new ZorkRoomParseStrategy(trigger);
private static final PropertyParseStrategy<ZorkCreature> creature = new ZorkCreatureParseStrategy(condition, trigger);
private static final ZorkParser xmlParser = new ZorkXMLParser(creature, container, item, room);
private ParserIOC() {
}
public static ZorkParser xmlParser() {
return xmlParser;
}
}

View File

@ -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<? extends Property> 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<? extends Property> 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<String> 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<String> subPropertyValues(final String propertyName) {
return subPropertiesByName(propertyName).stream()
.map(Property::value)
.collect(Collectors.toList());
}
}

View File

@ -0,0 +1,5 @@
package com.github.dtschust.zork.parser;
public interface PropertyParseStrategy<T> {
T parse(final Property source);
}

View File

@ -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<Property, ZorkTrigger> parse(final Property parent) {
return (source) -> this.parseTrigger(source, parent);
}
}

View File

@ -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<ZorkGameStatusType, PropertyParseStrategy<? extends ZorkObject>> strategies;
public ZorkParser(final PropertyParseStrategy<ZorkCreature> creatureStrategy,
final PropertyParseStrategy<ZorkContainer> containerStrategy,
final PropertyParseStrategy<ZorkItem> itemStrategy,
final PropertyParseStrategy<ZorkRoom> 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;
}
}

View File

@ -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;
}
}

View File

@ -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<ZorkCreature> creatureStrategy,
final PropertyParseStrategy<ZorkContainer> containerStrategy,
final PropertyParseStrategy<ZorkItem> itemStrategy,
final PropertyParseStrategy<ZorkRoom> 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
}
}
}

View File

@ -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<ZorkCondition> condition = new ZorkConditionParseStrategy();
private static final ZorkParseStrategy<ZorkTrigger> trigger = new ZorkTriggerListParseStrategy(condition);
public static final ZorkParseStrategy<ZorkContainer> container = new ZorkContainerParseStrategy(trigger);
public static final ZorkParseStrategy<ZorkItem> item = new ZorkItemParseStrategy(trigger);
public static final ZorkParseStrategy<ZorkRoom> room = new ZorkRoomParseStrategy(trigger);
public static final ZorkParseStrategy<ZorkCreature> creature = new ZorkCreatureParseStrategy(condition, trigger);
private Parsers() {
}
}

View File

@ -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<ZorkCondition> {
@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")
);
}
}
}

View File

@ -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<ZorkContainer> {
private final ZorkParseStrategy<ZorkTrigger> triggerStrategy;
public ZorkContainerParseStrategy(ZorkParseStrategy<ZorkTrigger> 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<ZorkTrigger> triggers = Elements.byTagName(element, "trigger").stream()
.map(triggerStrategy::parse)
.collect(Collectors.toList());
final String status = DOMUtils.getInnerTextByTagName(element, "status", "");
final List<String> accepts = Elements.innerTextByTagName(element, "accept");
final List<String> items = Elements.innerTextByTagName(element, "item");
return new ZorkContainer(name, description, status, items, accepts, triggers);
}
}

View File

@ -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<ZorkCreature> {
private final ZorkParseStrategy<ZorkCondition> conditionStrategy;
private final ZorkParseStrategy<ZorkTrigger> triggerStrategy;
public ZorkCreatureParseStrategy(final ZorkParseStrategy<ZorkCondition> conditionStrategy,
final ZorkParseStrategy<ZorkTrigger> triggerStrategy) {
this.conditionStrategy = conditionStrategy;
this.triggerStrategy = triggerStrategy;
}
@Override
public ZorkCreature parse(final Element source) {
// Get all possible creature attributes
final List<ZorkCondition> conditions = Elements.byTagName(source, "attack").stream()
.flatMap(e -> Elements.byTagName(e, "condition").stream())
.map(conditionStrategy::parse)
.collect(Collectors.toList());
final List<String> prints = Elements.byTagName(source, "attack").stream()
.flatMap(e -> Elements.innerTextByTagName(e, "print").stream())
.collect(Collectors.toList());
final List<String> actions = Elements.byTagName(source, "attack").stream()
.flatMap(e -> Elements.innerTextByTagName(e, "action").stream())
.collect(Collectors.toList());
final List<String> vulnerabilities = Elements.innerTextByTagName(source, "vulnerability");
final List<ZorkTrigger> 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);
}
}

View File

@ -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<ZorkItem> {
private final ZorkParseStrategy<ZorkTrigger> triggerStrategy;
public ZorkItemParseStrategy(final ZorkParseStrategy<ZorkTrigger> triggerStrategy) {
this.triggerStrategy = triggerStrategy;
}
@Override
public ZorkItem parse(final Element source) {
final List<String> prints = Elements.byTagName(source, "turnon").stream()
.flatMap(e -> Elements.innerTextByTagName(e, "print").stream())
.collect(Collectors.toList());
final List<String> actions = Elements.byTagName(source, "turnon").stream()
.flatMap(e -> Elements.innerTextByTagName(e, "action").stream())
.collect(Collectors.toList());
final List<ZorkTrigger> 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
);
}
}

View File

@ -1,7 +0,0 @@
package com.github.dtschust.zork.parser.builders;
import org.w3c.dom.Element;
public interface ZorkParseStrategy<T> {
T parse(Element source);
}

View File

@ -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<ZorkRoom> {
private final ZorkParseStrategy<ZorkTrigger> triggerStrategy;
public ZorkRoomParseStrategy(final ZorkParseStrategy<ZorkTrigger> 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<ZorkTrigger> triggers = Elements.byTagName(source, "trigger").stream()
.map(triggerStrategy::parse)
.collect(Collectors.toList());
final String status = DOMUtils.getInnerTextByTagName(source, "status", "");
final List<String> items = Elements.innerTextByTagName(source, "item");
final List<String> creatures = Elements.innerTextByTagName(source, "creature");
final List<String> containers = Elements.innerTextByTagName(source, "container");
final Map<ZorkDirection, String> 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);
}
}

View File

@ -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<ZorkTrigger> {
private final ZorkParseStrategy<ZorkCondition> conditionStrategy;
public ZorkTriggerListParseStrategy(final ZorkParseStrategy<ZorkCondition> conditionStrategy) {
this.conditionStrategy = conditionStrategy;
}
@Override
public ZorkTrigger parse(final Element trigger) {
final String type = DOMUtils.getInnerTextByTagName((Element) trigger.getParentNode(), "type", "single");
final List<ZorkCommand> commands = Elements.byTagName(trigger, "command").stream()
.map(DOMUtils::getInnerText)
.map(ZorkCommand::new)
.collect(Collectors.toList());
final List<ZorkCondition> conditions = Elements.byTagName(trigger, "condition").stream()
.map(conditionStrategy::parse)
.collect(Collectors.toList());
final List<String> prints = Elements.innerTextByTagName(trigger, "print");
final List<String> actions = Elements.innerTextByTagName(trigger, "action");
return new ZorkTrigger(type, conditions, commands, prints, actions);
}
}

View File

@ -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<? extends Property> subPropertiesByName(final String name) {
return DOMElementList.byTagName(backing, name);
}
@Override
public String subPropertyValue(final String elementName,
final Optional<String> 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<Property> subProperties() {
final List<Property> 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;
}
}

View File

@ -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<DOMElement> 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();
}
}

View File

@ -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<Element> 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<String> 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();
}
}

View File

@ -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<ZorkCondition> {
@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")
);
}
}
}

View File

@ -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<ZorkContainer> {
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<ZorkTrigger> triggers = element.subPropertiesByName("trigger").stream()
.map(triggerStrategy.parse(element))
.collect(Collectors.toList());
final String status = element.subPropertyValue("status", "");
final List<String> accepts = element.subPropertyValues("accept");
final List<String> items = element.subPropertyValues("item");
return new ZorkContainer(name, description, status, items, accepts, triggers);
}
}

View File

@ -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<ZorkCreature> {
private final PropertyParseStrategy<ZorkCondition> conditionStrategy;
private final TriggerPropertyParseStrategy triggerStrategy;
public ZorkCreatureParseStrategy(final PropertyParseStrategy<ZorkCondition> conditionStrategy,
final TriggerPropertyParseStrategy triggerStrategy) {
this.conditionStrategy = conditionStrategy;
this.triggerStrategy = triggerStrategy;
}
@Override
public ZorkCreature parse(final Property source) {
// Get all possible creature attributes
final List<ZorkCondition> conditions = source.subPropertiesByName("attack").stream()
.flatMap(e -> e.subPropertiesByName("condition").stream())
.map(conditionStrategy::parse)
.collect(Collectors.toList());
final List<String> prints = source.subPropertiesByName("attack").stream()
.flatMap(e -> e.subPropertyValues("print").stream())
.collect(Collectors.toList());
final List<String> actions = source.subPropertiesByName("attack").stream()
.flatMap(e -> e.subPropertyValues("action").stream())
.collect(Collectors.toList());
final List<String> vulnerabilities = source.subPropertyValues("vulnerability");
final List<ZorkTrigger> 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);
}
}

View File

@ -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<ZorkItem> {
private final TriggerPropertyParseStrategy triggerStrategy;
public ZorkItemParseStrategy(final TriggerPropertyParseStrategy triggerStrategy) {
this.triggerStrategy = triggerStrategy;
}
@Override
public ZorkItem parse(final Property source) {
final List<String> prints = source.subPropertiesByName("turnon").stream()
.flatMap(e -> e.subPropertyValues("print").stream())
.collect(Collectors.toList());
final List<String> actions = source.subPropertiesByName("turnon").stream()
.flatMap(e -> e.subPropertyValues("action").stream())
.collect(Collectors.toList());
final List<ZorkTrigger> 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
);
}
}

View File

@ -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<ZorkRoom> {
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<ZorkTrigger> triggers = source.subPropertiesByName("trigger").stream()
.map(triggerStrategy.parse(source))
.collect(Collectors.toList());
final String status = source.subPropertyValue("status", "");
final List<String> items = source.subPropertyValues("item");
final List<String> creatures = source.subPropertyValues("creature");
final List<String> containers = source.subPropertyValues("container");
final Map<ZorkDirection, String> 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);
}
}

View File

@ -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<ZorkCondition> conditionStrategy;
public ZorkTriggerParseStrategy(final PropertyParseStrategy<ZorkCondition> conditionStrategy) {
this.conditionStrategy = conditionStrategy;
}
@Override
public ZorkTrigger parseTrigger(final Property source, final Property parent) {
final String type = parent.subPropertyValue("type", "single");
final List<ZorkCommand> commands = source.subPropertiesByName("command").stream()
.map(Property::value)
.map(ZorkCommand::new)
.collect(Collectors.toList());
final List<ZorkCondition> conditions = source.subPropertiesByName("condition").stream()
.map(conditionStrategy::parse)
.collect(Collectors.toList());
final List<String> prints = source.subPropertyValues("print");
final List<String> actions = source.subPropertyValues("action");
return new ZorkTrigger(type, conditions, commands, prints, actions);
}
}

View File

@ -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);

View File

@ -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?

View File

@ -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<String> set = ((HasSetOfCollectable) tempObject).getSetFromType(element);
if (set.contains(object)) {

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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<String> getSetFromType(Zork.Type type) {
default Set<String> getSetFromType(ZorkGameStatusType type) {
throw new UnsupportedOperationException();
}
}

View File

@ -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<String> getSetFromType(Zork.Type type) {
public Set<String> getSetFromType(ZorkGameStatusType type) {
if (type.equals(ITEM))
return items;
throw new IllegalStateException("Unexpected value: " + type);

View File

@ -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<ZorkGameStatusType> fromPropertyName(final String propertyName) {
return EnumSet.allOf(ZorkGameStatusType.class).stream().filter(e -> e.propertyName.equals(propertyName)).findFirst();
}
}

View File

@ -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<String> getSetFromType(Zork.Type type) {
public Set<String> getSetFromType(ZorkGameStatusType type) {
switch (type) {
case CONTAINER:
return getContainer();