blob: 52dadefccff233a44b72816c2e6be1ae6562561d [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.flex.compiler.internal.legacy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.flex.compiler.common.ASModifier;
import org.apache.flex.compiler.common.Multiname;
import org.apache.flex.compiler.definitions.IDefinition;
import org.apache.flex.compiler.definitions.INamespaceDefinition;
import org.apache.flex.compiler.internal.legacy.ASDefinitionFilter.RequireImportsValue;
import org.apache.flex.compiler.internal.projects.CompilerProject;
import org.apache.flex.compiler.internal.scopes.ASProjectScope;
import org.apache.flex.compiler.internal.scopes.ASScope;
import org.apache.flex.compiler.internal.scopes.ASScopeBase;
import org.apache.flex.compiler.internal.scopes.ASScopeBase.FilteredCollection;
import org.apache.flex.compiler.internal.workspaces.Workspace;
import org.apache.flex.compiler.projects.ICompilerProject;
import org.apache.flex.compiler.scopes.IASScope;
import com.google.common.base.Predicate;
/**
* This class contains static methods that used to be instance methods of an
* {@link IASScope}. They now take a new first parameter which is the old
* <code>this</code> scope. The methods are no longer part of
* <code>IASScope</code> because {@link ASDefinitionFilter} has been removed
* from the compiler.
*/
public class ASScopeUtils
{
/**
* Finds all definitions in the specified scope (and possibly also its
* enclosing and inherited scopes, depending on the filter) that meet the
* filter criteria.
*
* @param thisScope The {@link IASScope} in which the search starts.
* @param project The {@link ICompilerProject} to use for resolving
* references.
* @param filter The {@Link ASDefinitionFilter} that determines the
* scope chain and the criteria that the definitions must meet.
* @param definitions The list to which the {@link IDefinition} objects are
* added.
*/
public static void findAllDefinitions(IASScope thisScope, ICompilerProject project,
ASDefinitionFilter filter,
List<IDefinition> definitions)
{
CompilerProject compilerProject = (CompilerProject)project;
// Project scopes are a special case.
if (thisScope instanceof ASProjectScope)
{
Set<INamespaceDefinition> namespaceSet = crackFilter(compilerProject, null, filter);
Predicate<IDefinition> filterPredicate = filter.computePredicate(compilerProject, null);
Collection<IDefinition> foundDefinitions = new FilteredCollection<IDefinition>(filterPredicate, definitions);
((ASProjectScope)thisScope).getAllProperties(compilerProject, foundDefinitions, namespaceSet);
return;
}
// All other scopes use this logic.
else
{
ASScope scope = (ASScope)thisScope;
Set<INamespaceDefinition> namespaceSet = crackFilter(project, scope, filter);
Predicate<IDefinition> filterPredicate = filter.computePredicate(project, scope);
Collection<IDefinition> foundDefinitions = new FilteredCollection<IDefinition>(filterPredicate, definitions);
if (filter.searchInheritedScopes() && filter.searchContainingScope())
{
// The filter wants to look at inherited scopes and containing scopes.
// This must be a findprop variant of find all definitions.
ASScope currentScope = scope;
while (currentScope != null)
{
currentScope.getAllPropertiesForScopeChain(compilerProject, foundDefinitions, namespaceSet);
currentScope = currentScope.getContainingScope();
}
if (filter.needsDifferentProjectPredicate())
foundDefinitions = adjustPredicateForProject(scope, project, filter, definitions);
// Check the project scope.
ASProjectScope projectScope = compilerProject.getScope();
projectScope.getAllProperties(compilerProject, foundDefinitions, namespaceSet);
}
else if (filter.searchInheritedScopes())
{
// The filter wants to look at inherited scopes but not at containing scopes.
if (!(filter.requiresModifier(ASModifier.STATIC) || filter.excludesModifier(ASModifier.STATIC)))
{
// Find both static and instance properties.
// This is what CodeModel does to try and hint some stuff inside a class.
// It's basically a lexical lookup that stops at the file scope,
// so we use the scope chain version of lookup.
scope.getAllPropertiesForScopeChain(compilerProject, foundDefinitions, namespaceSet);
}
else
{
// This must be a getprop variant of find all definitions.
scope.getAllPropertiesForMemberAccess(compilerProject, foundDefinitions, namespaceSet);
}
}
else if (filter.searchContainingScope())
{
// The filter wants to look at lexical scopes, but not at the inherited scopes.
// This type of lookup has no meaning in AS3 but CodeModel uses it.
boolean requireStatic = filter.requiresModifier(ASModifier.STATIC);
boolean excludeStatic = filter.excludesModifier(ASModifier.STATIC);
if (!requireStatic || !excludeStatic)
{
ASScope currentScope = scope;
while (currentScope != null)
{
currentScope.getAllLocalProperties(compilerProject, foundDefinitions, namespaceSet, null);
currentScope = currentScope.getContainingScope();
}
}
if (filter.needsDifferentProjectPredicate())
foundDefinitions = adjustPredicateForProject(scope, project, filter, definitions);
// Check the project scope.
ASProjectScope projectScope = compilerProject.getScope();
projectScope.getAllProperties(compilerProject, foundDefinitions, namespaceSet);
}
else
{
// The filter wants to look only at the specified scope
// and not any containing or inherited scopes.
scope.getAllLocalProperties(compilerProject, foundDefinitions, namespaceSet, null);
}
}
}
/**
* Finds the definitions in the specified scope (and possibly also its
* enclosing and inherited scopes, depending on the filter) that have the
* specified base name and that meet the filter criteria.
*
* @param thisScope The {@link IASScope} in which the search starts.
* @param project The {@link ICompilerProject} to use for resolving
* references.
* @param name The base name of the definitions to be found.
* @param filter The {@Link ASDefinitionFilter} that determines the
* scope chain and the criteria that the definitions must meet.
* @param definitions The list to which the {@link IDefinition} objects are
* added.
*/
public static void findAllDefinitionsByName(IASScope thisScope, ICompilerProject project,
String name, ASDefinitionFilter filter,
List<IDefinition> definitions)
{
findDefinitionsByNameImpl(thisScope, project, name, filter, definitions, true);
}
/**
* Finds the first definition in the specified scope (and possibly also its
* enclosing and inherited scopes, depending on the filter) that has the
* specified base name and that meets the filter criteria.
*
* @param thisScope The {@link IASScope} in which the search starts.
* @param project The {@link ICompilerProject} to use for resolving
* references.
* @param name The base name of the definition to be found.
* @param filter The {@Link ASDefinitionFilter} that determines the
* scope chain and the criteria that the definition must meet.
* @return definitions The {@link IDefinition}, if one was found, or
* <code>null</code>.
*/
public static IDefinition findDefinitionByName(IASScope thisScope, ICompilerProject project,
String name, ASDefinitionFilter filter)
{
List<IDefinition> definitions = new ArrayList<IDefinition>(1);
findDefinitionsByNameImpl(thisScope, project, name, filter, definitions, false);
return definitions.size() > 0 ? definitions.get(0) : null;
}
private static void findDefinitionsByNameImpl(IASScope thisScope, ICompilerProject project,
String name, ASDefinitionFilter filter,
List<IDefinition> definitions, boolean findAll)
{
CompilerProject compilerProject = (CompilerProject)project;
if (thisScope instanceof ASProjectScope)
{
Multiname multiName = crackNameAndFilter(project, name, filter);
String baseName = multiName.getBaseName();
Set<INamespaceDefinition> namespaceSet = multiName.getNamespaceSet();
Predicate<IDefinition> filterPredicate = filter.computePredicate(project, null);
Collection<IDefinition> foundDefinitions = new FilteredCollection<IDefinition>(filterPredicate, definitions);
((ASProjectScope)thisScope).getLocalProperty(project, foundDefinitions, baseName, namespaceSet);
}
else
{
ASScope scope = (ASScope)thisScope;
Multiname multiName = crackNameAndFilter(project, name, filter, scope);
String baseName = multiName.getBaseName();
Set<INamespaceDefinition> namespaceSet = multiName.getNamespaceSet();
Predicate<IDefinition> predicate = filter.computePredicate(project, scope);
Collection<IDefinition> foundDefinitions = new FilteredCollection<IDefinition>(predicate, definitions);
if (filter.searchInheritedScopes() && filter.searchContainingScope())
{
// The filter wants to look at inherited scopes and containing scopes,
// so this must be a findprop.
// TODO If we adjust the ASScope cache to cache ambiguous definition
// sets then we can call through to the ASScope cache here, if findAll is false.
scope.findProperty(foundDefinitions, compilerProject, baseName, namespaceSet, null, findAll);
}
else if (filter.searchInheritedScopes())
{
// The filter wants to look at inherited scopes but not at containing scopes,
// so this must be a getprop.
scope.getPropertyForMemberAccess(compilerProject, foundDefinitions, baseName, namespaceSet, findAll);
}
else if (filter.searchContainingScope())
{
// The filter wants to look at containing scopes but not at inherited scopes.
// This type of lookup has no meaning in AS3.
ASScope currentScope = scope;
while ((currentScope != null) && ((foundDefinitions.size() == 0) || findAll))
{
currentScope.getLocalProperty(project, foundDefinitions, baseName, namespaceSet);
currentScope = currentScope.getContainingScope();
}
if ((foundDefinitions.size() == 0) || findAll)
{
// Check project scope if we still don't have a definition.
// The project scope can not introduce ambiguities to the
// file scopes's definitions for the purpose of this method.
ASProjectScope projectScope = compilerProject.getScope();
projectScope.getLocalProperty(project, foundDefinitions, baseName, namespaceSet);
}
}
else
{
// The filter wants to look only at the specified scope
// and not any containing or inherited scopes.
scope.getLocalProperty(project, foundDefinitions, baseName, namespaceSet);
}
}
}
/**
* Constructs a {@link Multiname} by parsing the specified name string and
* extracting information from the specified {@link ASDefinitionFilter}
*
* @param project {@link ICompilerProject} whose symbol table is used to
* resolve namespace references in the "use namespace" set in the
* {@link ASDefnitionFilter}.
* @param name Either a simple definition name or a dotted qname.
* @param filter
* @return A new {@link Multiname} created from information in the specified
* name and {@link ASDefinitionFilter}.
*/
private static Multiname crackNameAndFilter(ICompilerProject project, String name, ASDefinitionFilter filter)
{
return crackNameAndFilter(project, name, filter, null);
}
/**
* Constructs a {@link Multiname} by parsing the specified name string and
* extracting information from the specified {@link ASDefinitionFilter}
*
* @param project {@link ICompilerProject} whose symbol table is used to
* resolve namespace references in the "use namespace" set in the
* {@link ASDefnitionFilter}.
* @param name Either a simple definition name or a dotted qname.
* @param filter
* @param scope the scope we are doing the lookup in - this is used to
* determine if any interface namespaces need to be added to the namespace
* set
* @return A new {@link Multiname} created from information in the specified
* name and {@link ASDefinitionFilter}.
*/
private static Multiname crackNameAndFilter(ICompilerProject project, String name,
ASDefinitionFilter filter, ASScope scope)
{
Workspace workspace = (Workspace)project.getWorkspace();
if (name != null)
{
final int lastIndexOfDot = name != null ? name.lastIndexOf('.') : -1;
if (lastIndexOfDot != -1)
{
Set<INamespaceDefinition> namespaceSet = null;
final String definitionName = name.substring(lastIndexOfDot + 1);
String packageName = name.substring(0, lastIndexOfDot);
INamespaceDefinition publicPackageNS =
workspace.getPackageNamespaceDefinitionCache().get(packageName, false);
namespaceSet = new HashSet<INamespaceDefinition>(1);
ASDefinitionFilter.AccessValue primaryAccessRule =
filter.getPrimaryAccessRule();
if ((primaryAccessRule == ASDefinitionFilter.AccessValue.ALL) ||
(primaryAccessRule == ASDefinitionFilter.AccessValue.INTERNAL))
{
INamespaceDefinition internalPackageNS =
workspace.getPackageNamespaceDefinitionCache().get(packageName, true);
namespaceSet.add(internalPackageNS);
}
namespaceSet.add(publicPackageNS);
return new Multiname(namespaceSet, definitionName);
}
}
// We will filter the funcs and vars in ASScopeBase.applyFilter
if (filter.getRequireImportsValue() != RequireImportsValue.YES)
return new Multiname(ASScopeBase.allNamespacesSet, name);
else
return new Multiname(filter.getNamespaceSetForName(project, scope, name), name);
}
/**
* Constructs a set of {@link INamespaceDefinition} by extracting
* information from the specified {@link ASDefinitionFilter}.
*
* @param project {@link ICompilerProject} whose symbol table is used to
* resolve namespace references in the "use namespace" set in the
* {@link ASDefnitionFilter}.
* @param filter
* @return A new set of {@link INamespaceDefinition} created from
* information in the {@link ASDefinitionFilter}.
*/
private static Set<INamespaceDefinition> crackFilter(ICompilerProject project, ASScope scope,
ASDefinitionFilter filter)
{
// Check for user defined namespace access value.
ASDefinitionFilter.AccessValue accessValue = filter.getPrimaryAccessRule();
if (accessValue != null)
{
// If there is a namespace definition on the access value, we're filtering
// on this one specific namespace in the set.
// No need to do instanceof SpecialAccessValue, as getNamespaceDef() is on the
// base class, and will just return null when not of type SpecialAccessValue.
INamespaceDefinition namespaceAccessValue = accessValue.getNamespaceDef();
if (namespaceAccessValue != null)
return Collections.singleton(namespaceAccessValue);
}
Set<INamespaceDefinition> namespaceSet = ASScopeBase.allNamespacesSet;
if (filter.getRequireImportsValue() == ASDefinitionFilter.RequireImportsValue.YES)
namespaceSet = filter.getNamespaceSet(project, scope);
return namespaceSet;
}
private static Collection<IDefinition> adjustPredicateForProject(ASScope scope, ICompilerProject project,
ASDefinitionFilter filter,
List<IDefinition> definitions)
{
Predicate<IDefinition> projectPredicate = filter.computeProjectPredicate(project, scope);
return new FilteredCollection<IDefinition>(projectPredicate, definitions);
}
}