| /* |
| * |
| * 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.tree.as; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.EnumSet; |
| import java.util.List; |
| |
| import org.apache.royale.compiler.common.ASImportTarget; |
| import org.apache.royale.compiler.common.IImportTarget; |
| import org.apache.royale.compiler.common.Multiname; |
| import org.apache.royale.compiler.common.RecursionGuard; |
| import org.apache.royale.compiler.constants.IMetaAttributeConstants; |
| import org.apache.royale.compiler.constants.INamespaceConstants; |
| import org.apache.royale.compiler.definitions.IClassDefinition.ClassClassification; |
| import org.apache.royale.compiler.definitions.IDefinition; |
| import org.apache.royale.compiler.definitions.metadata.IMetaTag; |
| import org.apache.royale.compiler.definitions.metadata.IMetaTagAttribute; |
| import org.apache.royale.compiler.definitions.references.INamespaceReference; |
| import org.apache.royale.compiler.definitions.references.IReference; |
| import org.apache.royale.compiler.internal.definitions.ClassDefinition; |
| import org.apache.royale.compiler.internal.definitions.FunctionDefinition; |
| import org.apache.royale.compiler.internal.definitions.NamespaceDefinition; |
| import org.apache.royale.compiler.internal.scopes.ASScope; |
| import org.apache.royale.compiler.internal.scopes.TypeScope; |
| import org.apache.royale.compiler.internal.semantics.PostProcessStep; |
| import org.apache.royale.compiler.internal.tree.as.metadata.MetaTagsNode; |
| import org.apache.royale.compiler.parsing.IASToken; |
| import org.apache.royale.compiler.problems.ICompilerProblem; |
| import org.apache.royale.compiler.tree.ASTNodeID; |
| import org.apache.royale.compiler.tree.as.IASNode; |
| import org.apache.royale.compiler.tree.as.IClassNode; |
| import org.apache.royale.compiler.tree.as.IDefinitionNode; |
| import org.apache.royale.compiler.tree.as.IExpressionNode; |
| import org.apache.royale.compiler.tree.as.IIdentifierNode; |
| import org.apache.royale.compiler.tree.metadata.IMetaTagNode; |
| import org.apache.royale.compiler.tree.metadata.IMetaTagsNode; |
| |
| /** |
| * ActionScript parse tree node representing a class definition |
| */ |
| public class ClassNode extends MemberedNode implements IClassNode |
| { |
| /** |
| * Constructor. |
| * |
| * @param name The node holding this class name. |
| */ |
| public ClassNode(ExpressionNodeBase name) |
| { |
| init(name); |
| } |
| |
| /** |
| * The class keyword |
| */ |
| protected KeywordNode classKeywordNode; |
| |
| /** |
| * The extends keyword (if one is present) |
| */ |
| protected KeywordNode extendsKeywordNode; |
| |
| /** |
| * The name of the base class |
| */ |
| protected ExpressionNodeBase baseClassNode; |
| |
| /** |
| * Cache of qualified name to use during type checking |
| */ |
| private String qualifiedName; |
| |
| /** |
| * The extends keyword (if one is present) |
| */ |
| protected KeywordNode implementsKeywordNode; |
| |
| /** |
| * Container full of interfaces implemented by this class |
| */ |
| protected TransparentContainerNode interfacesNode; |
| |
| /** |
| * Generated FunctionNode to represent explicit or default constructor |
| */ |
| protected FunctionNode constructorNode; |
| |
| /** |
| * Generated FunctionNode to represent default constructor (if there isn't |
| * an explicit one) |
| */ |
| protected FunctionNode defaultConstructorNode; |
| |
| // |
| // NodeBase overrides |
| // |
| |
| @Override |
| public ASTNodeID getNodeID() |
| { |
| return ASTNodeID.ClassID; |
| } |
| |
| @Override |
| public int getSpanningStart() |
| { |
| return getNodeStartForTooling(); |
| } |
| |
| @Override |
| protected void setChildren(boolean fillInOffsets) |
| { |
| addDecorationChildren(fillInOffsets); |
| addChildInOrder(classKeywordNode, fillInOffsets); |
| addChildInOrder(nameNode, fillInOffsets); |
| addChildInOrder(extendsKeywordNode, fillInOffsets); |
| addChildInOrder(baseClassNode, fillInOffsets); |
| addChildInOrder(implementsKeywordNode, fillInOffsets); |
| if (implementsKeywordNode != null || interfacesNode.getChildCount() > 0) |
| addChildInOrder(interfacesNode, fillInOffsets); |
| addChildInOrder(contentsNode, fillInOffsets); |
| } |
| |
| @Override |
| protected void analyze(EnumSet<PostProcessStep> set, ASScope scope, Collection<ICompilerProblem> problems) |
| { |
| if (set.contains(PostProcessStep.POPULATE_SCOPE)) |
| { |
| ClassDefinition definition = buildDefinition(); |
| setDefinition(definition); |
| scope.addDefinition(definition); |
| |
| // The BlockNode inside a ClassNode creates a new ASScope, |
| // with the current scope as its parent. |
| // This new scope then gets passed down as the current scope. |
| TypeScope typeScope = new TypeScope(scope, contentsNode, definition); |
| definition.setContainedScope(typeScope); |
| definition.setupThisAndSuper(); |
| definition.buildContingentDefinitions(); |
| scope = typeScope; |
| } |
| |
| if (set.contains(PostProcessStep.RECONNECT_DEFINITIONS)) |
| { |
| reconnectDef(scope); |
| scope = this.getDefinition().getContainedScope(); |
| contentsNode.reconnectScope(scope); |
| } |
| |
| // Recurse on the class block. |
| contentsNode.analyze(set, scope, problems); |
| |
| // Recurse on the class metadata. |
| MetaTagsNode metadata = getMetaTagsNode(); |
| if (metadata != null) |
| metadata.analyze(set, scope, problems); |
| |
| if (set.contains(PostProcessStep.POPULATE_SCOPE)) |
| { |
| // Look for a constructor, or add one if we can't find one |
| setupConstructor(set, scope, problems); |
| } |
| |
| // Don't call super.analyze, as we've already called analyze on some of our children |
| // and calling it again would result in duplicate work |
| //super.analyze(set, scope, errors); |
| |
| if (baseClassNode != null) |
| baseClassNode.analyze(set, scope, problems); |
| if (interfacesNode != null) |
| interfacesNode.analyze(set, scope, problems); |
| } |
| |
| /* |
| * For debugging only. |
| * Builds a string such as <code>"Button"</code> from |
| * the name of the class. |
| */ |
| @Override |
| protected boolean buildInnerString(StringBuilder sb) |
| { |
| sb.append('"'); |
| sb.append(getName()); |
| sb.append('"'); |
| |
| return true; |
| } |
| |
| // |
| // TreeNode overrides |
| // |
| |
| @Override |
| protected int getInitialChildCount() |
| { |
| return 6; |
| } |
| |
| // |
| // BaseDefinitionNode overrides |
| // |
| |
| @Override |
| protected void init(ExpressionNodeBase nameNode) |
| { |
| super.init(nameNode); |
| |
| extendsKeywordNode = null; |
| baseClassNode = null; |
| implementsKeywordNode = null; |
| interfacesNode = new TransparentContainerNode(); |
| constructorNode = null; |
| defaultConstructorNode = null; |
| } |
| |
| @Override |
| public ClassDefinition getDefinition() |
| { |
| return (ClassDefinition)super.getDefinition(); |
| } |
| |
| @Override |
| public void setDefinition(IDefinition def) |
| { |
| assert def instanceof ClassDefinition; |
| super.setDefinition(def); |
| } |
| |
| // |
| // IClassNode implementations |
| // |
| |
| @Override |
| public boolean isImplicit() |
| { |
| return false; |
| } |
| |
| @Override |
| public String getQualifiedName() |
| { |
| if (qualifiedName == null) |
| { |
| IImportTarget importTarget = ASImportTarget.buildImportFromPackageName(getWorkspace(), getPackageName()); |
| String qname = importTarget.getQualifiedName(getName()); |
| // #124877: core.as has a bunch of different packages in it, which is illegal. |
| // just handle it quietly here. |
| if (qname == null) |
| qualifiedName = getShortName(); |
| qualifiedName = qname; |
| } |
| return qualifiedName; |
| } |
| |
| @Override |
| public String getShortName() |
| { |
| String name = getName(); |
| int lastDot = name.lastIndexOf("."); |
| if (lastDot != -1) |
| name = name.substring(lastDot + 1); |
| return name; |
| } |
| |
| @Override |
| public ClassClassification getClassClassification() |
| { |
| if (getParent() instanceof FileNode) |
| return ClassClassification.FILE_MEMBER; |
| |
| return ClassClassification.PACKAGE_MEMBER; |
| } |
| |
| @Override |
| public IMetaTag[] getMetaTagsByName(String name) |
| { |
| return getDefinition().getMetaTagsByName(name); |
| } |
| |
| @Override |
| public IMetaTagNode[] getMetaTagNodesByName(String name) |
| { |
| ArrayList<IMetaTagNode> allMatchingAttributes = new ArrayList<IMetaTagNode>(); |
| getMetaTagsByName(name, new RecursionGuard(), allMatchingAttributes); |
| return allMatchingAttributes.toArray(new IMetaTagNode[0]); |
| } |
| |
| @Override |
| public IExpressionNode getBaseClassExpressionNode() |
| { |
| return baseClassNode; |
| } |
| |
| @Override |
| public String getBaseClassName() |
| { |
| return (baseClassNode instanceof IIdentifierNode) ? ((IIdentifierNode)baseClassNode).getName() : ""; |
| } |
| |
| @Override |
| public IExpressionNode[] getImplementedInterfaceNodes() |
| { |
| ArrayList<IExpressionNode> names = new ArrayList<IExpressionNode>(); |
| int childCount = interfacesNode.getChildCount(); |
| for (int i = 0; i < childCount; i++) |
| { |
| if (interfacesNode.getChild(i) instanceof IIdentifierNode) |
| names.add(((IExpressionNode)interfacesNode.getChild(i))); |
| } |
| return names.toArray(new IExpressionNode[0]); |
| } |
| |
| @Override |
| public String[] getImplementedInterfaces() |
| { |
| ArrayList<String> interfaceNodeList = new ArrayList<String>(); |
| if( interfacesNode != null ) |
| { |
| int childCount = interfacesNode.getChildCount(); |
| if (childCount > 0) |
| { |
| for (int i = 0; i < childCount; i++) |
| { |
| IASNode child = interfacesNode.getChild(i); |
| if (child instanceof IIdentifierNode) |
| { |
| interfaceNodeList.add(((IIdentifierNode)child).getName()); |
| } |
| } |
| } |
| } |
| return interfaceNodeList.toArray(new String[0]); |
| } |
| |
| @Override |
| public IDefinitionNode[] getAllMemberNodes() |
| { |
| ArrayList<IDefinitionNode> names = new ArrayList<IDefinitionNode>(); |
| int childCount = contentsNode.getChildCount(); |
| for (int i = 0; i < childCount; i++) |
| { |
| if (contentsNode.getChild(i) instanceof IDefinitionNode) |
| names.add(((IDefinitionNode)contentsNode.getChild(i))); |
| else if (contentsNode.getChild(i).getNodeID() == ASTNodeID.ConfigBlockID) |
| { |
| ConfigConditionBlockNode configNode = (ConfigConditionBlockNode)contentsNode.getChild(i); |
| int configChildCount = configNode.getChildCount(); |
| for (int j = 0; j < configChildCount; j++) |
| { |
| if (configNode.getChild(j) instanceof IDefinitionNode) |
| names.add(((IDefinitionNode)configNode.getChild(j))); |
| } |
| } |
| } |
| return names.toArray(new IDefinitionNode[0]); |
| } |
| |
| // |
| // Other methods |
| // |
| |
| /** |
| * Sets the class keyword if one is present. Used during parsing. |
| * |
| * @param classKeyword token containing the keyword |
| */ |
| public void setClassKeyword(IASToken classKeyword) |
| { |
| classKeywordNode = new KeywordNode(classKeyword); |
| } |
| |
| /** |
| * Sets the extends keyword if one is present. Used during parsing. |
| * |
| * @param extendsKeyword token containing the keyword |
| */ |
| public void setExtendsKeyword(IASToken extendsKeyword) |
| { |
| extendsKeywordNode = new KeywordNode(extendsKeyword); |
| } |
| |
| /** |
| * Set the base class of this class. Used during parsing. |
| * |
| * @param baseClassNode node containing the base class name |
| */ |
| public void setBaseClass(ExpressionNodeBase baseClassNode) |
| { |
| this.baseClassNode = baseClassNode; |
| } |
| |
| /** |
| * Sets the implements keyword if one is present. Used during parsing. |
| * |
| * @param implementsKeyword token containing the keyword |
| */ |
| public void setImplementsKeyword(IASToken implementsKeyword) |
| { |
| implementsKeywordNode = new KeywordNode(implementsKeyword); |
| } |
| |
| /** |
| * Add an implemented interface to this class. Used during parsing. |
| * |
| * @param interfaceName node containing the interface name |
| */ |
| public void addInterface(ExpressionNodeBase interfaceName) |
| { |
| interfacesNode.addChild(interfaceName); |
| } |
| |
| /** |
| * Method that will only build the explicit definitions for this AS3 class. |
| * This is used by the MXML scope build code to build definitions for a |
| * <fx:Script> tag. |
| * |
| * @param classScope {@link TypeScope} into which this AS3 class' definition |
| * should be added. |
| */ |
| public void buildExplicitMemberDefs(TypeScope classScope) |
| { |
| // Recurse on the class block. |
| contentsNode.analyze(EnumSet.of(PostProcessStep.POPULATE_SCOPE), |
| classScope, new ArrayList<ICompilerProblem>()); |
| } |
| |
| ClassDefinition buildDefinition() |
| { |
| // Ugh... CM allows |
| // class definition that look like this: |
| // public class org.apache.Foo {} |
| // out fix for this is to run the string |
| // through Multiname.getBaseNameForQName. |
| String definitionName = Multiname.getBaseNameForQName(nameNode.computeSimpleReference()); |
| INamespaceReference namespaceReference = NamespaceDefinition.createNamespaceReference(getASScope(), getNamespaceNode()); |
| |
| ClassDefinition definition = new ClassDefinition(definitionName, namespaceReference); |
| definition.setNode(this); |
| |
| fillInModifiers(definition); |
| fillInMetadata(definition); |
| fillInStateNames(definition); |
| |
| // Set the base class. |
| IReference baseRef = null; |
| if (baseClassNode != null) |
| baseRef = baseClassNode.computeTypeReference(); |
| definition.setBaseClassReference(baseRef); |
| |
| // Set the implemented interfaces. |
| if (interfacesNode != null) |
| { |
| int n = interfacesNode.getChildCount(); |
| List<IReference> interfaces = new ArrayList<IReference>(n); |
| for (int i = 0; i < n; i++) |
| { |
| IASNode child = interfacesNode.getChild(i); |
| if (child instanceof ExpressionNodeBase) |
| { |
| IReference typeReference = ((ExpressionNodeBase)child).computeTypeReference(); |
| if (typeReference != null) |
| interfaces.add(typeReference); |
| } |
| } |
| definition.setImplementedInterfaceReferences(interfaces.toArray(new IReference[interfaces.size()])); |
| } |
| |
| return definition; |
| } |
| |
| private void fillInStateNames(ClassDefinition definition) |
| { |
| IMetaTag[] statesMetaData = getMetaTagsByName(IMetaAttributeConstants.ATTRIBUTE_STATES); |
| for (IMetaTag stateMetaData : statesMetaData) |
| { |
| for (IMetaTagAttribute attribute : stateMetaData.getAllAttributes()) |
| { |
| // only look at the value of the attribute, ignoring any |
| // keys which may have been (incorrectly) specified. This matches |
| // the behavior of the old compiler. |
| definition.addStateName(attribute.getValue()); |
| } |
| } |
| } |
| |
| private void setupConstructor(EnumSet<PostProcessStep> set, ASScope scope, Collection<ICompilerProblem> problems) |
| { |
| FunctionDefinition ctorDef = null; |
| // If there's not an explicit constructor, use an implicit one. |
| if (constructorNode == null) |
| { |
| // We don't have an explicit constructor |
| // so we'll create one and add it to the ClassNode |
| IdentifierNode constructorNameNode = new IdentifierNode(getName()); |
| constructorNameNode.setReferenceValue(getDefinition()); |
| constructorNameNode.span(getNameAbsoluteStart(), getNameAbsoluteEnd(), -1, -1, -1, -1); |
| defaultConstructorNode = new FunctionNode(null, constructorNameNode); |
| NamespaceIdentifierNode pub = new NamespaceIdentifierNode(INamespaceConstants.public_); |
| pub.span(-1, -1, -1, -1, -1, -1); |
| defaultConstructorNode.setNamespace(pub); |
| defaultConstructorNode.normalize(true); |
| defaultConstructorNode.setParent(contentsNode); |
| ctorDef = defaultConstructorNode.buildDefinition(); |
| ctorDef.setImplicit(); |
| |
| scope.addDefinition(ctorDef); |
| |
| assert constructorNode == defaultConstructorNode : "FunctionNode.buildDefinition should set the constructor node field"; |
| |
| } |
| else |
| { |
| ctorDef = constructorNode.getDefinition(); |
| } |
| |
| // We need to tell the constructor definition |
| // that it is the definition of a constructor. |
| assert ctorDef != null; |
| ctorDef.setAsConstructor((ClassDefinition)scope.getDefinition()); |
| } |
| |
| /** |
| * Get the node representing the constructor |
| * |
| * @return explicit or default constructor |
| */ |
| public FunctionNode getConstructorNode() |
| { |
| return constructorNode; |
| } |
| |
| /** |
| * Get the node representing the default constructor (if there's no explicit |
| * constructor) |
| * |
| * @return default constructor |
| */ |
| public FunctionNode getDefaultConstructorNode() |
| { |
| return defaultConstructorNode; |
| } |
| |
| /** |
| * Get the implemented interface names (as they appear in the class |
| * definition) |
| * |
| * @return array of interface names |
| */ |
| public String[] getInterfaceNames() |
| { |
| ArrayList<String> names = new ArrayList<String>(); |
| int childCount = interfacesNode.getChildCount(); |
| for (int i = 0; i < childCount; i++) |
| { |
| if (interfacesNode.getChild(i) instanceof IIdentifierNode) |
| names.add(((IIdentifierNode)interfacesNode.getChild(i)).getName()); |
| } |
| return names.toArray(new String[0]); |
| } |
| |
| /** |
| * Get the node containing the class keyword |
| * |
| * @return the node containing class |
| */ |
| public KeywordNode getClassKeywordNode() |
| { |
| return classKeywordNode; |
| } |
| |
| /** |
| * Get the node containing the extends keyword, if one exists |
| * |
| * @return the node containing extends |
| */ |
| public KeywordNode getExtendsKeywordNode() |
| { |
| return extendsKeywordNode; |
| } |
| |
| /** |
| * Get the node containing the base class, if one exists |
| * |
| * @return the node containing the base class |
| */ |
| public ExpressionNodeBase getBaseClassNode() |
| { |
| return baseClassNode; |
| } |
| |
| /** |
| * Get the node containing the implements keyword, if one exists |
| * |
| * @return the node containing implements |
| */ |
| public KeywordNode getImplementsKeywordNode() |
| { |
| return implementsKeywordNode; |
| } |
| |
| /** |
| * Get the container of interfaces for this class |
| * |
| * @return the node containing the interfaces |
| */ |
| public ContainerNode getInterfacesNode() |
| { |
| return interfacesNode; |
| } |
| |
| /** |
| * Retrieves all of the matching meta attributes associated with this class |
| * or any of its base classes |
| * |
| * @param name name of meta attributes to retrieve |
| * @param recursionGuard guard to help avoid infinite loops in the base |
| * class hierarchy |
| * @param allMatchingAttributes list to be filled with all matching meta |
| * attributes |
| */ |
| protected void getMetaTagsByName(String name, RecursionGuard recursionGuard, ArrayList<IMetaTagNode> allMatchingAttributes) |
| { |
| IMetaTagsNode metaTags = getMetaTags(); |
| if (metaTags != null) |
| { |
| IMetaTagNode[] matchingAttributes = metaTags.getTagsByName(name); |
| allMatchingAttributes.addAll(Arrays.asList(matchingAttributes)); |
| } |
| //TODO where is metadata stored |
| // if(!IMetaAttributeConstants.NON_INHERITING_METATAGS.contains(name)) { |
| // IClassNode baseClass = getBaseClassDefinition(recursionGuard); |
| // if (baseClass instanceof ClassNode) |
| // ((ClassNode) baseClass).getMetaTagsByName(name, recursionGuard, allMatchingAttributes); |
| // } |
| } |
| |
| /** |
| * If a classes containing file has includes, it's metadata values cannot be |
| * cached |
| * |
| * @return true if they can be cached |
| */ |
| public boolean canCacheMetaTags() |
| { |
| //when we build, if we have includes, we can't cache these values. Unless we're a swc |
| //and in that case, we don't have to worry about the included definitions changing |
| FileNode fileNode = ((FileNode)getAncestorOfType(FileNode.class)); |
| if (!fileNode.hasIncludes()) |
| return true; |
| |
| return false; |
| } |
| } |