Java tutorial
/* * (C) Copyright 2015-2016, by Wil Selwood and Contributors. * * JGraphT : a free Java graph-theory library * * This program and the accompanying materials are dual-licensed under * either * * (a) the terms of the GNU Lesser General Public License version 2.1 * as published by the Free Software Foundation, or (at your option) any * later version. * * or (per the licensee's choosing) * * (b) the terms of the Eclipse Public License v1.0 as published by * the Eclipse Foundation. */ package de.knowwe.diaflux.utils; import java.io.IOException; import java.io.Reader; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.io.IOUtils; import org.jgrapht.DirectedGraph; import org.jgrapht.Graph; import org.jgrapht.ext.EdgeProvider; import org.jgrapht.ext.GraphImporter; import org.jgrapht.ext.ImportException; import org.jgrapht.ext.VertexProvider; import org.jgrapht.graph.AbstractBaseGraph; /** * Copy from {@link org.jgrapht.ext.DOTImporter}, adapted a little bit for this purpose. * * @param <V> the graph vertex type * @param <E> the graph edge type * @author Adrian Mller * @created 23.10. */ class DOTImporter<V, E> implements GraphImporter<V, E> { // Constants for the state machine private static final int NEXT = 0; private static final int HEADER = 1; private static final int NODE = 2; private static final int EDGE = 3; private static final int LINE_COMMENT = 4; private static final int BLOCK_COMMENT = 5; private static final int DONE = 32; private final VertexProvider<V> vertexProvider; private final EdgeProvider<V, E> edgeProvider; private String input; private int position; private Graph<V, E> graph; private Map<String, V> vertexes; private StringBuilder sectionBuffer; /** * Constructs a new DOTImporter with the given providers * * @param vertexProvider Provider to create a vertex * @param edgeProvider Provider to create an edge */ DOTImporter(VertexProvider<V> vertexProvider, EdgeProvider<V, E> edgeProvider) { this.vertexProvider = vertexProvider; this.edgeProvider = edgeProvider; } private static int findEndOfQuote(String input, int start) { int result; // "start" should be the opening quote for (result = input.indexOf('\"', start + 1); result != -1 && input.charAt(result - 1) == '\\'; result = input.indexOf('\"', result + 1)) { // if the previous character is an escape then search for char escaping the escape and so on int firstEscapeIndex = result - 1; while ((firstEscapeIndex > -1) && input.charAt(firstEscapeIndex) == '\\') { firstEscapeIndex--; } int numberOfEscapes = result - firstEscapeIndex + 1; if (numberOfEscapes % 2 == 0) { break; } } return result; } /** * Read a dot formatted input and populate the provided graph. * <p> * The current implementation reads the whole input as a string and then parses the graph. * * @param graph the graph to update * @param input the input reader * @throws ImportException if there is a problem parsing the file. */ @Override public void importGraph(Graph<V, E> graph, Reader input) throws ImportException { try { this.input = IOUtils.toString(input); } catch (IOException e) { throw new ImportException("IOException: " + e.getLocalizedMessage()); } this.graph = graph; vertexes = new HashMap<>(); position = 0; sectionBuffer = new StringBuilder(); read(); } /** * Read a dot formatted string and populate the provided graph. * * @throws ImportException if there is a problem parsing the file. */ private void read() throws ImportException { if ((input == null) || input.isEmpty()) { throw new ImportException("Dot string was empty"); } int state = HEADER; int lastState = HEADER; while ((state != DONE) && (position < input.length())) { int currentState = state; switch (state) { case HEADER: state = processHeader(); break; case NODE: state = processNode(); break; case EDGE: state = processEdge(); break; case LINE_COMMENT: state = processLineComment(lastState); // when we leave a line comment we need the new line to // still appear in the old block position--; break; case BLOCK_COMMENT: state = processBlockComment(lastState); break; case NEXT: state = processNext(); break; // DONE not included here as we can't get to it with the while loop. default: throw new ImportException("Error importing escaped state machine"); } position++; if (state != currentState) { lastState = currentState; } } // if we get to the end and are some how still in the header the input // must be invalid. if (state == HEADER) { throw new ImportException("Invalid Header"); } } /** * Process the header block. * * @return the new state. * @throws ImportException if there is a problem with the header section. */ private int processHeader() throws ImportException { if (isStartOfLineComment()) { return LINE_COMMENT; } if (isStartOfBlockComment()) { return BLOCK_COMMENT; } char current = input.charAt(position); sectionBuffer.append(current); if (current == '{') { // reached the end of the header. Validate it. String[] headerParts = sectionBuffer.toString().split(" ", 4); if (headerParts.length < 3) { throw new ImportException("Not enough parts in header"); } int i = 0; if (graph instanceof AbstractBaseGraph && ((AbstractBaseGraph<V, E>) graph).isAllowingMultipleEdges() && headerParts[i].equals("strict")) { throw new ImportException("graph defines strict but Multigraph given."); } else if (headerParts[i].equals("strict")) { i = i + 1; } if ((graph instanceof DirectedGraph) && headerParts[i].equals("graph")) { throw new ImportException("input asks for undirected graph and directed graph provided."); } else if (!(graph instanceof DirectedGraph) && headerParts[i].equals("digraph")) { throw new ImportException("input asks for directed graph but undirected graph provided."); } else if (!headerParts[i].equals("graph") && !headerParts[i].equals("digraph")) { throw new ImportException("unknown graph type"); } sectionBuffer.setLength(0); // reset the buffer. return NEXT; } return HEADER; } /** * When we start a new section of the graph we don't know what it is going to be. We work in * here until we can work out what type of section this is. * * @return the next state. * @throws ImportException if there is a problem with creating a node. */ private int processNext() throws ImportException { if (isStartOfLineComment()) { return LINE_COMMENT; } if (isStartOfBlockComment()) { return BLOCK_COMMENT; } char current = input.charAt(position); // ignore new line characters or section breaks between identified // sections. if ((current == '\n') || (current == '\r')) { return NEXT; } // if the buffer is currently empty skip spaces too. if ((sectionBuffer.length() == 0) && ((current == ' ') || (current == ';'))) { return NEXT; } // If we have a semi colon and some thing in the buffer we must be at // the end of a block. as we can't have had a dash yet we must be at the // end of a node. if (current == ';') { processCompleteNode(sectionBuffer.toString()); sectionBuffer.setLength(0); return NEXT; } sectionBuffer.append(input.charAt(position)); if (current == '"') { skipQuoted(); return NEXT; } if (position < (input.length() - 1)) { char next = input.charAt(position + 1); if (current == '-') { if ((next == '-') && (graph instanceof DirectedGraph)) { throw new ImportException("graph is directed but undirected edge found"); } else if ((next == '>') && !(graph instanceof DirectedGraph)) { throw new ImportException("graph is undirected but directed edge found"); } else if ((next == '-') || (next == '>')) { return EDGE; } } } if (current == '[') { return NODE; // if this was an edge we should have found a dash before // here. } return NEXT; } /** * Process a node entry. When we detect that we are at the end of the node create it in the * graph. * * @return the next state. * @throws ImportException if there is a problem with creating a node. */ private int processNode() throws ImportException { if (isStartOfLineComment()) { return LINE_COMMENT; } if (isStartOfBlockComment()) { return BLOCK_COMMENT; } char current = input.charAt(position); sectionBuffer.append(input.charAt(position)); if (current == '"') { skipQuoted(); return NODE; } if ((current == ']') || (current == ';')) { processCompleteNode(sectionBuffer.toString()); sectionBuffer.setLength(0); return NEXT; } return NODE; } private int processEdge() throws ImportException { if (isStartOfLineComment()) { return LINE_COMMENT; } if (isStartOfBlockComment()) { return BLOCK_COMMENT; } char current = input.charAt(position); sectionBuffer.append(input.charAt(position)); if (current == '"') { skipQuoted(); return EDGE; } if ((current == ';') || (current == '\r') || (current == ']')) { processCompleteEdge(sectionBuffer.toString()); sectionBuffer.setLength(0); return NEXT; } return EDGE; } private void skipQuoted() { int endOfQuote = findEndOfQuote(input, position); sectionBuffer.append(input.substring(position + 1, endOfQuote + 1)); position = endOfQuote; } private int processLineComment(int returnState) { char current; int endOfLine = position; do { current = input.charAt(endOfLine++); } while (!(current == '\r' || current == '\n')); sectionBuffer.append(input.substring(position, endOfLine + 1)); return returnState; } private int processBlockComment(int returnState) { char current; int endOfLine = position; do { current = input.charAt(endOfLine++); } while (!(current == '/' && input.charAt(position - 1) == '*')); return returnState; } private boolean isStartOfLineComment() { char current = input.charAt(position); if (current == '#') { return true; } else if (current == '/') { if (position < (input.length() - 1)) { if (input.charAt(position + 1) == '/') { return true; } } } return false; } private boolean isStartOfBlockComment() { char current = input.charAt(position); if (current == '/') { if (position < (input.length() - 1)) { if (input.charAt(position + 1) == '*') { return true; } } } return false; } private void processCompleteNode(String node) throws ImportException { Map<String, String> attributes = extractAttributes(node); String id = node.trim(); int bracketIndex = node.indexOf('['); if (bracketIndex > 0) { id = node.substring(0, node.indexOf('[')).trim(); } V existing = vertexes.get(id); if (existing == null) { V vertex = vertexProvider.buildVertex(id, attributes); if (vertex != null) { graph.addVertex(vertex); vertexes.put(id, vertex); } } else { throw new ImportException("Update required for vertex " + id + " but no vertexUpdater provided"); } } private void processCompleteEdge(String edge) throws ImportException { Map<String, String> attributes = extractAttributes(edge); List<String> ids = extractEdgeIds(edge); // for each pair of ids in the list create an edge. for (int i = 0; i < (ids.size() - 1); i++) { V v1 = getVertex(ids.get(i)); V v2 = getVertex(ids.get(i + 1)); E resultEdge = edgeProvider.buildEdge(v1, v2, attributes.get("label"), attributes); graph.addEdge(v1, v2, resultEdge); } } // if a vertex id doesn't already exist create one for it // with no attributes. private V getVertex(String id) { V v = vertexes.get(id); if (v == null) { v = vertexProvider.buildVertex(id, new HashMap<>()); graph.addVertex(v); vertexes.put(id, v); } return v; } private List<String> extractEdgeIds(String line) { String idChunk = line.trim(); if (idChunk.endsWith(";")) { idChunk = idChunk.substring(0, idChunk.length() - 1); } int bracketIndex = idChunk.indexOf('['); if (bracketIndex > 1) { idChunk = idChunk.substring(0, bracketIndex).trim(); } int index = 0; List<String> ids = new ArrayList<>(); while (index < idChunk.length()) { int nextSpace = idChunk.indexOf(' ', index); String chunk; if (nextSpace > 0) { // is this the last chunk chunk = idChunk.substring(index, nextSpace); index = nextSpace + 1; } else { chunk = idChunk.substring(index); index = idChunk.length() + 1; } if (!chunk.equals("--") && !chunk.equals("->")) { // a label then? ids.add(chunk); } } return ids; } private Map<String, String> extractAttributes(String line) throws ImportException { Map<String, String> attributes = new HashMap<>(); int bracketIndex = line.indexOf("["); if (bracketIndex > 0) { attributes = splitAttributes(line.substring(bracketIndex + 1, line.lastIndexOf(']')).trim()); } return attributes; } private Map<String, String> splitAttributes(String section) throws ImportException { int index = 0; Map<String, String> result = new HashMap<>(); while (index < section.length()) { // skip any leading white space index = skipWhiteSpace(section, index); // Now check for quotes int endOfKey = findEndOfSection(section, index, "="); if (endOfKey < 0) { throw new ImportException("Invalid attributes"); } if (section.charAt(endOfKey) == '"') { index = index + 1; } String key = section.substring(index, endOfKey).trim(); if ((endOfKey + 1) >= section.length()) { throw new ImportException("Invalid attributes"); } // Attribute value may be quoted or a single word. // First ignore any white space before the start int start = skipWhiteSpace(section, endOfKey + 1); int endChar = findEndOfSection(section, start, ",\n\t\t"); if (section.charAt(start) == '"') { start = start + 1; } if (endChar < 0) { endChar = section.length(); } String value = section.substring(start, endChar); result.put(key, value); if (endChar < section.length() && section.charAt(endChar) == '"') { endChar++; } index = endChar + 1; } return result; } private int skipWhiteSpace(String section, int start) throws ImportException { int i = 0; while (Character.isWhitespace(section.charAt(start + i)) || (section.charAt(start + i) == '=')) { i = i + 1; if ((start + i) >= section.length()) { throw new ImportException("Invalid attributes"); } } return start + i; } private int findEndOfSection(String section, int start, String terminator) { if (section.charAt(start) == '"') { return findEndOfQuote(section, start); } else { return section.indexOf(terminator, start); } } } // End DOTImporter.java