/* * * 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.flex.compiler.internal.graph; import java.io.OutputStream; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.xml.transform.TransformerException; import org.w3c.dom.Element; import org.apache.flex.compiler.common.DependencyType; import org.apache.flex.compiler.common.DependencyTypeSet; import org.apache.flex.compiler.definitions.IDefinition; import org.apache.flex.compiler.internal.projects.DependencyGraph; import org.apache.flex.compiler.internal.targets.LinkageChecker; import org.apache.flex.compiler.problems.ICompilerProblem; import org.apache.flex.compiler.problems.UnableToBuildReportProblem; import org.apache.flex.compiler.units.ICompilationUnit; import org.apache.flex.compiler.units.ICompilationUnit.UnitType; /** * Class to write a {@link DependencyGraph} as a XML link report. * <p> * This graph will walk through the graph from the {@link ICompilationUnit} stored in roots * and construct a XML link report. * <p> * This has mostly been kept consistent with the link report from the old flex compiler, * however a few differences remain. We may link different things from the framework, * or the old compiler might have failed to find certain dependencies. * <p> * Without a concrete spec, this class is still a work in progress */ public class LinkReportWriter extends XMLGraphWriter implements IReportWriter { /** * A {@link Comparator} that sorts qnames {@link String} based on alphabetical order. * <p> * This will put qnames with a package definition before ones that don't. * Otherwise, the ordering will be alphabetical. */ public static class QNameComparator implements Comparator<String> { @Override public int compare(String a, String b) { boolean aSingleNamed = (a.lastIndexOf('.')) == -1; boolean bSingleNamed = (b.lastIndexOf('.')) == -1; if (aSingleNamed == bSingleNamed) return a.compareTo(b); else { return aSingleNamed ? 1 : -1; } } } public LinkReportWriter(DependencyGraph graph, List<ICompilationUnit> roots, LinkageChecker linkageChecker) { super(graph, roots); this.linkageChecker = linkageChecker; } private final LinkageChecker linkageChecker; /** * A helper function that adds the dependencies to a script tag in the link report * <p> * Prerequist dependencies using tag "<pre>" (those that contain inheritances) * will be written before other dependencies using tag "<dep>". * * @param script The script that the dependencies will be added to * @param dependencies A {@link Map} of current dependencies to an {@link Integer} representing the {@link DependencyType} */ private void addScriptDependencies(Element script, Map<String, DependencyTypeSet> dependencies) { List<Element> prereqNodes = new ArrayList<Element>(); List<Element> dependencyNodes = new ArrayList<Element>(); List<String> dependencyQNames = new ArrayList<String>(dependencies.keySet()); Collections.sort(dependencyQNames, new QNameComparator()); for (String qname: dependencyQNames) { if (qname.isEmpty()) { continue; } String xmlStyleQName = formatXMLStyleQName(qname); DependencyTypeSet dependencySet = dependencies.get(qname); String typeString = DependencyType.getTypeString(dependencySet); if(DependencyType.INHERITANCE.existsIn(dependencySet)) { Element preNode = doc.createElement("pre"); preNode.setAttribute("id", xmlStyleQName); preNode.setAttribute("type", typeString); prereqNodes.add(preNode); } else { Element preNode = doc.createElement("dep"); preNode.setAttribute("id", xmlStyleQName); preNode.setAttribute("type", typeString); dependencyNodes.add(preNode); } } for (Element tag : prereqNodes) { script.appendChild(tag); } for (Element tag : dependencyNodes) { script.appendChild(tag); } } /** * A helper function that creates a {@link Element} of type "script" representing an internal {@link ICompilationUnit} * with its definitions and dependencies. * <p> * The dependencies tags will come before the definitions of tags "<def>" * <p> * This should only be called after the threads are done. The function gets definition names from a * compilation unit by requesting a {@link IFileScopeRequestResult} from it and using the filescopes's * externally visible definitions. * @param bytecodeSize The size of the ABC byte code of this {@link ICompilationUnit} in number of bytes. * @param cu The {@link ICompilationUnit} to be encoded as a script tag * @return A {@link Element} that encodes the compilation unit and its definitions and dependencies. * @throws InterruptedException */ private Element createScriptTag(int bytecodeSize, ICompilationUnit cu) throws InterruptedException { Element script = doc.createElement("script"); script.setAttribute("name", cu.getName()); script.setAttribute("mod", String.valueOf(cu.getSyntaxTreeRequest().get().getLastModified())); script.setAttribute("size", Integer.toString(bytecodeSize)); Collection<IDefinition> definitions = cu.getFileScopeRequest().get().getExternallyVisibleDefinitions(); List<String> defQNames = new ArrayList<String>(); for (IDefinition definition : definitions) { String xmlStyleQName = formatXMLStyleQName(definition.getQualifiedName()); defQNames.add(xmlStyleQName); } Collections.sort(defQNames, new QNameComparator()); for (String definitionQName : defQNames) { Element definition = doc.createElement("def"); script.appendChild(definition); definition.setAttribute("id", definitionQName); } return script; } /** * A helper function that reads an {@link ICompilationUnit}'s dependencies. * <p> * External definitions will be added to a set given in its parameters * @param externalDefs A {@link Set} that external definitions will be added to * @param cu The {@link ICompilationUnit} that is being read * @return A {@link Map} from the qnames of the dependencies to the dependency types * @throws InterruptedException */ private Map<String, DependencyTypeSet> readEdges(Set<String> externalDefs, ICompilationUnit cu) throws InterruptedException { Map<String, DependencyTypeSet> dependencies = new HashMap<String, DependencyTypeSet>(); Collection<ICompilationUnit> dependentUnits = graph.getDirectDependencies(cu); for (ICompilationUnit dependentUnit: dependentUnits) { Map<String, DependencyTypeSet> edgeDeps = graph.getDependencySet(cu, dependentUnit); dependencies.putAll(edgeDeps); Collection<String> edgeQNames = edgeDeps.keySet(); if (isLinkageExternal(dependentUnit)) { externalDefs.addAll(edgeQNames); } } return dependencies; } /** * A helper function that will sort the root nodes in a reverse topological order and * filter out all the internal {@link ICompilationUnit} that are visible. * @return A sorted {@link List} of {@link ICompilationUnit} that are both visible from the * root nodes and internal to the project. * @throws InterruptedException */ private List<ICompilationUnit> extractSortedInternalUnits() throws InterruptedException { List<ICompilationUnit> internalUnits = new ArrayList<ICompilationUnit>(); List<ICompilationUnit> vertices = graph.topologicalSort(roots); Collections.reverse(vertices); for (ICompilationUnit vertex : vertices) { if (!isLinkageExternal(vertex)) { internalUnits.add(vertex); } } return internalUnits; } /** * Determine if a compilation unit should be linked into the target. * * @param cu The compilation unit to test. * @param linkageChecker class to check the linkage of compilation units. * @return true if the compilation unit's linkage is external, false * otherwise. * @throws InterruptedException */ private boolean isLinkageExternal(ICompilationUnit cu) throws InterruptedException { return (cu.getCompilationUnitType() == UnitType.SWC_UNIT && linkageChecker.isExternal(cu)); } @Override public void writeToStream(OutputStream outStream, Collection<ICompilerProblem> problems) throws InterruptedException { List<ICompilationUnit> internalUnits = extractSortedInternalUnits(); Map<ICompilationUnit, Integer> bytecodeSizes = InvalidationBytesCalculator.calculateBytesChanged(internalUnits); Set<String> externalDefs = new HashSet<String>(); Element scripts = doc.createElement("scripts"); for (ICompilationUnit cu : internalUnits) { Element script = createScriptTag(bytecodeSizes.get(cu), cu); addScriptDependencies(script, readEdges(externalDefs, cu)); scripts.appendChild(script); } Element externalDefinitions = doc.createElement("external-defs"); List<String> extDefinitionList = new ArrayList<String>(externalDefs); Collections.sort(extDefinitionList, new QNameComparator()); for (String ext: extDefinitionList) { Element extNode = doc.createElement("ext"); extNode.setAttribute("id", formatXMLStyleQName(ext)); externalDefinitions.appendChild(extNode); } Element report = doc.createElement("report"); report.appendChild(scripts); report.appendChild(externalDefinitions); doc.appendChild(report); try { writeReport(outStream); } catch (TransformerException e) { problems.add(new UnableToBuildReportProblem(e)); } } }