blob: 3dd6e87bcc2d77d78b0bfdb463198b5aa06e1894 [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.definitions;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
import org.apache.royale.compiler.common.DependencyType;
import org.apache.royale.compiler.common.RecursionGuard;
import org.apache.royale.compiler.constants.IASKeywordConstants;
import org.apache.royale.compiler.constants.IASLanguageConstants.BuiltinType;
import org.apache.royale.compiler.definitions.IClassDefinition;
import org.apache.royale.compiler.definitions.IDefinition;
import org.apache.royale.compiler.definitions.IInterfaceDefinition;
import org.apache.royale.compiler.definitions.INamespaceDefinition;
import org.apache.royale.compiler.definitions.IPackageDefinition;
import org.apache.royale.compiler.definitions.ITypeDefinition;
import org.apache.royale.compiler.definitions.references.INamespaceReference;
import org.apache.royale.compiler.definitions.references.IReference;
import org.apache.royale.compiler.internal.projects.CompilerProject;
import org.apache.royale.compiler.internal.scopes.ASScope;
import org.apache.royale.compiler.problems.CircularTypeReferenceProblem;
import org.apache.royale.compiler.problems.ICompilerProblem;
import org.apache.royale.compiler.problems.IncompatibleInterfaceMethodProblem;
import org.apache.royale.compiler.problems.UnimplementedInterfaceMethodProblem;
import org.apache.royale.compiler.projects.ICompilerProject;
import org.apache.royale.compiler.scopes.IDefinitionSet;
/**
* Each instance of this class represents the definition of an ActionScript
* interface in the symbol table.
* <p>
* After an interface definition is in the symbol table, it should always be
* accessed through the read-only <code>IInterfaceDefinition</code> interface.
*/
public class InterfaceDefinition extends TypeDefinitionBase implements IInterfaceDefinition
{
public InterfaceDefinition(String name)
{
super(name);
interfaceNamespace = null;
}
private IReference[] extendedInterfaces;
/**
* The namespace to use as the interface namespace.
*/
private NamespaceDefinition.ILanguageNamespaceDefinition interfaceNamespace;
@Override
public InterfaceClassification getInterfaceClassification()
{
IDefinition parent = getParent();
if (parent instanceof IPackageDefinition)
return InterfaceClassification.PACKAGE_MEMBER;
if (parent == null)
{
if (inPackageNamespace())
return InterfaceClassification.PACKAGE_MEMBER;
return InterfaceClassification.FILE_MEMBER;
}
assert false;
return null;
}
@Override
public String[] getExtendedInterfacesAsDisplayStrings()
{
if (extendedInterfaces == null)
return new String[0];
String[] interfaces = new String[extendedInterfaces.length];
for (int i = 0, l = extendedInterfaces.length; i < l; ++i) {
if(extendedInterfaces[i] != null) {
interfaces[i] = extendedInterfaces[i].getDisplayString();
}
}
return interfaces;
}
public void setExtendedInterfaceReferences(IReference[] extendedInterfaces)
{
this.extendedInterfaces = extendedInterfaces;
}
@Override
public IReference[] getExtendedInterfaceReferences()
{
return extendedInterfaces;
}
@Override
public IInterfaceDefinition[] resolveExtendedInterfaces(ICompilerProject project)
{
return ((CompilerProject)project).getCacheForScope(getContainedScope()).resolveInterfaces();
}
/**
* resolve the interfaces this interface extends
* @param project the active project
* @return An Array of interfaces that this interface extends
*/
@Override
public IInterfaceDefinition[] resolveInterfacesImpl (ICompilerProject project)
{
int n = extendedInterfaces != null ? extendedInterfaces.length : 0;
// An interface which extends no other interfaces is considered
// to have an inheritance dependency on Object.
if (n == 0)
{
addDependencyOnBuiltinType((CompilerProject)project, BuiltinType.OBJECT,
DependencyType.INHERITANCE);
}
IInterfaceDefinition[] result = new IInterfaceDefinition[n];
for (int i = 0; i < n; i++)
{
ITypeDefinition typeDefinition =
resolveType(extendedInterfaces[i], project, DependencyType.INHERITANCE);
if (!(typeDefinition instanceof IInterfaceDefinition))
typeDefinition = null;
result[i] = (IInterfaceDefinition)typeDefinition;
}
return this.filterNullInterfaces(result);
}
/**
* Adds a dependency from the compilation unit for this interface
* to the the compilation unit for the specified builtin type,
* such as <code>Object</code>.
*
* @param project The {@link CompilerProject} used for resolving references.
* @param builtinType The {@link BuiltinType} type this interface is dependent on.
* @param dependencyType The {@link DependencyType} to be created.
*/
private void addDependencyOnBuiltinType(CompilerProject project, BuiltinType builtinType,
DependencyType dependencyType)
{
getContainingASScope().addDependencyOnBuiltinType(project, builtinType, dependencyType);
}
@Override
public boolean isInstanceOf(final ITypeDefinition type, ICompilerProject project)
{
// An interface is considered an instance of itself.
if (type == this)
return true;
// An interface is an instance of Object by definition.
if (type.equals(project.getBuiltinType(BuiltinType.OBJECT)))
return true;
// Since 'this' is an interface, 'type' must also be an interface.
// (An interface can't be a kind of class.)
if (!(type instanceof IInterfaceDefinition))
return false;
// We're trying to determine whether this interface
// extends a specified interface ('type').
// Iterate all of the interfaces that this class extends,
// looking for 'type'.
Iterator<IInterfaceDefinition> iter = interfaceIterator(project, false);
while (iter.hasNext())
{
IInterfaceDefinition intf = iter.next();
if (intf == type)
return true;
}
return false;
}
@Override
public Set<IInterfaceDefinition> resolveAllInterfaces(ICompilerProject project)
{
Set<IInterfaceDefinition> interfaces = new HashSet<IInterfaceDefinition>();
Iterator<IInterfaceDefinition> iter = interfaceIterator(project, false);
while (iter.hasNext())
{
IInterfaceDefinition intf = iter.next();
interfaces.add(intf);
}
return interfaces;
}
@Override
public Iterator<IInterfaceDefinition> interfaceIterator(ICompilerProject project, boolean includeThis)
{
return new InterfaceIterator(this, project, includeThis, null);
}
public Iterator<IInterfaceDefinition> interfaceIterator(ICompilerProject project, boolean includeThis, Collection<ICompilerProblem> problems)
{
return new InterfaceIterator(this, project, includeThis, problems);
}
/**
* Iterates over all the Interfaces that are implemented/extended by a given
* interface/class If a problem collection is passed in, will detect
* circular dependencies.
*/
public static class InterfaceIterator implements Iterator<IInterfaceDefinition>
{
/**
* Creates iterator for all interfaces extended by an interface
*
* @param thisInterface is the interface we are interested in
* @param problems may be null if problem reporting not needed
*/
public InterfaceIterator(IInterfaceDefinition thisInterface, ICompilerProject project, boolean includeThis, Collection<ICompilerProblem> problems)
{
this.project = project;
initFromInterface(Collections.singleton(thisInterface), includeThis, problems);
}
/**
* Creates iterator for all interfaces implemented by a class
*
* @param cls is the class definition we are interested in
* @param problems may be null if problem reporting not needed
*/
public InterfaceIterator(ClassDefinitionBase cls, ICompilerProject project, Collection<ICompilerProblem> problems)
{
this.project = project;
// First we walk the class hierarchy (not interface hierarchy) to gather all the
// "first level" interfaces implemented by cls
Set<IInterfaceDefinition> clsInterfaces = new HashSet<IInterfaceDefinition>();
Iterator<IClassDefinition> classIterator = cls.classIterator(project, true);
while (classIterator.hasNext())
{
IClassDefinition nextClass = classIterator.next();
ClassDefinitionBase classDefinitionBase = (ClassDefinitionBase)nextClass;
// Note: getImplementedIntefaceRefs will not return one for IEventDispatcher
// on [Bindable] classes. Since we don't want to see IEventDisp, we iterate this way.
IReference[] refs = nextClass.getImplementedInterfaceReferences();
InterfaceDefinition[] idefs = classDefinitionBase.resolveImplementedInterfaces(project, problems);
for (int i = 0; i < refs.length; ++i)
{
if (idefs[i] != null)
{
// Can be null if the interface doesn't actually exist
// we don't report a problem here - someone else does that
clsInterfaces.add(idefs[i]);
}
}
}
// now that we have all the "first level" interfaces, do the full analysis
initFromInterface(clsInterfaces, true, problems);
}
/*********** member fields **********/
private final ICompilerProject project;
// Constructor puts everything that will be iterated in this set
Set<IInterfaceDefinition> theInterfaces = new HashSet<IInterfaceDefinition>();
Iterator<IInterfaceDefinition> underlyingIterator = null;
/********** member functions ************/
/**
* Follows the inheritance of a some interfaces to find all interfaces,
* and detect loops Does a depth first search. This is critical, becuase
* otherwise it would be difficult or impossible to differentiate
* between a true dependency loop and a legal "diamond hierarchy".
*
* @param thisInterfaces is a set of interfaces to be analyzed.
* @param includeThis will cuase thisInterfaces to be aded to
* this.theInterfaces
*/
private void initFromInterface(Set<IInterfaceDefinition> thisInterfaces, boolean includeThis, Collection<ICompilerProblem> problems)
{
for (IInterfaceDefinition iface : thisInterfaces)
{
if (includeThis)
{
theInterfaces.add(iface);
}
RecursionGuard guard = new RecursionGuard();
analyze(iface, guard, problems);
}
underlyingIterator = theInterfaces.iterator();
}
/**
* Recursively analyzes an interface defintion. After analysis: all base
* interfaces will be added to this.theInterfaces any loops detected
* will be added to problems
*/
private void analyze(IInterfaceDefinition iface, RecursionGuard guard, Collection<ICompilerProblem> problems)
{
if (guard.isLoop(iface))
{
if (problems != null)
problems.add(new CircularTypeReferenceProblem(iface, iface.getBaseName()));
return;
}
IInterfaceDefinition[] parentIFaces = iface.resolveExtendedInterfaces(project);
for (IInterfaceDefinition parentIFace : parentIFaces)
{
// as we recurse, we need to create new recursion guards at each level. Otherwise a
// "diamond inheritance hierarchy" would generate a false positive.
// So we make a copy and pass it down to the next level.
RecursionGuard childGuard = new RecursionGuard(guard);
analyze(parentIFace, childGuard, problems);
if (!childGuard.foundLoop)
{
theInterfaces.add(parentIFace);
}
}
}
@Override
public boolean hasNext()
{
return underlyingIterator.hasNext();
}
@Override
public IInterfaceDefinition next()
{
return underlyingIterator.next();
}
@Override
public void remove()
{
throw new UnsupportedOperationException(); // as per the Iterator interface spec.
}
}
@Override
public INamespaceDefinition getProtectedNamespaceReference()
{
return null;
}
@Override
public INamespaceDefinition getStaticProtectedNamespaceReference()
{
return null;
}
/**
* Get the namespace representing the special interface namespace. This is
* the namespace that all interface methods go into for this interface.
*
* @return the special namespace for this interface
*/
public NamespaceDefinition.ILanguageNamespaceDefinition getInterfaceNamespaceReference()
{
return this.interfaceNamespace;
}
/**
* Helper method to generate a URI for the interface namespace. This
* implementation is consistent with the algorithm ASC used, so that we can
* interoperate.
*
* @return the URI to use for the namespace for this interface
*/
String generateInterfaceURI()
{
String uri;
String pack = this.getPackageName();
String shortName = this.getBaseName();
if (pack != "")
uri = pack + ":" + shortName;
else
uri = shortName;
return uri;
}
/**
* Generate a namespace set to use for references through a reference with a
* static type of an interface. For references through an interface, we have
* to generate a completely different namespace set than what is used for
* normal references. This is because interface methods really get put into
* their own special namespace namespace instead of say 'public'. The
* interface namespace set will consist of the namespace for this interface,
* plus the namespaces of any interfaces it extends. The interface
* namespaces are of type CONSTANT_Namespace, and their URI is of the form
* <package-name>:<interface-name>. So, the URI for
* flash.events.IEventDispatcher is "flash.events:IEventDispatcher". This is
* used for code like: var i : flash.events.IEventDispatcher
* i.addEventListener(...); // The namespace set for addEventListener will
* be the one generated by this method.
*
* @param project Project to use to resolve base interfaces
* @return The namespace set to use for member refs through an interface
* type. The returned set should not be modified.
*/
public Set<INamespaceDefinition> getInterfaceNamespaceSet(ICompilerProject project)
{
// TODO check cache on compiler project to see
// if we already know the namespace set for this scope.
// NOTE: Need to use LinkedHashSet here to make the order of the
// namespace set stable across runs of the compiler.
return getInterfaceNamespaceSet(project, InterfaceNamespaceSetOptions.INCLUDE_THIS);
}
/**
* Generate a namespace set to use for references through a reference with a
* static type of an interface. For references through an interface, we have
* to generate a completely different namespace set than what is used for
* normal references. This is because interface methods really get put into
* their own special namespace namespace instead of say 'public'. The
* interface namespace set will consist of the namespace for this interface,
* plus the namespaces of any interfaces it extends. The interface
* namespaces are of type CONSTANT_Namespace, and their URI is of the form
* <package-name>:<interface-name>. So, the URI for
* flash.events.IEventDispatcher is "flash.events:IEventDispatcher". This is
* used for code like: var i : flash.events.IEventDispatcher
* i.addEventListener(...); // The namespace set for addEventListener will
* be the one generated by this method.
*
* @param project Project to use to resolve base interfaces
* @param includeThis true if the interface namespace for this interface should be included.
* @return The namespace set to use for member refs through an interface
* type. The returned set should not be modified.
*/
Set<INamespaceDefinition> getInterfaceNamespaceSet (ICompilerProject project, InterfaceNamespaceSetOptions includeThis)
{
Set<INamespaceDefinition> result = new LinkedHashSet<INamespaceDefinition>();
Iterator<IInterfaceDefinition> iter = this.interfaceIterator(project, includeThis == InterfaceNamespaceSetOptions.INCLUDE_THIS);
while (iter.hasNext())
{
InterfaceDefinition intf = (InterfaceDefinition)iter.next();
result.add(intf.getInterfaceNamespaceReference());
}
return result;
}
/**
* Enum saying whether we should include the interface namespace for this interface in the interface namespace
* set
*/
enum InterfaceNamespaceSetOptions
{
INCLUDE_THIS, // include the namespace for this interface
DONT_INCLUDE_THIS // include only the base interface namespaces
}
@Override
public void setNamespaceReference(INamespaceReference value)
{
super.setNamespaceReference(value);
interfaceNamespace = NamespaceDefinition.createInterfaceNamespaceDefinition(this);
}
/**
* Method to find all the methods declared in this interface, and validate
* that the class definition passed in implements those methods, and that
* they are implemented with compatible signatures
*
* @param cls the class definition to check
* @param problems a list of problems to report errors to
*/
public void validateClassImplementsAllMethods(ICompilerProject project, ClassDefinition cls, Collection<ICompilerProblem> problems)
{
// Interface methods must be implemented by public methods
INamespaceDefinition publicNs = NamespaceDefinition.getPublicNamespaceDefinition();
ASScope classScope = cls.getContainedScope();
for (IDefinitionSet defSet : this.getContainedScope().getAllLocalDefinitionSets())
{
for (int i = 0, l = defSet.getSize(); i < l; ++i)
{
IDefinition def = defSet.getDefinition(i);
if (def instanceof FunctionDefinition)
{
FunctionDefinition interfMethod = (FunctionDefinition)def;
// Skip any implicit methods added for CM compat
if (interfMethod.isImplicit())
continue;
// Skip the constructor method of the interface.
if (interfMethod.getBaseName().equals(getBaseName()))
continue;
IDefinition c = classScope.getQualifiedPropertyFromDef(project,
cls,
interfMethod.getBaseName(),
publicNs,
false);
// Match up getters and setters
if (interfMethod instanceof SetterDefinition && c instanceof GetterDefinition)
c = ((GetterDefinition)c).resolveCorrespondingAccessor(project);
else if (interfMethod instanceof GetterDefinition && c instanceof SetterDefinition)
c = ((SetterDefinition)c).resolveCorrespondingAccessor(project);
String ifaceName = this.getBaseName();
if (c instanceof FunctionDefinition)
{
FunctionDefinition classMethod = (FunctionDefinition)c;
if (!classMethod.hasCompatibleSignature(interfMethod, project))
{
problems.add(new IncompatibleInterfaceMethodProblem(classMethod,
interfMethod.getBaseName(),
ifaceName,
cls.getBaseName()));
}
}
else
{
// Error, didn't implement the method
problems.add(new UnimplementedInterfaceMethodProblem(cls,
interfMethod.getBaseName(),
ifaceName,
cls.getBaseName()));
}
}
}
}
}
/**
* For debugging only. Produces a string such as
* <code>public interface I extends I1, I2</code>.
*/
@Override
protected void buildInnerString(StringBuilder sb)
{
sb.append(getNamespaceReferenceAsString());
sb.append(' ');
sb.append(IASKeywordConstants.INTERFACE);
sb.append(' ');
sb.append(getBaseName());
String[] extendedInterfaces = getExtendedInterfacesAsDisplayStrings();
int n = extendedInterfaces.length;
if (n > 0)
{
sb.append(' ');
sb.append(IASKeywordConstants.IMPLEMENTS);
sb.append(' ');
for (int i = 0; i < n; i++)
{
sb.append(extendedInterfaces[i]);
if (i < n - 1)
{
sb.append(',');
sb.append(' ');
}
}
}
}
@Override
public boolean matches(DefinitionBase node)
{
boolean matches = super.matches(node);
if (!matches)
return false;
String[] leftNames = ((InterfaceDefinition)node).getExtendedInterfacesAsDisplayStrings();
String[] rightNames = getExtendedInterfacesAsDisplayStrings();
if (leftNames.length != rightNames.length)
{
return false;
}
HashSet<String> hitTable = new HashSet<String>(Arrays.asList(leftNames));
for (int i = 0; i < rightNames.length; i++)
{
if (!hitTable.contains(rightNames[i]))
return false;
}
return true;
}
}