blob: 2243673932ae32938fffd87b051d508864693571 [file] [log] [blame]
/*
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.royale.compiler.internal.graph;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import javax.xml.transform.TransformerException;
import org.w3c.dom.Element;
import org.apache.royale.compiler.common.DependencyType;
import org.apache.royale.compiler.common.DependencyTypeSet;
import org.apache.royale.compiler.internal.projects.DependencyGraph;
import org.apache.royale.compiler.internal.targets.LinkageChecker;
import org.apache.royale.compiler.problems.ICompilerProblem;
import org.apache.royale.compiler.problems.UnableToBuildReportProblem;
import org.apache.royale.compiler.units.ICompilationUnit;
import org.apache.royale.compiler.units.ICompilationUnit.UnitType;
/**
* Class to write a {@link DependencyGraph} as a yed-format graphml file.
* <p>
* This graph will walk through the graph from the {@link ICompilationUnit} stored in roots
* and construct a graphml report from the edges.
* <p>
* In yed, the edges will be visually distinguishable from each other:
* <ul>
* <li>If a dependency contains inheritance, the edge in the graphml report will be solid</li>
* <li>If a dependency is signature/namespace but is not an inheritance relationship,
* the edge in the graphml report will be dashed</li>
* <li>If a dependency only contains an expression dependency, the edge will be dotted</li>
* </ul>
*/
public class GraphMLWriter extends XMLGraphWriter implements IReportWriter
{
/**
* GraphMLWriter constructor
*
* @param graph A {@link DependencyGraph} that this class will report on
* @param roots A list of {@link ICompilationUnit} that the graph walker will start on.
* Only units that are reachable from these units will be reported on.
* @param linkageChecker class to check the linkage of compilation units.
* May not be null.
*/
public GraphMLWriter(DependencyGraph graph, Collection<ICompilationUnit> roots,
boolean useExternalDependencies,
LinkageChecker linkageChecker)
{
super(graph, roots);
assert linkageChecker != null : "linkageChecker may not be null";
this.useExternalDependencies = useExternalDependencies;
this.linkageChecker = linkageChecker;
}
private boolean useExternalDependencies;
private String yNSuri;
private LinkageChecker linkageChecker;
/**
* A helper function that will read the {@link DependencyGraph} that was stored in this class and construct
* a {@link Element} of type "graph" by walking through the graph from the root nodes.
* <p>
* Do not call this while threads are still running. This function reads the bytes from each {@link ICompilationUnit}
* in order to find the ABC byte size of each unit. If the compilation threads are still running, you might get an
* exception or an invalid byte size.
*
* @return a {@link Element} of type "graph" that represents the contents of the {@link DependencyGraph}
*/
private Element readGraph() throws InterruptedException {
//reads a graph and its set of edges and stores them in a few arraylists
Element graphTag = doc.createElement("graph");
Map<ICompilationUnit, Integer> bytesChanged = InvalidationBytesCalculator.calculateBytesChanged(roots);
Map<ICompilationUnit, Integer> totalBytesChanged = InvalidationBytesCalculator.calculateTotalInvalidatedBytesChanged(roots);
ArrayList<ICompilationUnit> visibleVertices;
if(!useExternalDependencies)
{
visibleVertices = new ArrayList<ICompilationUnit>(readVisibleInternalVertices(roots));
}
else
{
visibleVertices = new ArrayList<ICompilationUnit>(readVisibleExternalVertices(roots));
}
int nodeIndex = 0;
/**
* Wad Class just to sort the edges and dependencies
*/
class EdgePair {
int fromIndex;
int toIndex;
DependencyTypeSet typeSet;
public EdgePair(int fromIndex, int toIndex, DependencyTypeSet typeSet)
{
this.fromIndex = fromIndex;
this.toIndex = toIndex;
this.typeSet = typeSet;
}
}
List<EdgePair> edges = new ArrayList<EdgePair>();
for (int i = 0; i < visibleVertices.size(); i++)
{
ICompilationUnit vertex = visibleVertices.get(i);
Element nodeTag = doc.createElement("node");
nodeTag.setAttribute("id", "n" + Integer.toString(nodeIndex));
String labelName = vertex.getName();
String label = labelName + "[" + bytesChanged.get(vertex) + " bytes | " + totalBytesChanged.get(vertex) + " total invalidated bytes]";
nodeTag.appendChild(createNodeDataTag(label, isExternal(vertex)));
graphTag.appendChild(nodeTag);
if (useExternalDependencies || !isExternal(vertex))
{
Collection<ICompilationUnit> dependentUnits = graph.getDirectDependencies(vertex);
for (ICompilationUnit toVertex : dependentUnits)
{
edges.add(new EdgePair(i, visibleVertices.indexOf(toVertex), graph.getDependencyTypes(vertex, toVertex)));
}
}
nodeIndex++;
}
int edgeIndex = 0;
for (EdgePair edge : edges)
{
int from = edge.fromIndex;
int to = edge.toIndex;
assert from > -1;
assert to > -1;
Element edgeTag = doc.createElement("edge");
edgeTag.setAttribute("id", "e" + Integer.toString(edgeIndex));
edgeTag.setAttribute("source", "n" + Integer.toString(from));
edgeTag.setAttribute("target", "n" + Integer.toString(to));
edgeTag.appendChild(createEdgeDataTag(edge.typeSet));
graphTag.appendChild(edgeTag);
edgeIndex++;
}
//tags.addAll(readGraphEdges(level, visibleVertices));*/
return graphTag;
}
/**
* Test is the compilation unit is external or should be linked into the
* application.
*
* @param vertex the compilation unit.
* @return true if the compilation should NOT be linked into the application,
* false otherwise.
* @throws InterruptedException
*/
private boolean isExternal(ICompilationUnit vertex) throws InterruptedException
{
if (vertex.getCompilationUnitType() == UnitType.SWC_UNIT &&
linkageChecker.isExternal(vertex))
{
return true;
}
return false;
}
/**
* Helper function to create a {@link Element} representing a node of {@link ICompilationUnit} in the {@link DependencyGraph}.
*
* @param label A {@link String} representing the compilation unit. This will be displayed on the node in yed when the report is opened.
* @param external A {@link boolean} representing whether this node is an external compilation unit or an internal one. The color of the node
* depends on this.
* @return A {@link Element} representing a node of {@link ICompilationUnit} in the {@link DependencyGraph}
*/
private Element createNodeDataTag(String label, boolean external)
{
Element dataTag = doc.createElement("data");
dataTag.setAttribute("key", "d0");
Element shapeNodeTag = doc.createElementNS(yNSuri, "y:ShapeNode");
String colorString = external ? "#FFCC00": "#CCCCFF";
Element fillTag = doc.createElementNS(yNSuri, "y:Fill");
fillTag.setAttribute("color", colorString);
fillTag.setAttribute("transparent", "false");
Element labelTag = doc.createElementNS(yNSuri, "y:NodeLabel");
labelTag.setTextContent(label);
shapeNodeTag.appendChild(fillTag);
shapeNodeTag.appendChild(labelTag);
dataTag.appendChild(shapeNodeTag);
return dataTag;
}
/**
* Helper function to create a {@link Element} representing a dependency
* based on a a {@link DependencyTypeSet} in the {@link DependencyGraph}.
*
* @param edge An {@link DependencyTypeSet} that will be encoded in a graphml edge tag.
* @return A {@link Element} representing a dependency in the {@link DependencyGraph}
*/
private Element createEdgeDataTag(DependencyTypeSet typeSet)
{
Element dataTag = doc.createElement("data");
dataTag.setAttribute("key", "d1");
Element polyLineEdgeTag = doc.createElementNS(yNSuri, "y:PolyLineEdge");
String lineStyle;
if (DependencyType.INHERITANCE.existsIn(typeSet))
{
lineStyle = "solid";
}
else if(DependencyType.NAMESPACE.existsIn(typeSet) || DependencyType.SIGNATURE.existsIn(typeSet))
{
lineStyle = "dashed";
}
else
{
lineStyle = "dotted";
}
Element lineStyleTag = doc.createElementNS(yNSuri, "y:LineStyle");
lineStyleTag.setAttribute("type", lineStyle);
Element arrowsTag = doc.createElementNS(yNSuri, "y:Arrows");
arrowsTag.setAttribute("target", "standard");
arrowsTag.setAttribute("source", "none");
Element labelTag = doc.createElementNS(yNSuri, "y:EdgeLabel");
labelTag.setTextContent(DependencyType.getTypeString(typeSet));
polyLineEdgeTag.appendChild(lineStyleTag);
polyLineEdgeTag.appendChild(arrowsTag);
polyLineEdgeTag.appendChild(labelTag);
dataTag.appendChild(polyLineEdgeTag);
return dataTag;
}
/**
* A helper function that will walk through the {@link DependencyGraph} and return a Set of all {@link ICompilationUnit}
* that is visible from the roots.
* @param roots The The {@link Collection} of root {@link ICompilationUnit} that this function will walk through
* @return A {@link Set} of visible {@link ICompilationUnit} from the roots
*/
private Set<ICompilationUnit> readVisibleExternalVertices(Collection<ICompilationUnit> roots)
{
Set<ICompilationUnit> dependentVertices = new HashSet<ICompilationUnit>();
Stack<ICompilationUnit> unsearched = new Stack<ICompilationUnit>();
unsearched.addAll(roots);
while (!unsearched.isEmpty())
{
unsearched.addAll(readDependencies(dependentVertices, unsearched.pop()));
}
return dependentVertices;
}
/**
* A helper function that will walk through the {@link DependencyGraph} and return a Set of {@link ICompilationUnit}
* that are either internal and visible from the root units OR external and a direct dependency of a visible internal node.
*
* @param roots The {@link Collection} of root {@link ICompilationUnit} that this function will walk through
* @return A {@link Set} of visible internal {@link ICompilationUnit} or direct external dependencies of one
* @throws InterruptedException
*/
private Set<ICompilationUnit> readVisibleInternalVertices(Collection<ICompilationUnit> roots) throws InterruptedException
{
Set<ICompilationUnit> dependentVertices = new HashSet<ICompilationUnit>();
List<ICompilationUnit> internalVertices = new ArrayList<ICompilationUnit>();
for (ICompilationUnit vertex : roots)
{
if(!isExternal(vertex))
internalVertices.add(vertex);
}
for (ICompilationUnit vertex : internalVertices)
{
dependentVertices.addAll(readDependencies(dependentVertices, vertex));
}
return dependentVertices;
}
/**
* A helper function that will return a set of {@link ICompliationUnit} are direct dependents of the vertex that are not
* in the closed set dependentVertices
* @param dependentVertices A closed {@link Set} of {@link ICompilationUnit}
* @param vertex The {@link ICompilationUnit} that this function will find direct depedencies on
* @return A {@link Set} of new {@link ICompilationUnit} that are direct dependencies of vertex but not in the closed set.
*/
private Set<ICompilationUnit> readDependencies(Set<ICompilationUnit> dependentVertices, ICompilationUnit vertex)
{
dependentVertices.add(vertex);
Set<ICompilationUnit> newDependencies = new HashSet<ICompilationUnit>();
Set<ICompilationUnit> dependencies = graph.getDirectDependencies(vertex);
for (ICompilationUnit dependency : dependencies)
{
if(!dependentVertices.contains(dependency))
{
newDependencies.add(dependency);
}
}
return newDependencies;
}
@Override
public void writeToStream(OutputStream outStream, Collection<ICompilerProblem> problems) throws InterruptedException
{
String uri_xmlns = "http://graphml.graphdrawing.org/xmlns";
String defaultns = "http://www.w3.org/2000/xmlns/";
String uri_xsi = "http://www.w3.org/2001/XMLSchema-instance";
String uri_yed = "http://www.yworks.com/xml/yed/3";
String uri_schema = "http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd";
yNSuri = "http://www.yworks.com/xml/graphml";
Element graphmlTag = doc.createElement("graphml");
graphmlTag.setAttribute("xmlns", uri_xmlns);
graphmlTag.setAttributeNS(defaultns, "xmlns:xsi", uri_xsi);
graphmlTag.setAttributeNS(defaultns, "xmlns:y", yNSuri);
graphmlTag.setAttributeNS(defaultns, "xmlns:yed", uri_yed);
graphmlTag.setAttributeNS(uri_xsi, "xsi:schemaLocation", uri_schema);
Element key0 = doc.createElement("key");
key0.setAttribute("for", "node");
key0.setAttribute("id", "d0");
key0.setAttribute("yfiles.type", "nodegraphics");
Element key1 = doc.createElement("key");
key1.setAttribute("for", "edge");
key1.setAttribute("id", "d1");
key1.setAttribute("yfiles.type", "edgegraphics");
graphmlTag.appendChild(key0);
graphmlTag.appendChild(key1);
graphmlTag.appendChild(readGraph());
doc.appendChild(graphmlTag);
try
{
writeReport(outStream);
}
catch (TransformerException e)
{
problems.add(new UnableToBuildReportProblem(e));
}
}
}