Package com.asakusafw.compiler.flow.visualizer

Source Code of com.asakusafw.compiler.flow.visualizer.VisualGraphEmitter$EmitContext

/**
* Copyright 2011-2014 Asakusa Framework Team.
*
* 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 com.asakusafw.compiler.flow.visualizer;

import java.io.Closeable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.asakusafw.compiler.common.Precondition;
import com.asakusafw.compiler.flow.plan.FlowBlock;
import com.asakusafw.compiler.flow.visualizer.VisualNode.Kind;
import com.asakusafw.utils.collections.Lists;
import com.asakusafw.utils.collections.Maps;
import com.asakusafw.utils.collections.Sets;
import com.asakusafw.utils.java.internal.model.util.LiteralAnalyzer;
import com.asakusafw.utils.java.model.util.NoThrow;
import com.asakusafw.vocabulary.flow.graph.FlowElement;
import com.asakusafw.vocabulary.flow.graph.FlowElementInput;
import com.asakusafw.vocabulary.flow.graph.FlowElementKind;
import com.asakusafw.vocabulary.flow.graph.FlowElementOutput;
import com.asakusafw.vocabulary.flow.graph.FlowIn;
import com.asakusafw.vocabulary.flow.graph.FlowOut;
import com.asakusafw.vocabulary.flow.graph.FlowPartDescription;
import com.asakusafw.vocabulary.flow.graph.OperatorDescription;

/**
* Emits {@link VisualGraph} object as Graphviz dot format.
* @since 0.1.0
* @version 0.4.0
*/
public final class VisualGraphEmitter {

    static final Charset ENCODING = Charset.forName("UTF-8");

    static final Logger LOG = LoggerFactory.getLogger(VisualGraphEmitter.class);

    private VisualGraphEmitter() {
        throw new AssertionError();
    }

    /**
     * Outputs a {@link VisualGraph} object to target file as Graphviz dot format.
     * @param graph target graph
     * @param partial {@code true} if target graph is partial, otherwise {@code false}
     * @param stream output target
     * @throws IOException if failed to output
     * @throws IllegalArgumentException if some parameters were {@code null}
     */
    public static void emit(VisualGraph graph, boolean partial, OutputStream stream) throws IOException {
        Precondition.checkMustNotBeNull(graph, "graph"); //$NON-NLS-1$
        Precondition.checkMustNotBeNull(stream, "stream"); //$NON-NLS-1$
        LOG.debug("Emitting a visual graph: {}", graph.getId());
        EmitContext context = new EmitContext(stream);
        try {
            List<Relation> relations = analyzeRelations(graph, partial);
            dump(context, graph.getNodes(), relations);
        } finally {
            context.close();
        }
    }

    /**
     * Outputs a {@link VisualGraph} object to target file as Graphviz dot format.
     * @param graph target graph
     * @param partial {@code true} if target graph is partial, otherwise {@code false}
     * @param file output target
     * @throws IOException if failed to output
     * @throws IllegalArgumentException if some parameters were {@code null}
     * @since 0.4.0
     */
    public static void emit(VisualGraph graph, boolean partial, File file) throws IOException {
        Precondition.checkMustNotBeNull(graph, "graph"); //$NON-NLS-1$
        Precondition.checkMustNotBeNull(file, "file"); //$NON-NLS-1$
        OutputStream output = new FileOutputStream(file);
        try {
            emit(graph, partial, output);
        } finally {
            output.close();
        }
    }

    private static List<Relation> analyzeRelations(VisualGraph graph, boolean partial) {
        assert graph != null;
        LOG.debug("Analyzing element relations");
        List<Relation> result = RelationCollector.collect(Collections.singleton(graph), partial);
        return result;
    }

    private static void dump(EmitContext context, Set<VisualNode> nodes, List<Relation> relations) {
        assert context != null;
        assert nodes != null;
        assert relations != null;
        LOG.debug("Emitting graph structure");
        context.put("digraph {");
        context.push();
        dumpStructure(context, nodes);
        dumpLabels(context, relations);
        dumpRelations(context, relations);
        context.pop();
        context.put("}");
    }

