meyfa/nsdlib

View on GitHub
src/main/java/nsdlib/reader/StructorizerReader.java

Summary

Maintainability
B
6 hrs
Test Coverage
A
94%
package nsdlib.reader;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import nsdlib.elements.NSDContainer;
import nsdlib.elements.NSDElement;
import nsdlib.elements.NSDInstruction;
import nsdlib.elements.NSDRoot;
import nsdlib.elements.alternatives.NSDCase;
import nsdlib.elements.alternatives.NSDDecision;
import nsdlib.elements.loops.NSDForever;
import nsdlib.elements.loops.NSDTestFirstLoop;
import nsdlib.elements.loops.NSDTestLastLoop;
import nsdlib.elements.parallel.NSDParallel;


/**
 * NS diagram reader for the XML-based format employed by Structorizer
 * (http://structorizer.fisch.lu/).
 */
public class StructorizerReader implements NSDReader
{
    @Override
    public NSDRoot read(InputStream in) throws NSDReaderException
    {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        DocumentBuilder db;
        try {
            db = dbf.newDocumentBuilder();
            db.setErrorHandler(new QuietErrorHandler());
        } catch (ParserConfigurationException e) {
            throw new NSDReaderException(e);
        }

        Document d;
        try {
            d = db.parse(in);
        } catch (IOException | SAXException e) {
            throw new NSDReaderException(e);
        }

        return parseRoot(d.getDocumentElement());
    }

    private NSDRoot parseRoot(Element e) throws NSDReaderException
    {
        String label = deserialize(e.getAttribute("text")).get(0);
        NSDRoot root = new NSDRoot(label);

        Element children = (Element) e.getElementsByTagName("children").item(0);
        addChildren(root, children);

        return root;
    }

    /**
     * Finds all child elements of {@code e}, parses them and adds the parsed
     * instances to {@code cont}.
     *
     * @param cont The container to add the elements to.
     * @param e The element whose child elements shall be parsed.
     * @throws NSDReaderException If an unsupported tag is encountered.
     */
    private void addChildren(NSDContainer<NSDElement> cont, Element e) throws NSDReaderException
    {
        NodeList nodes = e.getChildNodes();

        for (int i = 0, n = nodes.getLength(); i < n; ++i) {
            Node node = nodes.item(i);
            if (node instanceof Element) {
                cont.addChild(parse((Element) node));
            }
        }
    }

    /**
     * Parses the given element according to its tag.
     *
     * @param e The element to parse.
     * @return The parsed element class instance.
     * @throws NSDReaderException If an unsupported tag is encountered.
     */
    private NSDElement parse(Element e) throws NSDReaderException
    {
        String tag = e.getTagName().toLowerCase();

        switch (tag) {
            case "instruction":
                return parseInstruction(e);
            case "alternative":
                return parseDecision(e);
            case "case":
                return parseCase(e);
            case "forever":
                return parseForever(e);
            case "while":
                return parseTestFirstLoop(e);
            case "repeat":
                return parseTestLastLoop(e);
            case "parallel":
                return parseParallel(e);
        }

        throw new NSDReaderException("element unsupported: " + tag);
    }

    /**
     * Structorizer stores its string properties (e.g. "text") in a special
     * format, where lines are surrounded with quotation marks and separated by
     * commas. Quotes occurring inline as normal characters are escaped by
     * prefixing them with a second quote.
     *
     * <p>
     * This function reverses that process.
     *
     * <p>
     * Example: {@code "hello world","foo","\"\"bar\"\""} turns into
     * {@code ["hello world", "foo", "\"bar\""]} .
     *
     * @param s The string to deserialize.
     * @return The deserialized lines.
     */
    private List<String> deserialize(String s)
    {
        if (!s.startsWith("\"") || !s.endsWith("\"")) {
            return Collections.singletonList(s);
        }

        List<String> strings = new ArrayList<>();

        StringBuilder sb = new StringBuilder();
        boolean open = false;

        for (int i = 0; i < s.length(); ++i) {
            char chr = s.charAt(i);
            if (chr == '\"') {
                if (i < s.length() - 1) {
                    if (!open) {
                        open = true;
                        continue;
                    }
                    if (s.charAt(i + 1) == chr) {
                        sb.append(chr);
                        i++;
                        continue;
                    }
                }
                strings.add(sb.toString());
                sb = new StringBuilder();
                open = false;
            } else if (open) {
                sb.append(chr);
            }
        }

        String tmp = sb.toString();
        if (!tmp.trim().isEmpty()) {
            strings.add(tmp);
        }

        return strings;
    }

