/* * Copyright 2012-present Facebook, Inc. * * 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.facebook.buck.graph; import com.google.common.base.Function; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Sets; import java.io.IOException; import java.util.Map; public class Dot<T> { private final DirectedAcyclicGraph<T> graph; private final String graphName; private final Function<T, String> nodeToName; private final Function<T, String> nodeToTypeName; private final Appendable output; private static final Map<String, String> typeColors; static { typeColors = new ImmutableMap.Builder<String, String>() { { put("android_aar", "springgreen2"); put("android_library", "springgreen3"); put("android_resource", "springgreen1"); put("android_prebuilt_aar", "olivedrab3"); put("java_library", "indianred1"); put("prebuilt_jar", "mediumpurple1"); } }.build(); } public Dot( DirectedAcyclicGraph<T> graph, String graphName, Function<T, String> nodeToName, Function<T, String> nodeToTypeName, Appendable output) { this.graph = graph; this.graphName = graphName; this.nodeToName = nodeToName; this.nodeToTypeName = nodeToTypeName; this.output = output; } public void writeOutput() throws IOException { output.append("digraph " + graphName + " {\n"); new AbstractBottomUpTraversal<T, RuntimeException>(graph) { @Override public void visit(T node) { String source = nodeToName.apply(node); String sourceType = nodeToTypeName.apply(node); try { output.append( String.format( " %s [style=filled,color=%s];\n", source, Dot.colorFromType(sourceType))); } catch (IOException e) { throw new RuntimeException(e); } for (T sink : graph.getOutgoingNodesFor(node)) { String sinkName = nodeToName.apply(sink); try { output.append(String.format(" %s -> %s;\n", source, sinkName)); } catch (IOException e) { throw new RuntimeException(e); } } } }.traverse(); output.append("}\n"); } public static <T> void writeSubgraphOutput( final DirectedAcyclicGraph<T> graph, final String graphName, final ImmutableSet<T> nodesToFilter, final Function<T, String> nodeToName, final Function<T, String> nodeToTypeName, final Appendable output, final boolean bfsSorted) throws IOException { // Sorting the edges to have deterministic output and be able to test this. final ImmutableSet.Builder<String> builder = ImmutableSet.builder(); output.append("digraph " + graphName + " {\n"); if (bfsSorted) { for (T root : ImmutableSortedSet.copyOf(graph.getNodesWithNoIncomingEdges())) { new AbstractBreadthFirstTraversal<T>(root) { @Override public Iterable<T> visit(T node) { if (!nodesToFilter.contains(node)) { return ImmutableSet.<T>of(); } String source = nodeToName.apply(node); String sourceType = nodeToTypeName.apply(node); builder.add( String.format( " %s [style=filled,color=%s];\n", source, Dot.colorFromType(sourceType))); ImmutableSortedSet<T> nodes = ImmutableSortedSet.copyOf( Sets.filter(graph.getOutgoingNodesFor(node), nodesToFilter::contains)); for (T sink : nodes) { String sinkName = nodeToName.apply(sink); builder.add(String.format(" %s -> %s;\n", source, sinkName)); } return nodes; } }.start(); } } else { final ImmutableSortedSet.Builder<String> sortedSetBuilder = ImmutableSortedSet.naturalOrder(); new AbstractBottomUpTraversal<T, RuntimeException>(graph) { @Override public void visit(T node) { if (!nodesToFilter.contains(node)) { return; } String source = nodeToName.apply(node); String sourceType = nodeToTypeName.apply(node); sortedSetBuilder.add( String.format( " %s [style=filled,color=%s];\n", source, Dot.colorFromType(sourceType))); for (T sink : Sets.filter(graph.getOutgoingNodesFor(node), nodesToFilter::contains)) { String sinkName = nodeToName.apply(sink); sortedSetBuilder.add(String.format(" %s -> %s;\n", source, sinkName)); } } }.traverse(); builder.addAll(sortedSetBuilder.build()); } for (String line : builder.build()) { output.append(line); } output.append("}\n"); } public static String colorFromType(String type) { if (Dot.typeColors.containsKey(type)) { return Dot.typeColors.get(type); } int r = 192 + (type.hashCode() % 64); int g = 192 + (type.hashCode() / 64 % 64); int b = 192 + (type.hashCode() / 4096 % 64); return String.format("\"#%02X%02X%02X\"", r, g, b); } }