blob: 7f9289ea5a434796926e4cb5e868715d1a60235e [file] [log] [blame]
/*
*
* 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.codegen.databinding;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.apache.royale.compiler.constants.IASKeywordConstants;
import org.apache.royale.compiler.definitions.IClassDefinition;
import org.apache.royale.compiler.definitions.IConstantDefinition;
import org.apache.royale.compiler.definitions.IDefinition;
import org.apache.royale.compiler.definitions.IFunctionDefinition;
import org.apache.royale.compiler.definitions.IInterfaceDefinition;
import org.apache.royale.compiler.definitions.ITypeDefinition;
import org.apache.royale.compiler.definitions.IVariableDefinition;
import org.apache.royale.compiler.internal.codegen.databinding.WatcherInfoBase.WatcherType;
import org.apache.royale.compiler.internal.projects.RoyaleProject;
import org.apache.royale.compiler.internal.tree.as.FunctionCallNode;
import org.apache.royale.compiler.problems.ICompilerProblem;
import org.apache.royale.compiler.problems.MXMLDatabindingSourceNotBindableProblem;
import org.apache.royale.compiler.projects.ICompilerProject;
import org.apache.royale.compiler.tree.ASTNodeID;
import org.apache.royale.compiler.tree.as.IASNode;
import org.apache.royale.compiler.tree.as.IExpressionNode;
import org.apache.royale.compiler.tree.as.IFunctionCallNode;
import org.apache.royale.compiler.tree.as.IIdentifierNode;
import org.apache.royale.compiler.tree.as.IMemberAccessExpressionNode;
/**
* Analyzes a node that represents a binding expression, an generates all the
* WatcherInfo objects needed to describe the mx.internal.binding.Watchers we will code gen.
*/
public class WatcherAnalyzer
{
/**
* Constructor
*
* @param bindingDataBase - is where the results of the analysis are stored
* @param problems - is where any semantic problems discuvered during analysis are reported
* @param bindingInfo - is an object created by the BindingAnalyzer to represing the mx.internal.binding.Binding object that will control
* this binding expression
*/
public WatcherAnalyzer(
BindingDatabase bindingDataBase,
Collection<ICompilerProblem> problems,
BindingInfo bindingInfo,
ICompilerProject project)
{
this.bindingDataBase = bindingDataBase;
this.problems = problems;
this.bindingInfo = bindingInfo;
this.project = project;
}
//------------------- private state -----------------------------
private final BindingDatabase bindingDataBase;
private final Collection<ICompilerProblem> problems;
private final BindingInfo bindingInfo;
private final ICompilerProject project;
//------------------------ public functions ----------------------
/**
* structure to holder onto temporary information is we do a
* recursive descent parse of the binding expression node
*/
static class AnalysisState
{
/**
* If true, we we will chain watchers as we find new variables. ex: {a.b}
* If false, we will generate independent watchers ex: { foo(a, b) }
*/
public boolean chaining = false;
/**
* as we are building up a chain of dependent property watchers, curChain
* points to the lowest "link" that was build. Will be null if we are not yet building
* up a chain.
*
* It is of the specific type "PropertyWatcherInfo", because that is the only
* info class that knows how to chain.
*/
public WatcherInfoBase curChain = null;
/**
* While we are parsing a function call node, this will
* hold the definition for it's name
*/
public IDefinition functionCallNameDefintion = null;
/**
* If we are parsing an expression like model.a.b.c.d,
* where "Model" is an ObjectProxy or Model tag
*/
public boolean isObjectProxyExpression = false;
/**
* the event name(s) that the ObjectProxy/Model dispatches.
* We need to pass this back up the parse chain so that the subsequent
* property watchers know the event name(s)
*/
List<String> objectProxyEventNames = null;
}
public void analyze()
{
// When there are multiple expression (concatenating case), then we can
// just analyze each one independently
for (IExpressionNode expression : bindingInfo.getExpressionNodesForGetter())
{
doAnalyze(expression, new AnalysisState()); // recursively analyze the sub-tree.
}
}
private void doAnalyze(IASNode node, AnalysisState state)
{
ASTNodeID id = node.getNodeID();
switch(id)
{
case IdentifierID:
analyzeIdentifierNode((IIdentifierNode)node, state);
break;
case MemberAccessExpressionID:
andalyzeMemberAccessExpression( (IMemberAccessExpressionNode) node, state);
break;
case FunctionCallID:
analyzeFunctionCallNode((IFunctionCallNode) node, state);
break;
default:
// For other kinds of nodes, just recurse down looking for something to do
analyzeChildren(node, state);
break;
}
}
private void analyzeChildren(IASNode node, AnalysisState state)
{
// For other kinds of nodes, just recurse down looking for something to do
for (int i=0; i<node.getChildCount(); ++i)
{
IASNode ch = node.getChild(i);
doAnalyze(ch, state);
}
}
private void analyzeFunctionCallNode(IFunctionCallNode node, AnalysisState state)
{
assert state.functionCallNameDefintion == null; // we can't nest (yet?)
// First, let's get the node the represents the function name.
// That's the one that will have the binding metadata
IExpressionNode nameNode = node.getNameNode();
IDefinition nameDef = nameNode.resolve(project);
// we ignore non-bindable functions
// we also ignore functions that don't resolve - they are dynamic, and hence
// not watchable
if (nameDef!=null && nameDef.isBindable())
{
state.functionCallNameDefintion = nameDef;
}
analyzeChildren(node, state); // continue down looking for watchers.
state.functionCallNameDefintion = null; // now we are done with the function call node
}
private void andalyzeMemberAccessExpression(IMemberAccessExpressionNode node, AnalysisState state)
{
final boolean wasChaining = state.chaining;
state.chaining = true; // member access expressions require chained watcher.
// ex: {a.b} - the watcher for b will be a child of a
final IExpressionNode left = node.getLeftOperandNode();
final IExpressionNode right = node.getRightOperandNode();
final IDefinition leftDef = left.resolve(project);
final IDefinition rightDef = right.resolve(project);
if (leftDef instanceof IClassDefinition)
{
// In this case, is foo.prop, where "foo" is a class name.
// We can skip over the left side ("foo"), because when we
// analyze the right side we will still know what it is.
doAnalyze(right, state);
}
else
{
if ((rightDef == null) && (leftDef != null))
{
// maybe we are something like ObjectProxy.dynamicProperty
// (someone who extends ObjectProxy, like Model)
ITypeDefinition leftType= leftDef.resolveType(project);
RoyaleProject project = (RoyaleProject)this.project;
String objectProxyClass = project.getObjectProxyClass();
boolean isObjectProxy = leftType==null ? false : leftType.isInstanceOf(objectProxyClass, project);
// If we are proxy.prop, we set this info into the parse state. This does two things:
// 1) tells downstream properties that they can be dynamic, and hence don't need
// to be resolvable.
// 2) Stores off the event names from the proxy so that the chained property watchers
// can use the info
if (isObjectProxy)
{
state.isObjectProxyExpression = true;
state.objectProxyEventNames = leftType.getBindableEventNames();
}
else
{
String proxyClass = project.getProxyBaseClass();
boolean isProxy = leftType==null ? false : leftType.isInstanceOf(proxyClass, project);
if (isProxy)
{
state.isObjectProxyExpression = true;
state.objectProxyEventNames = leftType.getBindableEventNames();
}
}
}
doAnalyze(left, state);
doAnalyze(right, state);
}
// If we are finished generating the chain for the top member access expression,
// then shut off chaining. Otherwise the next variable we see might get added to this chain,
// even though is it not related
if (!wasChaining)
{
state.chaining = false;
state.curChain = null;
}
// If we are finished with a chain of ObjectProxy.prop.prop things,
// then clear out the remembered info from the ObjectProxy.
// Note that this "termination condition" is quite different from the one above.
// Above the logic was "I'm going to set this, recursively apply to children, then clear.
// In this case, the logic is "I'm going to set and forget, because my PARENT want these results.
// Eventually I can clear it when my I can determine that my parent is not part of the chain.
if ( state.isObjectProxyExpression)
{
IASNode parent = node.getParent();
if (!(parent instanceof IMemberAccessExpressionNode))
{
state.isObjectProxyExpression = false;
state.objectProxyEventNames = null;
}
}
}
private void analyzeIdentifierNode(IIdentifierNode node, AnalysisState state)
{
IDefinition def = node.resolve(project);
if ((def == null) && !state.isObjectProxyExpression)
{
if (node.getName() == IASKeywordConstants.THIS)
return; // this is not "defensive programming"!
// we fully expect to get non-resolvable identifiers in some cases:
// a) bad code.
// b) "this"
// It should be fine to skip over these and continue without creating a watcher
// If, on the other hand, we are in an object proxy, then we
// may very well be a dynamic property with no definition,
// so will will continue on (with the knowledge that we have no
// IDefinition
this.problems.add(new MXMLDatabindingSourceNotBindableProblem(node, node.getName()));
return;
}
if (def instanceof IConstantDefinition)
{
return; // we can ignore constants - they can't be watched
}
assert state.chaining || state.curChain==null; // we shouldn't have a chain if we aren't chaining
// Now round up the arguments to make the watcher info
List<String> eventNames = null;
WatcherType type = WatcherType.ERROR;
String name = null;
Object id = null;
if ((def == null) && state.isObjectProxyExpression)
{
// we are in a dynamic property situation...
type = WatcherType.PROPERTY; // always use property watcher
name = node.getName(); // get the property name from the id node, since
// we have no definition
id = node; //use the parse node as identifier for the watcher, since
// we don't have an identifier to use. Not that this means we can't
// share dynamic property watchers. too bad!
eventNames = state.objectProxyEventNames; // use the event names we remembered from the proxy
}
else
{
// we are a not a dynamic property
// Check to see if we are a cast. If so, we can ignore
if (def instanceof IClassDefinition || def instanceof IInterfaceDefinition)
{
// we are an identifier that resolves to a class. perhaps we are a cast?
// we are a case if the node parent is a function call, but in any case
// if we see a class here it's either a cast, or it's just a class name
// in an expression.
// If it's a cast, there's nothing wrong. If it's a class, we treat it like a constant string
// bottom line: don't try to make a watcher, and don't generate a problem
return;
}
type = determineWatcherType(def); // figure out what kind of watcher to make
if (type == WatcherType.ERROR)
{
System.err.println("can't get watcher for " + def);
return; // This should never happen. If it does, there is presumably a bug.
// But - better to recover here, than to go on and NPE.
// This is a workaround for CMP-1283
}
// if it's a function, make sure it is the one from the function call expression we are parsing
// If it isn't, just return. This might happen if, for example, the function was
// not bindable - then functionCallNameDefintion would be null.
// I suspect there are other cases, too.
if (type == WatcherType.FUNCTION)
{
if (def != state.functionCallNameDefintion)
return;
}
eventNames = WatcherInfoBase.getEventNamesFromDefinition(def, problems, node, project);
name = def.getBaseName();
id = def;
//solves a problem for function bindings where all bindings resolve to the latest one
if (type == WatcherType.FUNCTION) {
//make sure we have unique watchers for each functionCallNode
id = node;//or maybe node.getAncestorOfType(FunctionCallNode.class); ?
}
}
makeWatcherOfKnownType(id, type, state, node, eventNames, name, def);
// Now, check if we need to make an XML watcher. These are special:
// We make an XMLWatcher and a property watcher to watch the same thing, so two of them
// are created in this one call
if (def instanceof IVariableDefinition)
{
// Is the def for an XML type variable?
IVariableDefinition var = (IVariableDefinition)def;
ITypeDefinition varType = var.resolveType(project);
// note that varType might be null if code is bad
boolean isVarXML = (varType==null) ? false : varType.isInstanceOf("XML", project);
if (isVarXML)
{
// if XML,then create a watcher for it.
String key = "XML"; // using this unique key will work, but it means we can never
// share these. Which is OK.
List<String> empty = Collections.emptyList();
makeWatcherOfKnownType(key, WatcherType.XML, state, node, empty, name, null);
}
}
}
/**
* Master "factory function" for Watcher Info
* You should always use this factory, rather than instantiating the directly, as it is
* easier and more reliable to user this function.
*
* @param watcherKey is unique object "key" to refer to the created watcher. Often an IDefintion
* @param type is which kind of watcher we want to create
* @param state must be passed in so we can chain watchers as we create them during parse time
* @param node the node that corresponds to the object we will be watching
* @param eventNames the events that the watcher must listen to
* @param name the name of the property or function that we will be watching
* @param optionalDefinition the IDefinition for the node. Only required for Static Property Watchers
*/
private void makeWatcherOfKnownType(
Object watcherKey,
WatcherType type,
AnalysisState state,
IASNode node,
List<String> eventNames,
String name,
IDefinition optionalDefinition)
{
assert(eventNames != null);
assert(name != null);
WatcherInfoBase watcherInfo = bindingDataBase.getOrCreateWatcher(
state.curChain,
type,
watcherKey,
problems,
node,
eventNames);
// After doing the "generic creation" we need to do some type specific
// setup
if (type == WatcherType.FUNCTION)
{
// TODO: couldn't we move this into a ctor somehow?
FunctionWatcherInfo fwi = (FunctionWatcherInfo)watcherInfo;
// fwi.setFunctionName(def.getName());
fwi.setFunctionName(name);
// Note: we might want to retrieve the function arguments in the future.
// But they aren't available on this object - they are on the original FunctionCallExpression node
FunctionCallNode fnNode = (FunctionCallNode)node.getAncestorOfType(FunctionCallNode.class);
IExpressionNode[] paramNodes = fnNode.getArgumentNodes();
fwi.params = paramNodes;
}
else if ((type == WatcherType.STATIC_PROPERTY) || (type == WatcherType.PROPERTY))
{
PropertyWatcherInfo pwi = (PropertyWatcherInfo)watcherInfo;
pwi.setPropertyName(name);
// TODO: can we just have a "name" on the base class??
}
else if (type == WatcherType.XML)
{
XMLWatcherInfo xwi = (XMLWatcherInfo)watcherInfo;
xwi.setPropertyName(name);
}
if (type == WatcherType.STATIC_PROPERTY)
{
assert optionalDefinition != null;
// static property watchers need this extra call
StaticPropertyWatcherInfo pwi = (StaticPropertyWatcherInfo)watcherInfo;
pwi.init(optionalDefinition, project);
}
watcherInfo.addBinding(bindingInfo); // associate our binding with this watcher
// note that one watcher can have more than one binding.
if (state.chaining)
state.curChain = watcherInfo; // mark this as the new bottom link in the chain
}
private static WatcherType determineWatcherType(IDefinition definition)
{
WatcherType ret = WatcherType.ERROR;
if (definition == null) return ret; // there are "special" watchers where this will be null
if (definition instanceof IVariableDefinition)
{
if (!definition.isStatic())
{
// we use regular property watchers on non-static variables
ret = WatcherType.PROPERTY;
}
else
{
ret = WatcherType.STATIC_PROPERTY;
}
}
else if (definition instanceof IFunctionDefinition)
{
ret = WatcherType.FUNCTION;
}
else assert false;
return ret;
}
}