    private NSDInstruction parseInstruction(Element e) throws NSDReaderException
    {
        String label = deserialize(e.getAttribute("text")).get(0);
        return new NSDInstruction(label);
    }

    private NSDDecision parseDecision(Element e) throws NSDReaderException
    {
        String label = deserialize(e.getAttribute("text")).get(0);
        NSDDecision dec = new NSDDecision(label);

        Element qTrue = (Element) e.getElementsByTagName("qTrue").item(0);
        addChildren(dec.getThen(), qTrue);

        Element qFalse = (Element) e.getElementsByTagName("qFalse").item(0);
        addChildren(dec.getElse(), qFalse);

        return dec;
    }

    private NSDCase parseCase(Element e) throws NSDReaderException
    {
        List<String> lines = deserialize(e.getAttribute("text"));
        NSDCase cas = new NSDCase(lines.get(0));

        NodeList qCase = e.getElementsByTagName("qCase");
        for (int i = 0; i < qCase.getLength(); ++i) {
            String label = lines.get(i + 1);
            Element qCaseItem = (Element) qCase.item(i);

            NSDContainer<NSDElement> cont = new NSDContainer<>(label);
            addChildren(cont, qCaseItem);

            cas.addChild(cont);
        }

        return cas;
    }

    private NSDForever parseForever(Element e) throws NSDReaderException
    {
        NSDForever loop = new NSDForever();

        Element qForever = (Element) e.getElementsByTagName("qForever").item(0);
        addChildren(loop, qForever);

        return loop;
    }

    private NSDTestFirstLoop parseTestFirstLoop(Element e) throws NSDReaderException
    {
        String label = deserialize(e.getAttribute("text")).get(0);
        NSDTestFirstLoop loop = new NSDTestFirstLoop(label);

        Element qWhile = (Element) e.getElementsByTagName("qWhile").item(0);
        addChildren(loop, qWhile);

        return loop;
    }

    private NSDTestLastLoop parseTestLastLoop(Element e) throws NSDReaderException
    {
        String label = deserialize(e.getAttribute("text")).get(0);
        NSDTestLastLoop loop = new NSDTestLastLoop(label);

        Element qRepeat = (Element) e.getElementsByTagName("qRepeat").item(0);
        addChildren(loop, qRepeat);

        return loop;
    }

    private NSDParallel parseParallel(Element e) throws NSDReaderException
    {
        NSDParallel parallel = new NSDParallel();

        NodeList qPara = e.getElementsByTagName("qPara");
        for (int i = 0; i < qPara.getLength(); ++i) {
            Element qParaItem = (Element) qPara.item(i);

            NSDContainer<NSDElement> cont = new NSDContainer<>("");
            addChildren(cont, qParaItem);

            parallel.addChild(cont);
        }

        return parallel;
    }

    /**
     * The default DocumentBuilder error handler prints its exceptions, even
     * when they are caught. This is a replacement handler that does not do that
     * and instead simply rethrows the errors. Warnings are ignored.
     */
    private static class QuietErrorHandler implements ErrorHandler
    {
        @Override
        public void warning(SAXParseException exception) throws SAXException
        {
        }

        @Override
        public void error(SAXParseException exception) throws SAXException
        {
            throw exception;
        }

        @Override
        public void fatalError(SAXParseException exception) throws SAXException
        {
            throw exception;
        }
    }
}