/*
*
* 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.Collection;
import java.util.EnumSet;
import org.apache.flex.compiler.constants.IMetaAttributeConstants;
import org.apache.flex.compiler.constants.INamespaceConstants;
import org.apache.flex.compiler.internal.definitions.ConstantDefinition;
import org.apache.flex.compiler.internal.definitions.DefinitionBase;
import org.apache.flex.compiler.internal.definitions.VariableDefinition;
import org.apache.flex.compiler.internal.scopes.ASScope;
import org.apache.flex.compiler.internal.semantics.PostProcessStep;
import org.apache.flex.compiler.internal.tree.as.parts.IDecorationPart;
import org.apache.flex.compiler.internal.tree.as.parts.VariableDecorationPart;
import org.apache.flex.compiler.parsing.IASToken;
import org.apache.flex.compiler.problems.ICompilerProblem;
import org.apache.flex.compiler.tree.as.IASNode;
import org.apache.flex.compiler.tree.as.IExpressionNode;
import org.apache.flex.compiler.tree.as.INamespaceDecorationNode;
import org.apache.flex.compiler.tree.as.IVariableNode;
import org.apache.flex.compiler.tree.metadata.IMetaTagNode;
import org.apache.flex.compiler.tree.metadata.IMetaTagsNode;
/**
* Base class for variables, including both traditional variables as well as
* arguments
*/
public abstract class BaseVariableNode extends BaseTypedDefinitionNode implements IVariableNode, IInitializableDefinitionNode
{
/**
* Constructor.
*
* @param nameNode The node containing the name of the variable/argument.
*/
public BaseVariableNode(IdentifierNode nameNode)
{
super();
init(nameNode);
}
/**
* Constructor.
*
* @param nameNode The node containing the name of the variable/argument.
* @param typeNode The node containing the type of the variable/argument.
*/
public BaseVariableNode(IdentifierNode nameNode, ExpressionNodeBase typeNode)
{
super();
init(nameNode);
this.typeNode = typeNode;
}
/**
* Offset at which the equals operator ("=") starts
*/
protected int equalsOperatorStart;
protected boolean isConst;
//
// NodeBase overrides
//
@Override
protected void setChildren(boolean fillInOffsets)
{
addDecorationChildren(fillInOffsets);
addChildInOrder(((VariableDecorationPart)getDecorationPart()).getKeywordValue(), fillInOffsets);
addChildInOrder(nameNode, fillInOffsets);
ensureTypeNode();
addChildInOrder(typeNode, fillInOffsets);
addChildInOrder(getAssignedValueNode(), fillInOffsets);
}
@Override
protected void analyze(EnumSet<PostProcessStep> set, ASScope scope, Collection<ICompilerProblem> problems)
{
if (set.contains(PostProcessStep.POPULATE_SCOPE))
{
if (needsBindableDefinition())
{
DefinitionBase[] bindingDefs = buildBindableDefinitions(scope);
// Set the definition to the first "binding" definition - it will be a getter or setter,
// so you can always get to the corresponding getter/setter from it.
setDefinition(bindingDefs[0]);
scope.addDefinition(bindingDefs[0]);
scope.addDefinition(bindingDefs[1]);
}
else
{
DefinitionBase definition = buildDefinition();
setDefinition(definition);
scope.addDefinition(definition);
}
}
super.analyze(set, scope, problems);
}
/*
* For debugging only. Build a string such as <code>i:int</code> from the
* name and type of the variable being defined.
*/
@Override
protected boolean buildInnerString(StringBuilder sb)
{
sb.append(getName());
String type = getVariableType();
if (!type.isEmpty())
{
sb.append(':');
sb.append(type);
}
return true;
}
//
// BaseDefinitionNode overrides
//
@Override
protected void init(ExpressionNodeBase node)
{
super.init(node);
equalsOperatorStart = -1;
isConst = false;
}
@Override
protected IDecorationPart createDecorationPart()
{
return new VariableDecorationPart();
}
@Override
// TODO Remove unnecessary override.
public DefinitionBase getDefinition()
{
return super.getDefinition();
}
//
// IVariableNode implementations
//
@Override
public IExpressionNode getVariableTypeNode()
{
return getTypeNode();
}
@Override
public String getVariableType()
{
return getTypeName();
}
@Override
public boolean isConst()
{
return isConst;
}
@Override
public int getDeclarationEnd()
{
return getEnd();
}
//
// IInitializableDefinitionNode implementations
//
@Override
public final ExpressionNodeBase getAssignedValueNode()
{
return ((VariableDecorationPart)getDecorationPart()).getAssignedValue();
}
@Override
// TODO Setter should not be in interface
public void setAssignedValue(IASToken equalsOperator, ExpressionNodeBase value)
{
if (equalsOperator != null)
equalsOperatorStart = equalsOperator.getStart();
((VariableDecorationPart)getDecorationPart()).setAssignedValue(value);
}
//
// Other methods
//
public void setKeyword(IASToken keyword)
{
if (keyword != null) //keyword can be null in some cases
((VariableDecorationPart)getDecorationPart()).setKeywordNode(new KeywordNode(keyword));
}
public void setKeyword(KeywordNode keyword)
{
if (keyword != null)
((VariableDecorationPart)getDecorationPart()).setKeywordNode(keyword);
}
/**
* Sets a flag to indicate that this node is a constant
*
* @param isConst true if we are a constant
*/
public void setIsConst(boolean isConst)
{
this.isConst = isConst;
}
/**
* Ensures the that this variable will have a type as a child. Subclasses
* can override this method to provide different values for the type
*/
protected void ensureTypeNode()
{
//if we don't have a type node, drop in a default identifier that is implicit
if (typeNode == null)
typeNode = LanguageIdentifierNode.buildAnyType();
}
/**
* Build the VariableDefinition or ConstantDefinition that represents this
* Node.
*
* @return the Definition representing this Node.
*/
DefinitionBase buildDefinition()
{
String definitionName = nameNode.computeSimpleReference();
VariableDefinition definition;
definition = isConst() ?
new ConstantDefinition(definitionName) :
new VariableDefinition(definitionName);
fillinDefinition(definition);
definition.setDeclaredInControlFlow(isInControlFlow());
definition.setInitializer(this.getAssignedValueNode());
return definition;
}
/**
* Determine if this var decl was inside a control flow construct
* Basically looks up the parent chain until it gets to it's containing definition,
* if we hit anything other than a block or ChainedVariableNode before then we know we
* are inside some control flow.
* @return true if this variable is declared inside a control flow construct
*/
private boolean isInControlFlow()
{
boolean result = false;
IASNode n = getParent();
while( n !=null )
{
if( n instanceof BlockNode || // OK to be nested in empty blocks
n instanceof ChainedVariableNode )// OK to be nested in a chained variable
{
n = n.getParent();
}
else if( n instanceof BaseDefinitionNode )
{
// If we hit a DefinitionNode, then we made it to our containing definition
// we can stop looking now
n = null;
}
else
{
// Found something between the decl and the containing def, must be something
// control flowish.
result = true;
n = null;
}
}
return result;
}
/**
* Does this variable node need to construct a special definition this is
* true for variables that have bindable metadata and no attributes, or are
* "public" and inside of a ClassNode with bindable metadata with no
* attributes. If it has [Bindable] metadata with an attribute, such as
* [Bindable(event="MyEvent")], then no special handling is needed and we
* can just construct a normal VariableDefinition
*
* @return true if this represents a Bindable variable that needs special
* handling
*/
private boolean needsBindableDefinition()
{
if (hasEmptyBindableMetadata(getMetaTagsNode()))
return true;
INamespaceDecorationNode nsNode = getNamespaceNode();
if (nsNode != null && nsNode.getName() == INamespaceConstants.public_)
{
ClassNode cls = (ClassNode)getAncestorOfType(ClassNode.class);
if (cls != null && hasEmptyBindableMetadata(cls.getMetaTags()))
return true;
}
return false;
}
/**
* Helper method to determine if the IMetaTagsNode passed in has at least 1
* Bindable tag with no attributes
*/
private boolean hasEmptyBindableMetadata(IMetaTagsNode metaTagsNode)
{
boolean hasEmptyBindable = false;
if (metaTagsNode != null)
{
IMetaTagNode[] bindableTags = metaTagsNode.getTagsByName(IMetaAttributeConstants.ATTRIBUTE_BINDABLE);
for (IMetaTagNode node : bindableTags)
{
if (node.getAllAttributes().length == 0)
{
hasEmptyBindable = true;
break;
}
}
}
return hasEmptyBindable;
}
public boolean hasEqualsOperator()
{
return equalsOperatorStart != -1;
}
/**
* Get the offset at which the equals operator ("=") starts
*
* @return start offset for equals operator
*/
public int getEqualsOperatorStart()
{
return equalsOperatorStart;
}
/**
* Get the offset at which the equals operator ("=") ends
*
* @return end offset for equals operator
*/
public int getEqualsOperatorEnd()
{
return equalsOperatorStart + 1;
}
public final KeywordNode getKeywordNode()
{
return ((VariableDecorationPart)getDecorationPart()).getKeywordValue();
}
}