blob: c393502c436a1a8a37598db2456b244d2ac7778e [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.scopes;
import java.lang.ref.WeakReference;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.royale.compiler.common.DependencyType;
import org.apache.commons.io.FilenameUtils;
import org.apache.royale.compiler.common.IFileSpecificationGetter;
import org.apache.royale.compiler.constants.IASLanguageConstants;
import org.apache.royale.compiler.definitions.IDefinition;
import org.apache.royale.compiler.definitions.INamespaceDefinition;
import org.apache.royale.compiler.definitions.IPackageDefinition;
import org.apache.royale.compiler.internal.definitions.NamespaceDefinition;
import org.apache.royale.compiler.internal.definitions.ScopedDefinitionBase;
import org.apache.royale.compiler.internal.parsing.as.OffsetLookup;
import org.apache.royale.compiler.internal.projects.CompilerProject;
import org.apache.royale.compiler.internal.scopes.SWCFileScopeProvider.SWCFileScope;
import org.apache.royale.compiler.internal.tree.as.FileNode;
import org.apache.royale.compiler.internal.tree.as.FunctionNode;
import org.apache.royale.compiler.projects.ICompilerProject;
import org.apache.royale.compiler.scopes.IDefinitionSet;
import org.apache.royale.compiler.tree.as.IASNode;
import org.apache.royale.compiler.units.ICompilationUnit;
import org.apache.royale.compiler.workspaces.IWorkspace;
import org.apache.royale.compiler.scopes.IFileScope;
import org.apache.royale.utils.FastStack;
/**
* IASScope implementation for FileNodes. Conceptually, the file scope contains
* all top-level definitions in the file. Any top-level definitions that don't
* reside in packages are in the scope itself (see ASScope). This scope's
* implementations of findDefinitionByName, findAllDefinitionsByName, and
* findAllDefinitions also look for definitions in package scopes within the
* file. That way, we use the versions of these classes in the (possibly
* unsaved) file, rather than the versions that are stored in the project scope.
*/
public class ASFileScope extends ASScope implements IFileScope
{
private static Collection<String> IMPLICIT_IMPORTS_FOR_AS =
Collections.singleton(IASLanguageConstants.Vector_qname);
/**
* Returns the implicit imports for an ActionScript file.
* <p>
* For AS3, there is a single implicit import, <code>"__AS3__.vec.Vector"</code>.
*
* @return A collection of Strings for the implicit imports.
*/
public static Collection<String> getImplicitImportsForAS()
{
return IMPLICIT_IMPORTS_FOR_AS;
}
/**
* the path of the file this file scope represents
*/
protected String filePath;
/**
* A flag indicating whether this is an editable file (AS/MXML) or not
* (SWC).
*/
protected Boolean isEditableFile;
private final NamespaceDefinition.IFilePrivateNamespaceDefinition filePrivateNamespace;
private final IFileSpecificationGetter fileSpecGetter;
protected WeakReference<ICompilationUnit> compilationUnitReference;
/**
* The file scope needs to keep track or the parsed function bodies to pin the
* FileNode until those bodies are discarded again. If the file node is not pinned
* when function bodies are parsed, the FileNode can be gc'd, and reparsed, which
* will then reconnect the new FileNode to this FileScope. This leaves the scopes
* in a bad state, as the parse/discard function bodies modify the scope state
* Found in CMP-2238.
*/
private ConcurrentHashMap<FunctionNode, Object> parsedFunctionBodies;
/**
* Lookup filename and local offset from the absolute offset.
*/
private OffsetLookup offsetLookup;
/**
* Constructor
*
* @param fileSpecGetter {@link IFileSpecificationGetter} that is used to
* open files associated with this {@link ASFileScope}.
* @param filePath Normalized absolute name of the file for which this
* {@link ASFileScope} is being created.
*/
public ASFileScope(IFileSpecificationGetter fileSpecGetter, String filePath)
{
super(null);
this.filePath = filePath;
String uri = "FilePrivateNS";
filePrivateNamespace = NamespaceDefinition.createFilePrivateNamespaceDefinition(uri);
this.fileSpecGetter = fileSpecGetter;
this.compilationUnitReference = null;
addImplicitImportsForAS();
this.parsedFunctionBodies = new ConcurrentHashMap<FunctionNode, Object>();
}
/**
* Constructor
*
* @param fileNode file node whose scope this represents
*/
public ASFileScope(FileNode fileNode)
{
super(null, fileNode);
filePath = fileNode.getSourcePath();
final String uri = "FilePrivateNS:" + FilenameUtils.getBaseName(filePath);
filePrivateNamespace = NamespaceDefinition.createFilePrivateNamespaceDefinition(uri);
fileSpecGetter = fileNode.getFileSpecificaitonGetter();
this.compilationUnitReference = null;
addImplicitImportsForAS();
this.parsedFunctionBodies = new ConcurrentHashMap<FunctionNode, Object>();
}
/**
* Adds the appropriate implicit imports for ActionScript.
*/
private void addImplicitImportsForAS()
{
for (String implicitImport : ASFileScope.getImplicitImportsForAS())
{
addImport(implicitImport);
}
}
/**
* Determine whether or not this is an editable file. This will check the
* attached FileNode to see if it represents a SWC file (as opposed to an AS
* or MXML file). If so, then we know not to prefer in-file definitions to
* those in the project scope.
*
* @return true if the file is editable
*/
protected boolean isEditableFile()
{
if (isEditableFile == null)
{
boolean isEditable = true;
if (filePath != null &&
filePath.endsWith(".swc"))
isEditable = false;
isEditableFile = isEditable;
}
return isEditableFile.booleanValue();
}
/**
* For debugging only.
*/
@Override
protected String toStringHeader()
{
StringBuilder sb = new StringBuilder();
sb.append(super.toStringHeader());
if (filePath != null)
{
sb.append(" for \"");
sb.append(filePath);
sb.append("\"");
}
return sb.toString();
}
/**
* Determine if p is the name of a package - at File Scope it checks
* declared package names in addition to package names introduced via import
*
* @param p the string to test
* @return true it p is a known package name
*/
@Override
public boolean isPackageName(String p)
{
// Check the imports
if (super.isPackageName(p))
return true;
// Just get the def set and iterate over the result looking for a package.
// Common cases are the string isn't found, or the def set will contain
// 1 definition so iterating over it isn't a problem.
IDefinitionSet defSet = getLocalDefinitionSetByName(p);
if (defSet != null)
{
int n = defSet.getSize();
for (int i = 0; i < n; i++)
{
IDefinition def = defSet.getDefinition(i);
if (def instanceof IPackageDefinition)
{
IPackageDefinition packageDef = (IPackageDefinition)def;
if (p.equals(packageDef.getBaseName()))
return true;
}
}
}
return false;
}
@Override
protected boolean namespaceSetSameAsContainingScopeNamespaceSet()
{
return false;
}
public NamespaceDefinition.ILanguageNamespaceDefinition getFilePrivateNamespaceReference()
{
return filePrivateNamespace;
}
@Override
public IWorkspace getWorkspace()
{
return fileSpecGetter.getWorkspace();
}
/**
* Resolve the source file path of the given definition QName. The QName
* must be defined within this file scope.
* <p>
* If this {@code ASFileScope} comes from an ActionScript file, return the
* ActionScript file path. File scopes coming from ABC or SWC files will
* override this method to locate their source file path.
*
* @param qName QName of a definition
* @param project resolve the source path within this project
* @return Source file path of this {@code ASFileScope}.
*/
@Override
public String getContainingSourcePath(final String qName, final ICompilerProject project)
{
return filePath;
}
@Override
public String getContainingPath()
{
return filePath;
}
@Override
public void collectExternallyVisibleDefinitions(Collection<IDefinition> defs, boolean includePrivateDefinitions)
{
FastStack<ASScope> scopesToVisit = new FastStack<ASScope>();
scopesToVisit.push(this);
while (scopesToVisit.size() > 0)
{
ASScope currentScope = scopesToVisit.pop();
for (IDefinitionSet defSet : currentScope.getAllLocalDefinitionSets())
{
int n = defSet.getSize();
for (int i = 0; i < n; i++)
{
IDefinition def = defSet.getDefinition(i);
if (def instanceof IPackageDefinition)
{
IPackageDefinition packageDef = (IPackageDefinition)def;
ASScope packageScope = (ASScope)packageDef.getContainedScope();
scopesToVisit.push(packageScope);
}
else if ((def.getContainingScope() == currentScope) &&
(includePrivateDefinitions || def.getNamespaceReference().isPublicOrInternalNamespace()))
{
defs.add(def);
}
}
}
}
}
@Override
public void addImplicitOpenNamespaces(CompilerProject compilerProject, Set<INamespaceDefinition> result)
{
result.add(getFilePrivateNamespaceReference());
compilerProject.addGlobalUsedNamespacesToNamespaceSet(result);
}
/**
* Re-parse the file this scope was built for.
*
* @return The root {@link IASNode} of the syntax tree.
*/
public IASNode reparseFile() throws InterruptedException
{
return getCompilationUnit().getSyntaxTreeRequest().get().getAST();
}
/**
* Set the {@code OffsetLookup} utility.
*
* @param offsetLookup offset lookup utility
*/
public void setOffsetLookup(OffsetLookup offsetLookup)
{
assert offsetLookup != null : "Offset lookup can't be null";
this.offsetLookup = offsetLookup;
}
/**
* @return Offset lookup utility for this file scope.
*/
public OffsetLookup getOffsetLookup()
{
return offsetLookup;
}
@Override
public final ScopedDefinitionBase getContainingDefinition()
{
return null;
}
@Override
public boolean isSWC()
{
return false;
}
/**
* Get the IASNode this FileScope came from. This will reparse the file if necessary
* to rebuild the IASNode.
* If this FileScope did not come from a source file, this will return null.
* @return The IASNode that produced this FileScope, or null if this Scope did not come from
* a source file
*/
public IASNode getNode()
{
IASNode node = null;
try
{
node = reparseFile();
}
catch (InterruptedException ie)
{
node = null;
}
return node;
}
/**
* Set the {@link ICompilationUnit} related to this {@link ASFileScope}.
* This class is overridden by SWCFileScope which will never set the
* project reference, as SWC scopes are shared across projects.
*
* @param compilationUnit A compilation unit.
* @return true if the {@link ICompilationUnit} was stored, false if not
* stored. This happens when trying to store a SWCCompilationUnit.
*/
public boolean setCompilationUnit(ICompilationUnit compilationUnit)
{
if (compilationUnit == null)
{
compilationUnitReference = null;
return true;
}
compilationUnitReference = new WeakReference<ICompilationUnit>(compilationUnit);
return true;
}
/**
* Get the {@link ICompilationUnit} related to this {@link ASFileScope}.
* SWCFileScope will override this class.
*
* Note that this class should only ever be called from ASProjectScope or CompilerProject.
* Clients should only ever use ASProjectScope.getCompilationUnitForScope()
*
* @return the {@link ICompilationUnit}. May be null when a {@link SWCFileScope}
*/
public ICompilationUnit getCompilationUnit()
{
if (compilationUnitReference == null)
return null;
return compilationUnitReference.get();
}
/**
* Add a function node the the collection of function nodes which
* have been parsed
*
* @param functionNode the FunctionNode to add
*/
public void addParsedFunctionBodies(FunctionNode functionNode)
{
parsedFunctionBodies.put(functionNode, Object.class);
}
/**
* Remove a function node the the collection of function nodes which
* have been parsed, when the body is discarded
*
* @param functionNode the FunctionNode to add
*/
public void removeParsedFunctionBodies(FunctionNode functionNode)
{
parsedFunctionBodies.remove(functionNode);
}
public void addDependencyOnBuiltinType(ICompilerProject project, IASLanguageConstants.BuiltinType builtinType,
DependencyType dependencyType)
{
ASScopeCache cache = ((CompilerProject)project).getCacheForScope(this);
cache.addDependencyOnBuiltinType(builtinType, dependencyType);
}
}