    private static void dumpLabels(EmitContext context, List<Relation> relations) {
        assert relations != null;
        Set<UUID> saw = Sets.create();
        for (Relation relation : relations) {
            if (saw.contains(relation.source.getResolved().getId()) == false) {
                dumpLabel(context, relation.source.getResolved());
                saw.add(relation.source.getResolved().getId());
            }
            if (saw.contains(relation.source.getResolved().getId()) == false) {
                dumpLabel(context, relation.source.getResolved());
                saw.add(relation.source.getResolved().getId());
            }
            dumpLabel(context, relation.sink.getResolved());
        }
    }

    private static void dumpLabel(EmitContext context, VisualNode node) {
        assert node != null;
        if (node.getKind() == Kind.LABEL) {
            StructureEmitter emitter = new StructureEmitter();
            node.accept(emitter, context);
        }
    }

    private static void dumpStructure(EmitContext context, Set<VisualNode> nodes) {
        assert context != null;
        assert nodes != null;
        StructureEmitter emitter = new StructureEmitter();
        for (VisualNode node : nodes) {
            node.accept(emitter, context);
        }
    }

    private static void dumpRelations(EmitContext context, List<Relation> relations) {
        assert context != null;
        assert relations != null;
        for (Relation relation : relations) {
            context.put("{0} -> {1} [label={2}];",
                    toLiteral(relation.source.getResolved().getId().toString()),
                    toLiteral(relation.sink.getResolved().getId().toString()),
                    toLiteral(MessageFormat.format(
                            "{0}>{1}",
                            relation.source.name,
                            relation.sink.name)));
        }
    }

    static String toLiteral(String string) {
        assert string != null;
        return LiteralAnalyzer.stringLiteralOf(string);
    }

    private static class RelationCollector extends VisualNodeVisitor<Void, Void, NoThrow> {

        private final boolean partial;

        private final Set<Relation> saw = Sets.create();

        final List<Relation> relations = Lists.create();

        final Map<FlowElement, VisualNode> resolveMap = Maps.create();

        RelationCollector(boolean partial) {
            this.partial = partial;
        }

        static List<Relation> collect(Iterable<? extends VisualNode> nodes, boolean partial) {
            RelationCollector engine = new RelationCollector(partial);
            engine.acceptAll(null, nodes);
            Iterator<Relation> iter = engine.relations.iterator();
            while (iter.hasNext()) {
                Relation relation = iter.next();
                boolean removed = false;
                if (engine.resolve(relation.source) == false) {
                    if (partial == false) {
                        resolveFailed(relation.source);
                    }
                    iter.remove();
                    removed = true;
                }
                if (engine.resolve(relation.sink) == false) {
                    if (partial == false) {
                        resolveFailed(relation.sink);
                    }
                    if (removed == false) {
                        iter.remove();
                    }
                }
            }
            return engine.relations;
        }

        private static void resolveFailed(Port port) {
            assert port != null;
            LOG.warn("Failed to resolve an element {}, ignored.", port.element);
        }

        @Override
        protected Void visitGraph(Void context, VisualGraph node) {
            acceptAll(context, node.getNodes());
            return null;
        }

        @Override
        protected Void visitBlock(Void context, VisualBlock node) {
            if (partial) {
                for (FlowBlock.Input input : node.getInputs()) {
                    Port sink = toPort(input.getElementPort());
                    for (FlowBlock.Connection conn : input.getConnections()) {
                        Port source = toPort(conn.getUpstream().getElementPort());
                        related(source, sink);
                    }
                }
            }

            for (FlowBlock.Output output : node.getOutputs()) {
                Port source = toPort(output.getElementPort());
                for (FlowBlock.Connection conn : output.getConnections()) {
                    FlowElementInput downstream = conn.getDownstream().getElementPort();
                    connect(source, downstream);
                }
            }
            acceptAll(context, node.getNodes());
            return null;
        }

        @Override
        protected Void visitFlowPart(Void context, VisualFlowPart node) throws NoThrow {
            register(node, node.getElement());
            connectSuccessors(node.getElement());
            acceptAll(context, node.getNodes());
            return null;
        }

        @Override
        protected Void visitElement(Void context, VisualElement node) {
            register(node, node.getElement());
            connectSuccessors(node.getElement());
            return null;
        }

