blob: d6e08a89b36d70c2348e85363d6a2bd2afb42c77 [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.CompilationUnit;
import flex2.compiler.Source;
import flex2.compiler.common.FramesConfiguration;
import flex2.compiler.common.FramesConfiguration.FrameInfo;
import flex2.compiler.swc.SwcLibrary;
import flex2.compiler.swc.SwcScript;
import flex2.compiler.util.*;
import flex2.compiler.util.graph.Visitor;
import flash.swf.Frame;
import flash.swf.tags.FrameLabel;
import java.util.*;
/**
* Extends SimpleMovie by adding support for multiple frames and
* keeping AS3 metadata.
*/
public class FlexMovie extends SimpleMovie
{
private List<FrameInfo> frameInfoList;
private List<FrameInfo> configFrameInfoList;
private String mainDef;
private Set<String> externs;
private Set<String> includes;
private Set<String> unresolved;
private SortedSet<String> resourceBundles;
/**
* List of metadata names that are the unions of the library's metadata names
* that have script linked into this movie (both internal and external references).
*/
private Set<String> metadata;
public FlexMovie( LinkerConfiguration linkerConfiguration )
{
super( linkerConfiguration );
mainDef = linkerConfiguration.getMainDefinition();
// C: FlexMovie should keep its own copy of externs, includes, unresolved and resourceBundles
// so that incremental compilation can do the single-compile-multiple-link scenario.
externs = new HashSet<String>(linkerConfiguration.getExterns());
includes = new LinkedHashSet<String>(linkerConfiguration.getIncludes());
unresolved = new HashSet<String>(linkerConfiguration.getUnresolved());
generateLinkReport = linkerConfiguration.generateLinkReport();
generateRBList = linkerConfiguration.generateRBList();
resourceBundles = new TreeSet<String>(linkerConfiguration.getResourceBundles());
frameInfoList = new LinkedList<FrameInfo>();
configFrameInfoList = new LinkedList<FrameInfo>();
configFrameInfoList.addAll( linkerConfiguration.getFrameList() );
metadata = new HashSet<String>();
}
private void prelink( List<CompilationUnit> units ) throws LinkerException
{
// Starting at the main definition, build the list of frames and frame classes.
// No new classes can be discovered here, we're just building the frame class list.
Map<String, CompilationUnit> def2unit = new HashMap<String, CompilationUnit>();
for (Iterator<CompilationUnit> it = units.iterator(); it.hasNext(); )
{
CompilationUnit unit = it.next();
mapAll( def2unit, unit.topLevelDefinitions.getStringSet(), unit );
}
buildFrames( def2unit, mainDef, new HashSet<String>() );
frameInfoList.addAll( configFrameInfoList );
if (frameInfoList.size() > 0)
{
topLevelClass = formatSymbolClassName( frameInfoList.get( 0 ).frameClasses.get( 0 ) );
}
}
private boolean hasFrameClass( String queryClassName )
{
// This is horribly inefficient, but the inner loop will only get called a few times
// for a typical Flex movie.
for (Iterator<FrameInfo> fit = frameInfoList.iterator(); fit.hasNext();)
{
FramesConfiguration.FrameInfo frameInfo = fit.next();
for (Iterator<String> cit = frameInfo.frameClasses.iterator(); cit.hasNext(); )
{
String className = cit.next();
if (className.equals( queryClassName ))
return true;
}
}
return false;
}
private void buildFrames( Map<String, CompilationUnit> def2unit, String className, Set<String> progress ) throws LinkerException
{
if (hasFrameClass( className ))
return;
if (progress.contains( className ))
return;
progress.add( className );
CompilationUnit unit = def2unit.get( className );
if (unit == null) // this should get picked up elsewhere
throw new LinkerException.UndefinedSymbolException( className ); // fixme - add special frame class error?
if (unit.loaderClass != null)
{
buildFrames( def2unit, unit.loaderClass, progress );
}
FramesConfiguration.FrameInfo info = new FramesConfiguration.FrameInfo();
info.label = className.replaceAll( "[^A-Za-z0-9]", "_" );
info.frameClasses.add( className );
info.frameClasses.addAll( unit.resourceBundles );
info.frameClasses.addAll( unit.extraClasses );
frameInfoList.add( info );
}
// shouldn't need swcContext at this point - units should have all referenced defs by now.
public void generate(List<CompilationUnit> units) throws LinkerException
{
try
{
prelink( units );
}
catch (LinkerException e)
{
// You can't actually throw a LinkerException from generate,
// because an assert fires downstream that expects errorcount > 0!
// So, we have to warn here and then rethrow.
ThreadLocalToolkit.log( e );
throw e;
}
List<CULinkable> linkables = new LinkedList<CULinkable>();
// TODO remove - see note below
String serverConfigDef = null;
CULinkable mainLinkable = null;
for (Iterator<CompilationUnit> it = units.iterator(); it.hasNext();)
{
CompilationUnit unit = it.next();
// NOTE Here we watch for specific generated loose code units we have carnal knowledge of, and add their
// definitions as deps to the main unit.
// TODO Remove once serverconfigdata is handled within the standard bootstrap setup.
//
Source source = unit.getSource();
String sourceName = source.getName();
if (sourceName.equals("serverConfigData.as"))
{
serverConfigDef = unit.topLevelDefinitions.first().toString();
}
CULinkable linkable = new CULinkable( unit );
if (unit.isRoot())
mainLinkable = linkable;
if (source.isInternal())
{
externs.addAll( unit.topLevelDefinitions.getStringSet() );
}
linkables.add( linkable );
}
frames = new ArrayList<Frame>();
// FIXME - hook serverconfigdata to FlexInit mixin
if (mainLinkable != null)
{
if (serverConfigDef != null)
mainLinkable.addDep(serverConfigDef);
}
try
{
final Set<SwcLibrary> librariesProcessed = new HashSet<SwcLibrary>();
int counter = 0;
DependencyWalker.LinkState state = new DependencyWalker.LinkState( linkables, externs, includes, unresolved );
for (Iterator<FrameInfo> it = frameInfoList.iterator(); it.hasNext();)
{
FramesConfiguration.FrameInfo frameInfo = it.next();
final Frame f = new Frame();
f.pos = ++counter;
if (frameInfo.label != null)
{
f.label = new FrameLabel();
f.label.label = frameInfo.label;
}
// note that we only allow externs on the last frame
DependencyWalker.traverse( frameInfo.frameClasses, state, !it.hasNext(), !it.hasNext(),
getInheritanceDependenciesOnly(),
new Visitor<Linkable>()
{
public void visit( Linkable o )
{
// FIXME - keep an eye on those lazy abcs... do we have loose script?
// TODO yep! delete "false &&" once loose-script bootstrapping code has been eliminated - see note above
CULinkable l = (CULinkable) o;
// exportUnitOnFrame( l.getUnit(), f, false);// && !l.hasDefinition( frameClass ) );
exportUnitOnFrame( l.getUnit(), f, lazyInit);
// for any scripts that we include from libraries, add the libraries keep-as3-metadata
// to the list of metadata we will preserve in postlink.
Source source = l.getUnit().getSource();
if (source.isSwcScriptOwner() && !source.isInternal())
{
SwcScript script = (SwcScript)source.getOwner();
SwcLibrary library = script.getLibrary();
// lots of scripts, but not many swcs, so avoid adding the same metadata
// over and over.
if (!librariesProcessed.contains(library))
{
librariesProcessed.add(library);
metadata.addAll(library.getMetadata());
}
}
}
});
frames.add( f );
}
if (generateLinkReport)
{
linkReport = DependencyWalker.dump( state );
}
if (generateRBList)
{
rbList = dumpRBList(resourceBundles);
}
if (unresolved.size() != 0)
{
boolean fatal = false;
for (Iterator<String> it = unresolved.iterator(); it.hasNext();)
{
String u = it.next();
if (!externs.contains( u ))
{
ThreadLocalToolkit.log( new LinkerException.UndefinedSymbolException( u ) );
fatal = true;
}
}
if (fatal)
{
throw new LinkerException.LinkingFailed();
}
}
}
catch (LinkerException e)
{
ThreadLocalToolkit.log( e );
throw e;
}
}
public static String dumpRBList(Set<String> bundles)
{
StringBuilder b = new StringBuilder();
b.append("bundles = ");
for (Iterator<String> iterator = bundles.iterator(); iterator.hasNext();)
{
String str = iterator.next();
b.append(str + " ");
}
return b.toString();
}
private static void mapAll( Map<String, CompilationUnit> map, Set keys, CompilationUnit val )
{
for (Iterator it = keys.iterator(); it.hasNext();)
{
String defname = (String) it.next();
// defname = defname.replace( ':', '.' ); // FIXME - which is the canonical form?
map.put( defname, val );
}
}
/**
* Get the set of metadata names that should be preserved when optimizing this movie.
*
* @return Set of metadata names to keep in the movie.
*/
public Set<String> getMetadata()
{
return metadata;
}
// todo - move/refactor, this is temporary 'til linkable/script stuff gets hoisted out of Compunit
}