/* * Copyright 2012 Odysseus Software GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package de.odysseus.ithaka.digraph.io.graphml; import java.util.EnumSet; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import javax.xml.XMLConstants; import javax.xml.namespace.QName; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; import de.odysseus.ithaka.digraph.Digraph; import de.odysseus.ithaka.digraph.DigraphProvider; /** * Export digraph as GraphML. * @param <V> vertex type * @param <E> edge type */ public class GraphMLExporter { static class CanonicalIdGenerator { private int dataIndex = 0; private int graphIndex = 0; private int nodeIndex = 0; private int edgeIndex = 0; private String format(char prefix, int index) { return String.format("%c%d", prefix, index); } String nextDataId() { return format('d', dataIndex++); } String nextGraphId() { return format('g', graphIndex++); } String nextNodeId() { return format('n', nodeIndex++); } String nextEdgeId() { return format('e', edgeIndex++); } } static final String GRAPHML_NAMESPACE_URI = "http://graphml.graphdrawing.org/xmlns"; private final Set<GraphMLParseInfo> parseInfos; public GraphMLExporter() { this(EnumSet.noneOf(GraphMLParseInfo.class)); } public GraphMLExporter(EnumSet<GraphMLParseInfo> parseInfos) { this.parseInfos = parseInfos; } private <V, E, G extends Digraph<? extends V, ? extends E>> void write( GraphMLProvider<V, E, G> provider, DigraphProvider<? super V, G> subgraphs, XMLStreamWriter writer, G digraph, Map<GraphMLProperty<?>, String> propertyIds, CanonicalIdGenerator idGenerator) throws XMLStreamException { writer.writeStartElement("graph"); writer.writeAttribute("id", idGenerator.nextGraphId()); writer.writeAttribute("edgedefault", "directed"); if (parseInfos.contains(GraphMLParseInfo.Order)) { writer.writeAttribute("parse.order", "nodesfirst"); } if (parseInfos.contains(GraphMLParseInfo.Nodes)) { writer.writeAttribute("parse.nodes", String.valueOf(digraph.getVertexCount())); } if (parseInfos.contains(GraphMLParseInfo.NodeIds)) { writer.writeAttribute("parse.nodeids", "canonical"); } if (parseInfos.contains(GraphMLParseInfo.Edges)) { writer.writeAttribute("parse.edges", String.valueOf(digraph.getEdgeCount())); } if (parseInfos.contains(GraphMLParseInfo.EdgeIds)) { writer.writeAttribute("parse.edgeids", "canonical"); } Digraph<?, ?> reverse = null; if (parseInfos.contains(GraphMLParseInfo.InDegree) || parseInfos.contains(GraphMLParseInfo.MaxInDegree)) { reverse = digraph.reverse(); } if (parseInfos.contains(GraphMLParseInfo.MaxInDegree)) { int maxInDegree = 0; for (V vertex : digraph.vertices()) { maxInDegree = Math.max(maxInDegree, reverse.getOutDegree(vertex)); } writer.writeAttribute("parse.maxindegree", String.valueOf(maxInDegree)); } if (parseInfos.contains(GraphMLParseInfo.MaxOutDegree)) { int maxOutDegree = 0; for (V vertex : digraph.vertices()) { maxOutDegree = Math.max(maxOutDegree, digraph.getOutDegree(vertex)); } writer.writeAttribute("parse.maxoutdegree", String.valueOf(maxOutDegree)); } for (GraphMLProperty<G> property : provider.getGraphProperties()) { property.writeData(writer, propertyIds.get(property), digraph); } Map<V, String> nodeIds = new HashMap<V, String>(); for (V vertex : digraph.vertices()) { writer.writeStartElement(GRAPHML_NAMESPACE_URI, "node"); String id = idGenerator.nextNodeId(); writer.writeAttribute("id", id); if (parseInfos.contains(GraphMLParseInfo.InDegree)) { writer.writeAttribute("parse.indegree", String.valueOf(reverse.getOutDegree(vertex))); } if (parseInfos.contains(GraphMLParseInfo.OutDegree)) { writer.writeAttribute("parse.outdegree", String.valueOf(digraph.getOutDegree(vertex))); } nodeIds.put(vertex, id); for (GraphMLProperty<V> property : provider.getNodeProperties()) { property.writeData(writer, propertyIds.get(property), vertex); } G subgraph = subgraphs == null ? null : subgraphs.get(vertex); if (subgraph != null) { write(provider, subgraphs, writer, subgraph, propertyIds, idGenerator); } writer.writeEndElement(); // node } for (V source : digraph.vertices()) { for (V target : digraph.targets(source)) { writer.writeStartElement(GRAPHML_NAMESPACE_URI, "edge"); E edge = digraph.get(source, target); if (parseInfos.contains(GraphMLParseInfo.EdgeIds)) { writer.writeAttribute("id", idGenerator.nextEdgeId()); } writer.writeAttribute("source", nodeIds.get(source)); writer.writeAttribute("target", nodeIds.get(target)); for (GraphMLProperty<E> property : provider.getEdgeProperties()) { property.writeData(writer, propertyIds.get(property), edge); } writer.writeEndElement(); // edge } } writer.writeEndElement(); // graph } public <V, E, G extends Digraph<? extends V, ? extends E>> void export( GraphMLProvider<V, E, G> provider, G digraph, DigraphProvider<? super V, G> subgraphs, XMLStreamWriter writer) throws XMLStreamException { writer.writeStartDocument("UTF-8", "1.0"); writer.setDefaultNamespace(GRAPHML_NAMESPACE_URI); writer.writeStartElement(GRAPHML_NAMESPACE_URI, "graphml"); Set<QName> nsdecls = new LinkedHashSet<QName>(); nsdecls.add(new QName(GRAPHML_NAMESPACE_URI, XMLConstants.DEFAULT_NS_PREFIX)); for (GraphMLProperty<?> property : provider.getGraphProperties()) { if (property.getNamespaceURI() != null) { nsdecls.add(new QName(property.getNamespaceURI(), property.getPrefix())); } } for (GraphMLProperty<?> property : provider.getNodeProperties()) { if (property.getNamespaceURI() != null) { nsdecls.add(new QName(property.getNamespaceURI(), property.getPrefix())); } } for (GraphMLProperty<?> property : provider.getEdgeProperties()) { if (property.getNamespaceURI() != null) { nsdecls.add(new QName(property.getNamespaceURI(), property.getPrefix())); } } for (QName extension : nsdecls) { writer.writeNamespace(extension.getLocalPart(), extension.getNamespaceURI()); } if (provider.getSchemaLocation() != null) { writer.writeNamespace("xsi", XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI); writer.writeAttribute( XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, "schemaLocation", GRAPHML_NAMESPACE_URI + " " + provider.getSchemaLocation()); } CanonicalIdGenerator idGenerator = new CanonicalIdGenerator(); Map<GraphMLProperty<?>, String> propertyIds = new HashMap<GraphMLProperty<?>, String>(); for (GraphMLProperty<?> property : provider.getGraphProperties()) { String id = idGenerator.nextDataId(); propertyIds.put(property, id); property.writeKey(writer, id); } for (GraphMLProperty<?> property : provider.getNodeProperties()) { String id = idGenerator.nextDataId(); propertyIds.put(property, id); property.writeKey(writer, id); } for (GraphMLProperty<?> property : provider.getEdgeProperties()) { String id = idGenerator.nextDataId(); propertyIds.put(property, id); property.writeKey(writer, id); } write(provider, subgraphs, writer, digraph, propertyIds, idGenerator); writer.writeEndElement(); // graphml writer.writeEndDocument(); writer.close(); } }