| /* |
| * |
| * 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; |
| } |
| } |