        private void connectSuccessors(FlowElement element) {
            assert element != null;
            if (element.getDescription().getKind() == FlowElementKind.FLOW_COMPONENT) {
                FlowPartDescription desc = (FlowPartDescription) element.getDescription();
                for (FlowElementOutput output : element.getOutputPorts()) {
                    FlowOut<?> internal = desc.getInternalOutputPort(output.getDescription());
                    Port source = toPort(internal.toInputPort());
                    for (FlowElementInput downstream : output.getOpposites()) {
                        connect(source, downstream);
                    }
                }
            } else {
                for (FlowElementOutput output : element.getOutputPorts()) {
                    Port source = toPort(output);
                    for (FlowElementInput downstream : output.getOpposites()) {
                        connect(source, downstream);
                    }
                }
            }
        }

        private void connect(Port source, FlowElementInput downstream) {
            assert source != null;
            assert downstream != null;
            if (downstream.getOwner().getDescription().getKind() == FlowElementKind.FLOW_COMPONENT) {
                FlowPartDescription desc = (FlowPartDescription) downstream.getOwner().getDescription();
                FlowIn<?> internal = desc.getInternalInputPort(downstream.getDescription());
                Port sink = toPort(internal.toOutputPort());
                related(source, sink);
            } else {
                Port sink = toPort(downstream);
                related(source, sink);
            }
        }

        private Port toPort(FlowElementOutput port) {
            assert port != null;
            return new Port(port.getOwner(), port.getDescription().getName());
        }

        private Port toPort(FlowElementInput port) {
            assert port != null;
            return new Port(port.getOwner(), port.getDescription().getName());
        }

        private boolean resolve(Port port) {
            assert port != null;
            VisualNode node = resolveMap.get(port.element);
            if (node != null) {
                port.setResolved(node);
                return true;
            }
            if (partial) {
                port.setResolved(new VisualLabel(null));
                return true;
            }
            return false;
        }

        private void register(VisualNode node, FlowElement element) {
            assert node != null;
            assert element != null;
            resolveMap.put(element, node);
        }

        private void related(Port source, Port sink) {
            assert source != null;
            assert sink != null;
            Relation relation = new Relation(source, sink);
            if (saw.contains(relation) == false) {
                relations.add(relation);
                saw.add(relation);
            }
        }

        private void acceptAll(Void context, Iterable<? extends VisualNode> nodes) {
            for (VisualNode node : nodes) {
                node.accept(this, context);
            }
        }
    }

    private static class Relation {

        final Port source;

        final Port sink;

        Relation(Port source, Port sink) {
            assert source != null;
            assert sink != null;
            this.source = source;
            this.sink = sink;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + sink.hashCode();
            result = prime * result + source.hashCode();
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            Relation other = (Relation) obj;
            if (!sink.equals(other.sink)) {
                return false;
            }
            if (!source.equals(other.source)) {
                return false;
            }
            return true;
        }
    }

    private static class Port {

        final FlowElement element;

        final String name;

        private VisualNode resolved;

        public Port(FlowElement element, String name) {
            assert element != null;
            assert name != null;
            this.element = element;
            this.name = name;
        }

        public VisualNode getResolved() {
            assert resolved != null;
            return resolved;
        }

        public void setResolved(VisualNode resolved) {
            assert resolved != null;
            assert this.resolved == null || this.resolved == resolved;
            this.resolved = resolved;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + element.hashCode();
            result = prime * result + name.hashCode();
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            Port other = (Port) obj;
            if (!element.equals(other.element)) {
                return false;
            }
            if (!name.equals(other.name)) {
                return false;
            }
            return true;
        }
    }

    private static class StructureEmitter extends VisualNodeVisitor<Void, EmitContext, NoThrow> {

        StructureEmitter() {
            return;
        }

        @Override
        public Void visitGraph(EmitContext context, VisualGraph node) {
            if (node.getLabel() != null) {
                context.put("subgraph {0} '{'",
                        toLiteral("cluster_" + node.getId().toString()));
                context.push();
                context.put("label = {0};", toLiteral(node.getLabel()));
                context.put("style = bold;");
            }
            for (VisualNode element : node.getNodes()) {
                element.accept(this, context);
            }
            if (node.getLabel() != null) {
                context.pop();
                context.put("}");
            }
            return null;
        }

