/*
*
* 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.tree.as;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.apache.flex.compiler.common.IEmbedResolver;
import org.apache.flex.compiler.common.IFileSpecificationGetter;
import org.apache.flex.compiler.common.RecursionGuard;
import org.apache.flex.compiler.constants.INamespaceConstants;
import org.apache.flex.compiler.definitions.IDefinition;
import org.apache.flex.compiler.filespecs.IFileSpecification;
import org.apache.flex.compiler.internal.parsing.as.IncludeHandler;
import org.apache.flex.compiler.internal.parsing.as.OffsetLookup;
import org.apache.flex.compiler.internal.scopes.ASFileScope;
import org.apache.flex.compiler.internal.scopes.ASScope;
import org.apache.flex.compiler.internal.semantics.PostProcessStep;
import org.apache.flex.compiler.internal.targets.ITargetAttributes;
import org.apache.flex.compiler.internal.targets.TargetAttributesMetadata;
import org.apache.flex.compiler.problems.ICompilerProblem;
import org.apache.flex.compiler.projects.ICompilerProject;
import org.apache.flex.compiler.scopes.IASScope;
import org.apache.flex.compiler.tree.ASTNodeID;
import org.apache.flex.compiler.tree.as.IASNode;
import org.apache.flex.compiler.tree.as.IClassNode;
import org.apache.flex.compiler.tree.as.IDefinitionNode;
import org.apache.flex.compiler.tree.as.IFileNode;
import org.apache.flex.compiler.tree.as.IFileNodeAccumulator;
import org.apache.flex.compiler.tree.as.IImportNode;
import org.apache.flex.compiler.tree.as.IPackageNode;
import org.apache.flex.compiler.tree.as.IScopedNode;
import org.apache.flex.compiler.tree.metadata.IMetaTagNode;
import org.apache.flex.compiler.workspaces.IWorkspace;
import org.apache.flex.utils.FilenameNormalization;
/**
* ActionScript parse tree node representing a file
*/
public class FileNode extends ScopedBlockNode implements Serializable, IFileNode, IFileNodeAccumulator
{
private static final long serialVersionUID = 1L;
/**
* Private constructor.
*
* @param fileSpecGetter The {@link IFileSpecificationGetter} to be
* used to open included files while parsing the file for this file node.
* @param fileSpec The {@link IFileSpecification} of the file
* for this file node.
*/
private FileNode(IFileSpecificationGetter fileSpecGetter, IFileSpecification fileSpec)
{
super();
this.fileSpecGetter = fileSpecGetter;
this.fileSpec = fileSpec;
setSourcePath(fileSpec.getPath());
includeHandler = new IncludeHandler(fileSpecGetter);
embedNodes = new LinkedList<IEmbedResolver>();
requiredResourceBundles = new HashSet<String>();
deferredFunctionNodes = new HashSet<FunctionNode>();
// Create the implicit import nodes.
// (Currently there is only one, for __AS3__.vec.Vector.)
// These are not considered children of this file node,
// but they have this file node as their parent.
implicitImportNodes = new LinkedList<IImportNode>();
for (String implicitImport : ASFileScope.getImplicitImportsForAS())
{
ImportNode implicitImportNode = ImportNode.buildImportNode(implicitImport);
implicitImportNode.setParent(this);
implicitImportNodes.add(implicitImportNode);
}
// Initialize the list of all import nodes to the implicit import nodes.
// The parser will add the explicit import nodes within the file.
importNodes = new LinkedList<IImportNode>();
for (IImportNode implicitImportNode : implicitImportNodes)
{
importNodes.add(implicitImportNode);
}
}
/**
* Constructor for a {@link FileNode} that has a source file
* associated with it.
*
* @param fileSpecGetter The {@link IFileSpecificationGetter} to be
* used to open included files while parsing the file for this file node.
* @param pathName The path name of the file for this file node.
*/
public FileNode(IFileSpecificationGetter fileSpecGetter, String pathName)
{
this(fileSpecGetter, fileSpecGetter.getFileSpecification(pathName));
}
/**
* Constructor for a {@link FileNode} that does not have a source file
* associated with it.
*
* @param fileSpecGetter The {@link IFileSpecificationGetter} to be
* used to open included files while parsing this file node.
*/
public FileNode(IFileSpecificationGetter fileSpecGetter)
{
this(fileSpecGetter, FilenameNormalization.normalize(""));
}
private IFileSpecification fileSpec;
private final IFileSpecificationGetter fileSpecGetter;
private Collection<ICompilerProblem> problems;
private final IncludeHandler includeHandler;
/**
* {@code StreamingASTokenizer} and {@code IncludeHandler} generates data
* needed to do offset lookup. The data is encapsulated in an
* {@code OffsetLookup} object. It is parked on this {@code FileNode} so
* that it can be later passed into the {@code ASFileScope}.
*/
private OffsetLookup offsetLookup;
/**
* A list of the implicit imports for each ActionScript file.
* Currently there is only one, for the __AS3__vec.Vector class.
*/
private final List<IImportNode> implicitImportNodes;
/**
* A list of every import node (both implicit and explicit) within this file.
* It is initialized to the one implicit import node for __AS3__.vec.Vector.
* Then as importDirective() in ASParser builds each import node,
* it adds it to this list.
*/
private final List<IImportNode> importNodes;
private final List<IEmbedResolver> embedNodes;
private ITargetAttributes targetAttributes;
private final Set<String> requiredResourceBundles;
/**
* A convenient collection of all {@code FunctionNode}s whose body nodes
* are deferred.
*/
private final Set<FunctionNode> deferredFunctionNodes;
//
// NodeBase overrides
//
@Override
public ASTNodeID getNodeID()
{
return ASTNodeID.FileID;
}
@Override
public IFileSpecification getFileSpecification()
{
return fileSpec;
}
@Override
public IWorkspace getWorkspace()
{
return fileSpecGetter.getWorkspace();
}
@Override
public void setParent(NodeBase parent)
{
super.setParent(parent);
if (parent != null)
connectedToProjectScope();
}
@Override
protected void analyze(EnumSet<PostProcessStep> set, ASScope scope, Collection<ICompilerProblem> problems)
{
if (set.contains(PostProcessStep.POPULATE_SCOPE))
{
assert scope == null;
// A FileNode creates a parentless ASFileScope,
// which then gets passed down as the current scope.
initializeScope(scope);
scope = getASScope();
}
if (set.contains(PostProcessStep.RECONNECT_DEFINITIONS))
{
assert scope != null;
assert scope instanceof ASFileScope;
reconnectScope(scope);
}
super.analyze(set, scope, problems);
}
@Override
public void collectImportNodes(Collection<IImportNode> importNodes)
{
// Collect the explicit import nodes.
super.collectImportNodes(importNodes);
collectImplicitImportNodes(importNodes);
}
/**
* Collect only the nodes for the implicit Imports.
* @param importNodes The Collection to add the implicit import nodes to
*/
void collectImplicitImportNodes(Collection<IImportNode> importNodes)
{
// Add the implicit import nodes.
importNodes.addAll(implicitImportNodes);
}
//
// IFileNode implementations
//
@Override
public OffsetLookup getOffsetLookup()
{
return this.offsetLookup;
}
@Override
public boolean hasIncludes()
{
return getFileScope().getOffsetLookup().hasIncludes();
}
@Override
public long getIncludeTreeLastModified()
{
return getIncludeHandler().getLastModified();
}
@Override
public IDefinitionNode[] getTopLevelDefinitionNodes(boolean includeDefinitionsOutsideOfPackage,
boolean includeNonPublicDefinitions)
{
List<IDefinitionNode> list = new ArrayList<IDefinitionNode>();
// Check children of this FileNode that might be definition nodes.
int n = getChildCount();
for (int i = 0; i < n; i++)
{
IASNode child = getChild(i);
if (child instanceof IPackageNode)
{
IScopedNode packageBodyNode = ((IPackageNode)child).getScopedNode();
// Check children of the package body node that might be definition nodes.
int m = packageBodyNode.getChildCount();
for (int j = 0; j < m; j++)
{
IASNode packageBodyChild = packageBodyNode.getChild(j);
addIfDefinitionNode(packageBodyChild, includeNonPublicDefinitions, list);
}
}
else if (includeDefinitionsOutsideOfPackage)
{
addIfDefinitionNode(child, includeNonPublicDefinitions, list);
}
}
return list.toArray(new IDefinitionNode[0]);
}
@Override
public IDefinition[] getTopLevelDefinitions(boolean includeDefinitionsOutsideOfPackage,
boolean includeNonPublicDefinitions)
{
IDefinitionNode[] nodes = getTopLevelDefinitionNodes(
includeDefinitionsOutsideOfPackage, includeNonPublicDefinitions);
int n = nodes.length;
IDefinition[] definitions = new IDefinition[n];
for (int i = 0; i < n; i++)
{
definitions[i] = nodes[i].getDefinition();
}
return definitions;
}
@Override
public ITargetAttributes getTargetAttributes(ICompilerProject project)
{
if (targetAttributes == null)
{
final IDefinitionNode[] definitionNodes = getTopLevelDefinitionNodes(false, false);
if (definitionNodes.length == 1 && definitionNodes[0] instanceof IClassNode)
{
final IClassNode classNode = (IClassNode)definitionNodes[0];
final IMetaTagNode[] metaTagSWF = classNode.getMetaTagNodesByName("SWF");
if (metaTagSWF != null && metaTagSWF.length > 0)
{
if (metaTagSWF.length > 1)
throw new IllegalStateException("Only allow one [SWF] metadata tag.");
targetAttributes = new TargetAttributesMetadata(metaTagSWF[0]);
}
}
}
return targetAttributes;
}
@Override
public Collection<ICompilerProblem> getProblems()
{
return problems;
}
//
// IFileNodeAccumulator implementations
//
@Override
public void addImportNode(IImportNode node)
{
// don't add imports inside a function body to the list of import
// nodes, as the import node list shouldn't be modified once the
// FileNode has been created, but deferred function body parsing
// happens later, so skip this case.
if (node.getAncestorOfType(FunctionNode.class) != null)
return;
importNodes.add(node);
}
@Override
public List<IImportNode> getImportNodes()
{
return importNodes;
}
@Override
public void addEmbedNode(IEmbedResolver node)
{
embedNodes.add(node);
}
@Override
public List<IEmbedResolver> getEmbedNodes()
{
return embedNodes;
}
@Override
public void addRequiredResourceBundle(String bundleName)
{
requiredResourceBundles.add(bundleName);
}
@Override
public Set<String> getRequiredResourceBundles()
{
return requiredResourceBundles;
}
@Override
public void addDeferredFunctionNode(FunctionNode functionNode)
{
assert functionNode != null : "Function node can't be null.";
this.deferredFunctionNodes.add(functionNode);
}
@Override
public synchronized void populateFunctionNodes()
{
for (final FunctionNode fn : deferredFunctionNodes)
{
fn.parseFunctionBody(problems);
}
}
/**
* Run through all the deferredFunctionNodes, and if their containing
* scope isn't file, package or class, it needs to be parsed
*/
public synchronized void parseRequiredFunctionBodies()
{
Iterator<FunctionNode> iter = deferredFunctionNodes.iterator();
while (iter.hasNext())
{
FunctionNode fn = iter.next();
// don't parse, but do remove from the deferred list functions
// which are inside a disabled config block, as they can't be
// parsed properly later, and they are never needed.
if (isInsideDisabledConfigBlock(fn))
{
iter.remove();
continue;
}
IASNode functionParent = fn.getParent();
if (functionParent == null || functionParent instanceof FileNode)
continue;
if (functionParent instanceof ScopedBlockNode || functionParent instanceof ContainerNode)
functionParent = functionParent.getParent();
// This shouldn't really ever be null, but for some reason it
// is, so guard against it for now.
if (functionParent == null)
continue;
switch (functionParent.getNodeID())
{
case ClassID:
case PackageID:
break;
default:
fn.parseFunctionBody(problems);
iter.remove();
}
}
}
private static boolean isInsideDisabledConfigBlock(FunctionNode fn)
{
ConfigConditionBlockNode configBlock = (ConfigConditionBlockNode)fn.getAncestorOfType(ConfigConditionBlockNode.class);
if (configBlock != null && configBlock.getChildCount() == 0)
return true;
return false;
}
//
// Other methods
//
/**
* Gets the {@link IFileSpecificationGetter} for that was used to open
* included files when this {@link FileNode} was parsed.
*
* @return The {@link IFileSpecificationGetter} for that was used to open
* included files when this {@link FileNode} was parsed.
*/
// TODO Fix type in name
public IFileSpecificationGetter getFileSpecificaitonGetter()
{
return fileSpecGetter;
}
public void setOffsetLookup(final OffsetLookup offsetLookup)
{
assert offsetLookup != null : "OffsetLookup can't be null.";
this.offsetLookup = offsetLookup;
}
/**
* Gets the {@link IncludeHandler} for this file node. The
* {@link IncludeHandler} can be used to get a list of files included by
* this file.
*
* @return The {@link IncludeHandler} for this file node.
*/
public IncludeHandler getIncludeHandler()
{
return this.includeHandler;
}
/**
* Retrieve the temporary enclosing scope (which is used to make an included
* file look like it's inside the scope where the include statement lives.
* See Project.connectToIncluder for details.
*
* @return the temporary enclosing scope
*/
// TODO Does this need 'synchronized'?
public synchronized IASScope getTemporaryEnclosingScope(RecursionGuard scopeGuard)
{
// Leaving this method here until we deal with includes properly.
// If we find the old connected scopes thing will work for real, then
// we can use version control to bring back all the code that was here.
// In the mean time nobody has a "connected" scope and thus
// nobody will have a temporary enclosing scope.
return null;
}
public void processAST(EnumSet<PostProcessStep> features)
{
//START DEBUG HERE FOR SCOPE POPULATION
Collection<ICompilerProblem> problems = runPostProcess(features);
if (problems != null)
{
if (this.problems == null)
this.problems = new ArrayList<ICompilerProblem>();
this.problems.addAll(problems);
}
}
public void reconnectDefinitions(ASFileScope fileScope)
{
this.scope = fileScope;
Collection<ICompilerProblem> problems = new ArrayList<ICompilerProblem>();
analyze(EnumSet.of(PostProcessStep.RECONNECT_DEFINITIONS), scope, problems);
}
protected void initializeScope(ASScope containingScope)
{
//containing scope will always be null here
final ASFileScope fileScope = new ASFileScope(this);
if (offsetLookup != null)
fileScope.setOffsetLookup(offsetLookup);
scope = fileScope;
}
public void setProblems(Collection<ICompilerProblem> problems)
{
if (this.problems != null)
this.problems.addAll(problems);
else
this.problems = problems;
}
public void addProblem(ICompilerProblem problem)
{
if (problems == null)
problems = new ArrayList<ICompilerProblem>();
problems.add(problem);
}
/*
* Helper method for geAllToplevelDefinitionNodes()
*/
private void addIfDefinitionNode(IASNode node, boolean includeNonPublicDefinitions,
List<IDefinitionNode> list)
{
if (!(node instanceof IDefinitionNode))
return;
IDefinitionNode definitionNode = (IDefinitionNode)node;
if (!includeNonPublicDefinitions)
{
String namespace = definitionNode.getNamespace();
if (!INamespaceConstants.public_.equals(namespace))
return;
}
list.add(definitionNode);
}
}