/* * * 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.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.flex.compiler.common.DependencyType; import org.apache.flex.compiler.common.RecursionGuard; import org.apache.flex.compiler.constants.IASKeywordConstants; import org.apache.flex.compiler.constants.IASLanguageConstants.BuiltinType; import org.apache.flex.compiler.definitions.IClassDefinition; import org.apache.flex.compiler.definitions.IDefinition; import org.apache.flex.compiler.definitions.IInterfaceDefinition; import org.apache.flex.compiler.definitions.INamespaceDefinition; import org.apache.flex.compiler.definitions.IPackageDefinition; import org.apache.flex.compiler.definitions.ITypeDefinition; import org.apache.flex.compiler.definitions.references.INamespaceReference; import org.apache.flex.compiler.definitions.references.IReference; import org.apache.flex.compiler.internal.projects.CompilerProject; import org.apache.flex.compiler.internal.scopes.ASScope; import org.apache.flex.compiler.problems.CircularTypeReferenceProblem; import org.apache.flex.compiler.problems.ICompilerProblem; import org.apache.flex.compiler.problems.IncompatibleInterfaceMethodProblem; import org.apache.flex.compiler.problems.UnimplementedInterfaceMethodProblem; import org.apache.flex.compiler.projects.ICompilerProject; import org.apache.flex.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; } }