blob: 0765d48d84d3898d4edbe1794f9a96d86f6d2743 [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 flex2.linker;
import flex2.compiler.util.graph.Visitor;
import java.util.*;
/**
* Walk the dependency graph implied by a collection of linkables, and visit
* each linkable; prerequisites form a DAG and are visited in DFS order.
* (Non-prerequisite connected linkables are visited in an arbitrary order.)
*
* Topological sort of DAG G is equivilent to the DFS of the graph G'
* where for every edge (u,v) in G there is an edge (v,u) in the transposed
* graph G'.
*
* This is handy since dependencies in Flex are a transposed DAG
* (edges point to predecessors, not successors).
*
* @author Roger Gonzalez
*/
public class DependencyWalker
{
/**
* A value object which maintains external definitions, included
* definitions, unresolved definitions.
*/
public static class LinkState
{
public LinkState( Collection linkables, Set extdefs, Set includes, Set<String> unresolved )
throws LinkerException
{
this.extdefs = extdefs;
this.includes = includes;
this.unresolved = unresolved;
// Build the defname -> linkable map and check for non-unique linkables
for (Iterator li = linkables.iterator(); li.hasNext();)
{
Linkable l = (Linkable) li.next();
if (lmap.containsKey( l.getName() ))
{
throw new LinkerException.DuplicateSymbolException( l.getName() );
}
LinkableContext lc = new LinkableContext( l );
lmap.put( l.getName(), lc);
String external = null;
for (Iterator di = l.getDefinitions(); di.hasNext();)
{
String def = (String) di.next();
LinkableContext c = defs.get( def );
if (c != null)
{
throw new LinkerException.MultipleDefinitionsException( def, l.getName(), c.l.getName() );
}
defs.put( def, lc );
if (extdefs.contains( def ))
{
external = def;
}
else if (external != null)
{
throw new LinkerException.PartialExternsException( lc.l.getName(), def, external );
}
}
}
}
public Set<String> getUnresolved()
{
return unresolved;
}
public Set getExternal()
{
return extdefs;
}
public Set getIncludes()
{
return includes;
}
public Set<String> getDefNames()
{
return defs.keySet();
}
public Collection<LinkableContext> getLinkables()
{
return lmap.values();
}
public Collection<Linkable> getVisitedLinkables()
{
return vmap.values();
}
Map<String, Linkable> vmap = new HashMap<String, Linkable>();
Map<String, LinkableContext> lmap = new HashMap<String, LinkableContext>();
Map<String, LinkableContext> defs = new HashMap<String, LinkableContext>();
Set extdefs;
Set includes;
Set<String> unresolved;
}
/**
* @param defs the base definition set to start traversal, if null, link all.
* @param state a (mostly opaque) state object that can be used for multiple traversals
* @param v the visitor to invoke for each linkable
* @throws LinkerException
*/
public static void traverse( List<String> defs, LinkState state, boolean allowExternal, boolean exportIncludes,
boolean includeInheritanceDependenciesOnly, Visitor<Linkable> v )
throws LinkerException
{
if (defs == null)
{
// If we want inheritance dependencies only, skip populating defs with all the non-external names.
defs = new LinkedList<String>();
if (!includeInheritanceDependenciesOnly)
{
for (Iterator<String> it = state.getDefNames().iterator(); it.hasNext();)
{
String def = it.next();
if (!state.getExternal().contains( def ))
{
defs.add( def );
}
}
}
}
if (exportIncludes)
{
for (Iterator iterator = state.getIncludes().iterator(); iterator.hasNext();)
{
String def = (String)iterator.next();
defs.add( def );
}
}
Stack<LinkableContext> stack = new Stack<LinkableContext>(); // holds contexts
LinkedList<LinkableContext> queue = new LinkedList<LinkableContext>(); // holds contexts
for (Iterator<String> it = defs.iterator(); it.hasNext();)
{
String defname = it.next();
LinkableContext start = resolve( defname, state, allowExternal, exportIncludes,
includeInheritanceDependenciesOnly);
if (start == null)
continue;
queue.add( start );
}
while (!queue.isEmpty())
{
LinkableContext qc = queue.removeFirst();
if (qc.visited)
continue;
qc.progress = true;
stack.push( qc );
while (!stack.isEmpty())
{
LinkableContext c = stack.peek();
if (c.visited)
{
stack.pop();
continue;
}
if (c.pi.hasNext())
{
LinkableContext prereq = resolve( (String) c.pi.next(), state,
allowExternal,
exportIncludes,
includeInheritanceDependenciesOnly);
if (prereq != null)
{
if (prereq.progress)
{
throw new LinkerException.CircularReferenceException( c.l.getName() );
}
if (!prereq.visited)
{
prereq.progress = true;
stack.push( prereq );
}
}
continue;
}
// if (c.visited)
// {
// throw new DependencyException( DependencyException.CIRCULAR,
// c.l.getName(),
// "prerequisites of " + c.l.getName() + " contain a circular reference" );
// }
v.visit( c.l );
c.visited = true;
c.progress = false;
state.vmap.put( c.l.getName(), c.l );
stack.pop();
while (c.di.hasNext())
{
LinkableContext dc = resolve( (String) c.di.next(), state,
allowExternal,
exportIncludes,
includeInheritanceDependenciesOnly);
if ((dc == null) || dc.visited)
continue;
queue.add( dc );
}
}
}
}
static LinkableContext resolve( String name, LinkState state, boolean allowExternal, boolean exportIncludes,
boolean includeInheritianceDependenciesOnly) throws LinkerException
{
if (allowExternal && (state.extdefs != null) && state.extdefs.contains( name ))
{
state.unresolved.add( name );
return null;
}
if (! exportIncludes && (state.includes != null) && state.includes.contains( name ))
{
state.includes.remove(name);
}
LinkableContext lc = state.defs.get( name );
if (lc == null)
{
if (state.unresolved == null)
throw new LinkerException.UndefinedSymbolException( name );
else
state.unresolved.add( name );
}
else
{
if (lc.l.isNative())
{
state.unresolved.add( name ); // natives are always external
return null;
}
if (!allowExternal && state.extdefs.contains( name ))
{
state.extdefs.remove( name ); // not external anymore, we had to resolve it.
}
lc.activate(includeInheritianceDependenciesOnly);
}
return lc;
}
public static String dump( LinkState state )
{
StringBuilder buf = new StringBuilder( 2048 );
buf.append( "<report>\n" );
buf.append( " <scripts>\n" );
for (Iterator<Linkable> scripts = state.getVisitedLinkables().iterator(); scripts.hasNext();)
{
CULinkable l = (CULinkable) scripts.next();
buf.append( " <script name=\"")
.append(l.getName())
.append("\" mod=\"")
.append(l.getLastModified())
.append("\" size=\"")
.append(l.getSize())
// optimizedsize is often considerably smaller than size
.append("\" optimizedsize=\"")
.append(macromedia.abc.Optimizer.optimize(l.getUnit().bytes).size())
.append("\">\n");
for (Iterator defs = l.getDefinitions(); defs.hasNext();)
{
buf.append( " <def id=\"" + (String) defs.next() + "\" />\n" );
}
for (Iterator pre = l.getPrerequisites(); pre.hasNext();)
{
buf.append( " <pre id=\"" + (String) pre.next() + "\" />\n" );
}
for (Iterator dep = l.getDependencies(); dep.hasNext();)
{
buf.append( " <dep id=\"" + (String) dep.next() + "\" />\n" );
}
buf.append( " </script>\n" );
}
buf.append( " </scripts>\n" );
if ((state.getExternal() != null) || (state.getUnresolved() != null))
{
buf.append( " <external-defs>\n");
for (Iterator external = state.getExternal().iterator(); external.hasNext();)
{
String ext = (String) external.next();
if (!state.getUnresolved().contains( ext )) // only print exts we actually depended on
continue;
buf.append( " <ext id=\"" + ext + "\" />\n" );
}
for (Iterator<String> unresolved = state.getUnresolved().iterator(); unresolved.hasNext();)
{
String unr = unresolved.next();
if (state.getExternal().contains( unr ))
continue;
buf.append( " <missing id=\"" + unr + "\" />\n" );
}
buf.append( " </external-defs>\n");
}
buf.append( "</report>\n" );
return buf.toString();
}
static private class LinkableContext
{
public LinkableContext( Linkable l )
{
this.l = l;
}
public void activate(boolean includeInheritianceDependenciesOnly)
{
if (!active)
{
active = true;
pi = l.getPrerequisites();
if (!includeInheritianceDependenciesOnly)
di = l.getDependencies();
else
di = Collections.EMPTY_LIST.iterator();
}
}
public String toString()
{
return l.getName() + " " + (visited? "v":"") + (progress? "p":"");
}
public final Linkable l;
public Iterator pi;
public Iterator di;
public boolean active = false;
public boolean visited = false;
public boolean progress = false;
}
}