/* * * 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.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.flex.compiler.common.ASImportTarget; import org.apache.flex.compiler.common.IImportTarget; import org.apache.flex.compiler.common.Multiname; import org.apache.flex.compiler.common.RecursionGuard; import org.apache.flex.compiler.constants.IMetaAttributeConstants; import org.apache.flex.compiler.constants.INamespaceConstants; import org.apache.flex.compiler.definitions.IClassDefinition.ClassClassification; import org.apache.flex.compiler.definitions.IDefinition; import org.apache.flex.compiler.definitions.metadata.IMetaTag; import org.apache.flex.compiler.definitions.metadata.IMetaTagAttribute; import org.apache.flex.compiler.definitions.references.INamespaceReference; import org.apache.flex.compiler.definitions.references.IReference; import org.apache.flex.compiler.internal.definitions.ClassDefinition; import org.apache.flex.compiler.internal.definitions.FunctionDefinition; import org.apache.flex.compiler.internal.definitions.NamespaceDefinition; import org.apache.flex.compiler.internal.scopes.ASScope; import org.apache.flex.compiler.internal.scopes.TypeScope; import org.apache.flex.compiler.internal.semantics.PostProcessStep; import org.apache.flex.compiler.internal.tree.as.metadata.MetaTagsNode; import org.apache.flex.compiler.parsing.IASToken; import org.apache.flex.compiler.problems.ICompilerProblem; import org.apache.flex.compiler.tree.ASTNodeID; import org.apache.flex.compiler.tree.as.IASNode; import org.apache.flex.compiler.tree.as.IClassNode; import org.apache.flex.compiler.tree.as.IDefinitionNode; import org.apache.flex.compiler.tree.as.IExpressionNode; import org.apache.flex.compiler.tree.as.IIdentifierNode; import org.apache.flex.compiler.tree.metadata.IMetaTagNode; import org.apache.flex.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))); } 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); defaultConstructorNode = new FunctionNode(null, constructorNameNode); NamespaceIdentifierNode pub = new NamespaceIdentifierNode(INamespaceConstants.public_); pub.span(-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; } }