        @Override
        protected Void visitBlock(EmitContext context, VisualBlock node) throws NoThrow {
            if (node.getLabel() != null) {
                context.put("subgraph {0} '{'",
                        toLiteral("cluster_" + node.getId().toString()));
                context.push();
                context.put("label = {0};", toLiteral(node.getLabel()));
            }
            for (VisualNode element : node.getNodes()) {
                element.accept(this, context);
            }
            if (node.getLabel() != null) {
                context.pop();
                context.put("}");
            }
            return null;
        }

        @Override
        protected Void visitFlowPart(EmitContext context, VisualFlowPart node) throws NoThrow {
            context.put("subgraph {0} '{'",
                    toLiteral("cluster_" + node.getId().toString()));
            context.push();
            context.put("label = {0};",
                    toLiteral(node.getElement().getDescription().getName()));
            for (VisualNode element : node.getNodes()) {
                element.accept(this, context);
            }
            context.pop();
            context.put("}");
            return null;
        }

        @Override
        protected Void visitElement(EmitContext context, VisualElement node) {
            FlowElement element = node.getElement();
            switch (element.getDescription().getKind()) {
            case INPUT:
            case OUTPUT:
                context.put("{0} [shape=invhouse, label={1}];",
                        toLiteral(node.getId().toString()),
                        toLiteral(element.getDescription().getName()));
                break;
            case OPERATOR:
                context.put("{0} [shape=box, label={1}];",
                        toLiteral(node.getId().toString()),
                        toLiteral(toOperatorName(node)));
                break;
            case FLOW_COMPONENT:
                context.put("{0} [shape=component, label={1}];",
                        toLiteral(node.getId().toString()),
                        toLiteral(element.getDescription().getName()));
                break;
            default:
                context.put("{0} [shape=point];",
                        toLiteral(node.getId().toString()));
                break;
            }
            return null;
        }

        @Override
        protected Void visitLabel(EmitContext context, VisualLabel node) {
            if (node.getLabel() == null) {
                context.put("{0} [shape=point];", toLiteral(node.getId().toString()));
            } else {
                context.put("{0} [shape=ellipse, label={1}];",
                        toLiteral(node.getId().toString()),
                        toLiteral(node.getLabel()));
            }
            return super.visitLabel(context, node);
        }

        static String toOperatorName(VisualElement node) {
            assert node != null;
            FlowElement element = node.getElement();
            assert element.getDescription().getKind() == FlowElementKind.OPERATOR;
            OperatorDescription desc = (OperatorDescription) element.getDescription();
            StringBuilder buf = new StringBuilder();
            buf.append("@");
            buf.append(desc.getDeclaration().getAnnotationType().getSimpleName());
            buf.append("\n");
            buf.append(desc.getName());
            return buf.toString();
        }
    }

    private static class EmitContext implements Closeable {

        private static final int INDENT_UNIT = 4;

        private final PrintWriter writer;

        private int indent = 0;

        public EmitContext(OutputStream output) {
            assert output != null;
            writer = new PrintWriter(new OutputStreamWriter(output, ENCODING));
        }

        public void push() {
            indent++;
        }

        public void pop() {
            assert indent >= 1;
            indent--;
        }

        public void put(String pattern, Object... arguments) {
            assert pattern != null;
            assert arguments != null;
            StringBuilder buf = new StringBuilder();
            insertIndent(buf);
            if (arguments.length == 0) {
                buf.append(pattern);
            } else {
                buf.append(MessageFormat.format(pattern, arguments));
            }
            String text = buf.toString();
            writer.println(text);
            LOG.debug(text);
        }

        private void insertIndent(StringBuilder buf) {
            for (int i = 0, n = indent * INDENT_UNIT; i < n; i++) {
                buf.append(' ');
            }
        }

        @Override
        public void close() {
            writer.close();
        }
    }
}
TOP

Related Classes of com.asakusafw.compiler.flow.visualizer.VisualGraphEmitter$EmitContext

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.