From a2ed695a4a0c92c07cad8df15c49a89b2154d65c Mon Sep 17 00:00:00 2001 From: FyloZ Date: Sun, 29 May 2022 11:00:06 -0400 Subject: [PATCH] Add configuration parser --- src/TestMain.java | 12 ++ src/configuration/ConfigurationParser.java | 11 + .../ConfigurationParsingException.java | 22 ++ src/configuration/IterableNodeList.java | 31 +++ .../NodeListElementIterator.java | 41 ++++ .../SimulationConfiguration.java | 23 +++ src/configuration/SimulationData.java | 24 +++ src/configuration/XmlConfigurationParser.java | 190 ++++++++++++++++++ src/model/BuildingState.java | 24 ++- src/model/ComponentType.java | 23 ++- src/model/Factory.java | 1 + src/model/Warehouse.java | 2 +- 12 files changed, 395 insertions(+), 9 deletions(-) create mode 100644 src/TestMain.java create mode 100644 src/configuration/ConfigurationParser.java create mode 100644 src/configuration/ConfigurationParsingException.java create mode 100644 src/configuration/IterableNodeList.java create mode 100644 src/configuration/NodeListElementIterator.java create mode 100644 src/configuration/SimulationConfiguration.java create mode 100644 src/configuration/SimulationData.java create mode 100644 src/configuration/XmlConfigurationParser.java diff --git a/src/TestMain.java b/src/TestMain.java new file mode 100644 index 0000000..8c0bb96 --- /dev/null +++ b/src/TestMain.java @@ -0,0 +1,12 @@ +import configuration.ConfigurationParser; +import configuration.ConfigurationParsingException; +import configuration.XmlConfigurationParser; + +public class TestMain { + public static void main(String[] args) throws ConfigurationParsingException { + String configurationFilePath = "/home/william/Dev/Projects/Uni/Log121/TP01/squelette/src/ressources/configuration.xml"; + + ConfigurationParser parser = new XmlConfigurationParser(); + parser.parseConfiguration(configurationFilePath); + } +} diff --git a/src/configuration/ConfigurationParser.java b/src/configuration/ConfigurationParser.java new file mode 100644 index 0000000..54bae30 --- /dev/null +++ b/src/configuration/ConfigurationParser.java @@ -0,0 +1,11 @@ +package configuration; + +public interface ConfigurationParser { + /** + * Analyse et lit une configuration de simulation depuis un fichier au chemin donné. + * + * @param path Le chemin vers le fichier de configuration. + * @return La configuration de simulation correspondant au contenu du fichier. + */ + SimulationConfiguration parseConfiguration(String path) throws ConfigurationParsingException; +} diff --git a/src/configuration/ConfigurationParsingException.java b/src/configuration/ConfigurationParsingException.java new file mode 100644 index 0000000..8325223 --- /dev/null +++ b/src/configuration/ConfigurationParsingException.java @@ -0,0 +1,22 @@ +package configuration; + +public class ConfigurationParsingException extends Exception { + public ConfigurationParsingException() { + } + + public ConfigurationParsingException(String message) { + super(message); + } + + public ConfigurationParsingException(String message, Throwable cause) { + super(message, cause); + } + + public ConfigurationParsingException(Throwable cause) { + super(cause); + } + + public ConfigurationParsingException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/src/configuration/IterableNodeList.java b/src/configuration/IterableNodeList.java new file mode 100644 index 0000000..033e56a --- /dev/null +++ b/src/configuration/IterableNodeList.java @@ -0,0 +1,31 @@ +package configuration; + +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; + +import java.util.Iterator; +import java.util.Spliterator; +import java.util.function.Consumer; + +public class IterableNodeList implements Iterable { + private final NodeList nodes; + + public IterableNodeList(NodeList nodes) { + this.nodes = nodes; + } + + @Override + public Iterator iterator() { + return new NodeListElementIterator(nodes); + } + + @Override + public void forEach(Consumer action) { + Iterable.super.forEach(action); + } + + @Override + public Spliterator spliterator() { + return Iterable.super.spliterator(); + } +} diff --git a/src/configuration/NodeListElementIterator.java b/src/configuration/NodeListElementIterator.java new file mode 100644 index 0000000..9859829 --- /dev/null +++ b/src/configuration/NodeListElementIterator.java @@ -0,0 +1,41 @@ +package configuration; + +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import java.util.Iterator; + +public class NodeListElementIterator implements Iterator { + private final NodeList nodes; + private int position = 0; + + public NodeListElementIterator(NodeList nodes) { + this.nodes = nodes; + } + + @Override + public boolean hasNext() { + if (position >= nodes.getLength()) return false; + + // Check if there is a remaining element node + for (int i = position; i < nodes.getLength(); i++) { + Node node = nodes.item(i); + + if (node.getNodeType() == Node.ELEMENT_NODE) return true; + } + + return false; + } + + @Override + public Element next() { + if (position >= nodes.getLength()) return null; + + Node node = nodes.item(position); + position++; + + if (node.getNodeType() == Node.ELEMENT_NODE) return (Element) node; + return next(); + } +} diff --git a/src/configuration/SimulationConfiguration.java b/src/configuration/SimulationConfiguration.java new file mode 100644 index 0000000..9d41541 --- /dev/null +++ b/src/configuration/SimulationConfiguration.java @@ -0,0 +1,23 @@ +package configuration; + +import model.metadata.BuildingMetadata; + +import java.util.Map; + +public class SimulationConfiguration { + private final Map metadata; + private final SimulationData simulationData; + + public SimulationConfiguration(Map metadata, SimulationData simulationData) { + this.metadata = metadata; + this.simulationData = simulationData; + } + + public Map getMetadata() { + return metadata; + } + + public SimulationData getSimulationData() { + return simulationData; + } +} diff --git a/src/configuration/SimulationData.java b/src/configuration/SimulationData.java new file mode 100644 index 0000000..cbee077 --- /dev/null +++ b/src/configuration/SimulationData.java @@ -0,0 +1,24 @@ +package configuration; + +import model.Building; +import model.Route; + +import java.util.Collection; + +public class SimulationData { + private final Collection buildings; + private final Collection routes; + + public SimulationData(Collection buildings, Collection routes) { + this.buildings = buildings; + this.routes = routes; + } + + public Collection getBuildings() { + return buildings; + } + + public Collection getRoutes() { + return routes; + } +} diff --git a/src/configuration/XmlConfigurationParser.java b/src/configuration/XmlConfigurationParser.java new file mode 100644 index 0000000..192f390 --- /dev/null +++ b/src/configuration/XmlConfigurationParser.java @@ -0,0 +1,190 @@ +package configuration; + +import model.*; +import model.metadata.*; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.xml.sax.SAXException; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +public class XmlConfigurationParser implements ConfigurationParser { + public static final String TAG_METADATA = "metadonnees"; + public static final String TAG_SIMULATION_DATA = "simulation"; + public static final String TAG_BUILDING = "usine"; + public static final String TAG_ROUTES = "chemins"; + public static final String TAG_ICONS = "icones"; + public static final String TAG_INPUT = "entree"; + public static final String TAG_OUTPUT = "sortie"; + public static final String TAG_PRODUCTION_INTERVAL = "interval-production"; + public static final String ATTRIBUTE_TYPE = "type"; + public static final String ATTRIBUTE_PATH = "path"; + public static final String ATTRIBUTE_QUANTITY = "quantite"; + public static final String ATTRIBUTE_CAPACITY = "capacite"; + public static final String ATTRIBUTE_ID = "id"; + public static final String ATTRIBUTE_X = "x"; + public static final String ATTRIBUTE_Y = "y"; + public static final String ATTRIBUTE_FROM = "de"; + public static final String ATTRIBUTE_TO = "vers"; + + @Override + public SimulationConfiguration parseConfiguration(String path) throws ConfigurationParsingException { + Document document = parseDocument(path); + + Map buildingsMetadata = parseBuildingsMetadata(document); + SimulationData simulationData = parseSimulationData(document); + + return new SimulationConfiguration(buildingsMetadata, simulationData); + } + + private Document parseDocument(String path) throws ConfigurationParsingException { + try { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + + Document document = builder.parse(path); + document.getDocumentElement().normalize(); + + return document; + } catch (ParserConfigurationException | IOException | SAXException e) { + throw new ConfigurationParsingException("La lecture du fichier a échoué", e); + } + } + + private Map parseBuildingsMetadata(Document document) { + Node metadataNode = document.getElementsByTagName(TAG_METADATA).item(0); + Map metadata = new HashMap<>(); + + for (Element elem : new IterableNodeList(metadataNode.getChildNodes())) { + String buildingType = elem.getAttribute(ATTRIBUTE_TYPE); + + metadata.put(buildingType, parseBuildingMetadata(elem)); + } + + return metadata; + } + + private BuildingMetadata parseBuildingMetadata(Element buildingElement) { + String type = buildingElement.getAttribute(ATTRIBUTE_TYPE); + + return type.equals("entrepot") ? + parseWarehouseMetadata(buildingElement, type) : + parseFactoryMetadata(buildingElement, type); + } + + private BuildingMetadata parseFactoryMetadata(Element factoryElement, String type) { + Map iconsPaths = null; + Collection inputs = new ArrayList<>(); + FactoryOutput output = null; + int productionInterval = -1; + + for (Element elem : new IterableNodeList(factoryElement.getChildNodes())) { + switch (elem.getNodeName()) { + case TAG_ICONS -> iconsPaths = parseBuildingIcons(elem); + case TAG_INPUT -> { + FactoryInput input = parseFactoryInput(elem); + inputs.add(input); + } + case TAG_OUTPUT -> output = parseFactoryOutput(elem); + case TAG_PRODUCTION_INTERVAL -> productionInterval = Integer.parseInt(elem.getTextContent()); + } + } + + return new FactoryMetadata(type, iconsPaths, productionInterval, inputs, output); + } + + private BuildingMetadata parseWarehouseMetadata(Element warehouseElement, String type) { + Map iconsPaths = null; + WarehouseInput input = null; + + for (Element elem : new IterableNodeList(warehouseElement.getChildNodes())) { + switch (elem.getNodeName()) { + case TAG_ICONS -> iconsPaths = parseBuildingIcons(elem); + case TAG_INPUT -> input = parseWarehouseInput(elem); + } + } + + return new WarehouseMetadata(type, iconsPaths, input); + } + + private Map parseBuildingIcons(Element iconsElement) { + Map iconsPaths = new HashMap<>(); + + for (Element elem : new IterableNodeList(iconsElement.getChildNodes())) { + BuildingState state = BuildingState.getFromTypeName(elem.getAttribute(ATTRIBUTE_TYPE)); + String path = elem.getAttribute(ATTRIBUTE_PATH); + + iconsPaths.put(state, path); + } + + return iconsPaths; + } + + private FactoryInput parseFactoryInput(Element inputElement) { + ComponentType type = ComponentType.getForTypeName(inputElement.getAttribute(ATTRIBUTE_TYPE)); + int quantity = Integer.parseInt(inputElement.getAttribute(ATTRIBUTE_QUANTITY)); + + return new FactoryInput(type, quantity); + } + + private WarehouseInput parseWarehouseInput(Element inputElement) { + ComponentType type = ComponentType.getForTypeName(inputElement.getAttribute(ATTRIBUTE_TYPE)); + int capacity = Integer.parseInt(inputElement.getAttribute(ATTRIBUTE_CAPACITY)); + + return new WarehouseInput(type, capacity); + } + + private FactoryOutput parseFactoryOutput(Element outputElement) { + ComponentType type = ComponentType.getForTypeName(outputElement.getAttribute(ATTRIBUTE_TYPE)); + + return new FactoryOutput(type); + } + + private SimulationData parseSimulationData(Document document) { + Node dataNode = document.getElementsByTagName(TAG_SIMULATION_DATA).item(0); + + Collection buildings = new ArrayList<>(); + Collection routes = null; + + for (Element elem : new IterableNodeList(dataNode.getChildNodes())) { + switch (elem.getNodeName()) { + case TAG_BUILDING -> buildings.add(parseBuilding(elem)); + case TAG_ROUTES -> routes = parseRoutes(elem); + } + } + + return new SimulationData(buildings, routes); + } + + private Building parseBuilding(Element buildingElement) { + String type = buildingElement.getAttribute(ATTRIBUTE_TYPE); + int id = Integer.parseInt(buildingElement.getAttribute(ATTRIBUTE_ID)); + int x = Integer.parseInt(buildingElement.getAttribute(ATTRIBUTE_X)); + int y = Integer.parseInt(buildingElement.getAttribute(ATTRIBUTE_Y)); + + return type.equals("entrepot") ? + new Warehouse(id, type, x, y) : + new Factory(id, type, x, y); + } + + private Collection parseRoutes(Element routesElement) { + Collection routes = new ArrayList<>(); + + for (Element elem : new IterableNodeList(routesElement.getChildNodes())) { + int from = Integer.parseInt(elem.getAttribute(ATTRIBUTE_TO)); + int to = Integer.parseInt(elem.getAttribute(ATTRIBUTE_FROM)); + + routes.add(new Route(from, to)); + } + + return routes; + } +} diff --git a/src/model/BuildingState.java b/src/model/BuildingState.java index 8be4de4..c85d989 100644 --- a/src/model/BuildingState.java +++ b/src/model/BuildingState.java @@ -1,8 +1,24 @@ package model; +import java.util.Arrays; +import java.util.Objects; + public enum BuildingState { - EMPTY, - ONE_THIRD, - TWO_THIRD, - FULL + EMPTY("vide"), + ONE_THIRD("un-tiers"), + TWO_THIRD("deux-tiers"), + FULL("plein"); + + private final String typeName; + + BuildingState(String typeName) { + this.typeName = typeName; + } + + public static BuildingState getFromTypeName(String typeName) { + return Arrays.stream(BuildingState.values()) + .filter(t -> Objects.equals(t.typeName, typeName)) + .findFirst().orElse(null); + + } } diff --git a/src/model/ComponentType.java b/src/model/ComponentType.java index 18f77e5..d3fee65 100644 --- a/src/model/ComponentType.java +++ b/src/model/ComponentType.java @@ -1,8 +1,23 @@ package model; +import java.util.Arrays; +import java.util.Objects; + public enum ComponentType { - METAL, - MOTOR, - PLANE, - WING + METAL("metal"), + MOTOR("moteur"), + PLANE("avion"), + WING("aile"); + + private final String typeName; + + ComponentType(String typeName) { + this.typeName = typeName; + } + + public static ComponentType getForTypeName(String typeName) { + return Arrays.stream(ComponentType.values()) + .filter(t -> Objects.equals(t.typeName, typeName)) + .findFirst().orElse(null); + } } diff --git a/src/model/Factory.java b/src/model/Factory.java index ef38d61..0123bf4 100644 --- a/src/model/Factory.java +++ b/src/model/Factory.java @@ -5,6 +5,7 @@ public class Factory extends Building { super(id, type, x, y); } + // TODO WN: Move logic outside model @Override public void processInput(Component input) { } diff --git a/src/model/Warehouse.java b/src/model/Warehouse.java index be3ad39..f0a6d29 100644 --- a/src/model/Warehouse.java +++ b/src/model/Warehouse.java @@ -1,7 +1,7 @@ package model; public class Warehouse extends Building { - protected Warehouse(int id, String type, int x, int y) { + public Warehouse(int id, String type, int x, int y) { super(id, type, x, y); }