| /* |
| * |
| * 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.ArrayList; |
| import java.util.Collection; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| import java.util.TreeSet; |
| import java.util.WeakHashMap; |
| |
| import org.apache.royale.compiler.definitions.IClassDefinition; |
| import org.apache.royale.compiler.internal.as.codegen.MXMLClassDirectiveProcessor; |
| import org.apache.royale.compiler.internal.codegen.databinding.WatcherInfoBase.WatcherType; |
| import org.apache.royale.compiler.internal.scopes.ASScope; |
| import org.apache.royale.compiler.problems.ICompilerProblem; |
| import org.apache.royale.compiler.tree.as.IASNode; |
| import org.apache.royale.compiler.tree.mxml.IMXMLBindingNode; |
| import org.apache.royale.compiler.tree.mxml.IMXMLDataBindingNode; |
| |
| /** |
| * After all the bindings in an MXML document (class) are analyzed, this |
| * database will have all the information required to CG the getters, bindings, and watchers |
| * |
| * Note that part of the division of labor is that that the database and the analyzers is uses to get |
| * its data know about parse tree nodes, but clients of the database should not need to know about nodes. |
| * |
| * Conversely the database does not know anything about ABC, but presumably the client of the database does. |
| * |
| * Clients are assumed to use the database as follows: |
| * create a single database for an MXML document (class) |
| * call analyze() on each binding node found in the document. |
| * call finishAnalysis(). |
| * |
| * Then call various getXXX functions to retrieve information |
| */ |
| public class BindingDatabase |
| { |
| |
| public BindingDatabase() |
| { |
| // Testing only: If someone has registered interest, send them "this" |
| if (_diagnosticLogger != null) |
| { |
| _diagnosticLogger.add(this); |
| } |
| } |
| |
| /************** private data ****************/ |
| |
| private Set<BindingInfo> bindingInfoSet = new TreeSet<BindingInfo>(); |
| |
| /** |
| * map that relates one binding index to another |
| * If the pair is in this map, then there is a two way binding relationship between them |
| * Note that key/value makes no difference... Either side of the relationship can be the Key or the Value, |
| * but a pair must only be represented once |
| */ |
| private Map<Integer, Integer> twoWayPairs = null; |
| |
| // Note that we are using a single WatcherInfo as a holder |
| // for all the watchers, which are its children. |
| // This isn't ideal, but the alternative had a lot of code duplication, |
| // which was worse. |
| private WatcherInfoBase watcherList = new WatcherInfoBase(null, null, new ArrayList<String>()); |
| |
| private int numWatchers=-1; |
| |
| private ASScope scope; |
| private boolean analysisFinished = false; |
| |
| private static List<BindingDatabase> _diagnosticLogger; |
| |
| public static WeakHashMap<IClassDefinition, BindingDatabase> bindingMap = new WeakHashMap<IClassDefinition, BindingDatabase>(); |
| |
| /** |
| * test only field. Total number of watcher info's of all types |
| */ |
| public int _watchersCreated; |
| |
| /**************************** public functions ******************/ |
| |
| /** |
| * Unit tests may use this API to get notified of Database creation |
| * Here are the rules: |
| * Don't call this from production code - just for unit testsing |
| * Always call later with null to remove the logger |
| */ |
| public static void _setDiagnosticLogger(List<BindingDatabase> logger) |
| { |
| assert ((_diagnosticLogger == null) || (logger==null)); |
| _diagnosticLogger = logger; |
| } |
| |
| /** |
| * Will do the binding analysis on a single binding node. |
| * May find bindings and watchers already marked for creation by other nodes and re-use them. |
| * |
| */ |
| public BindingInfo analyze(IMXMLDataBindingNode node, Collection<ICompilerProblem> problems, MXMLClassDirectiveProcessor host) |
| { |
| BindingInfo bindingInfo = BindingAnalyzer.analyze(node, this, problems, this.bindingInfoSet.size(), host); |
| this.bindingInfoSet.add(bindingInfo); |
| |
| // now watchers |
| WatcherAnalyzer watcherAnalyzer = new WatcherAnalyzer(this, problems, bindingInfo, host.getProject()); |
| watcherAnalyzer.analyze(); |
| return bindingInfo; |
| } |
| |
| /** |
| * Will do the binding analysis on a single binding node. |
| * May find bindings and watchers already marked for creation by other nodes and re-use them. |
| * |
| */ |
| |
| public void analyzeBindingNode(IMXMLBindingNode node, Collection<ICompilerProblem> problems, MXMLClassDirectiveProcessor host) |
| { |
| BindingInfo info1 = analyzeBindingNode(node, problems, host, false); |
| if (node.getTwoWay()) |
| { |
| // If two way nodex, then analyze again, reversing source and destination roles |
| BindingInfo info2 = analyzeBindingNode(node, problems, host, true); |
| |
| // And note the two bindings are "paired" |
| info1.setTwoWayCounterpart( info2.getIndex()); |
| info2.setTwoWayCounterpart( info1.getIndex()); |
| } |
| } |
| private BindingInfo analyzeBindingNode(IMXMLBindingNode node, Collection<ICompilerProblem> problems, MXMLClassDirectiveProcessor host, boolean reverseSourceAndDest) |
| { |
| BindingInfo bindingInfo = BindingAnalyzer.analyze(node, this, problems, this.bindingInfoSet.size(), reverseSourceAndDest, host); |
| this.bindingInfoSet.add(bindingInfo); |
| |
| // now watchers |
| WatcherAnalyzer watcherAnalyzer = new WatcherAnalyzer(this, problems, bindingInfo, host.getProject()); |
| watcherAnalyzer.analyze(); |
| |
| return bindingInfo; |
| } |
| |
| /** |
| * Must be called once after all the nodes in a document have been analyzed |
| */ |
| public void finishAnalysis() |
| { |
| assert !analysisFinished; |
| assignWatchersToSlots(); |
| makeTwoWayMap(); |
| analysisFinished = true; |
| assert isValid(); |
| } |
| |
| /** |
| * @return information on all the bindings. |
| * |
| * Note that the set will be sorted in the order the binding were created, |
| * which is their "slot index" |
| */ |
| public Set<BindingInfo> getBindingInfo() |
| { |
| return bindingInfoSet; |
| } |
| |
| /** |
| * returns a map relating BindingInfos that are paired in two way bindings |
| * If a binding is returns as a key, it will also not be found as a value - each |
| * binding only appears once |
| * |
| */ |
| public Map<Integer, Integer> getTwoWayBindingInfoPairs() |
| { |
| assert twoWayPairs != null; |
| return twoWayPairs; |
| } |
| |
| /** |
| * @return information about all the watchers - or null if none |
| * |
| * For efficiency reasons, we just return the entry set. We could be nice and turn it into |
| * a list, but the overhead isn't worth if for a low level function like this |
| */ |
| public Set< Entry<Object, WatcherInfoBase>> getWatcherChains() |
| { |
| assert analysisFinished; |
| return watcherList.getChildren(); |
| } |
| |
| /** |
| * |
| * @return total number of watchers required for document |
| */ |
| public int getNumWatchers() |
| { |
| assert analysisFinished; |
| return numWatchers; |
| } |
| |
| /** |
| * @return true is class requires a propertyGetter function |
| */ |
| public boolean getRequiresPropertyGetter() |
| { |
| assert analysisFinished; |
| |
| boolean ret = false; |
| Set<Entry<Object, WatcherInfoBase>> rootWatchers = getWatcherChains(); |
| if (rootWatchers == null) |
| return false; |
| for (Entry<Object, WatcherInfoBase> rootWatcherEntry : rootWatchers) |
| { |
| WatcherInfoBase rootWatcher = rootWatcherEntry.getValue(); |
| if (rootWatcher.type == WatcherType.PROPERTY) |
| { |
| ret = true; // we found a non-static property watcher at the root of a chain |
| } |
| } |
| return ret; |
| } |
| |
| /** |
| * returns the containing scope of one of the databinding nodes that have been analyzed |
| */ |
| public ASScope getScope() |
| { |
| return scope; |
| } |
| |
| public void setScope(ASScope scope) |
| { |
| assert(this.scope==null || this.scope == scope); |
| this.scope = scope; |
| } |
| |
| |
| /** |
| * Searches for appropriate existing watcher, or creates new one. |
| * |
| * If new one is created, it will be added to the correct place in the watcher chains |
| * |
| * @param parent - the parent watcher of the watcher we are interested in, or null for root watchers |
| * @param type - type type of watcher we want to find/create |
| * @param watcherKey - the object that identifies this watcher (map key). Often it is the definition, but we |
| * don't always have a definition for dynamic properties |
| * @param problems - if semantic problems are found, report them here. |
| * @param sourceNode - parse node we are basing the watcher on |
| * @param eventNames - a list of events that the watcher must listen for |
| */ |
| public WatcherInfoBase getOrCreateWatcher( |
| WatcherInfoBase parent, |
| WatcherInfoBase.WatcherType type, |
| Object watcherKey, |
| Collection<ICompilerProblem> problems, |
| IASNode sourceNode, |
| List<String> eventNames ) |
| { |
| assert watcherKey!= null; // or is it OK? |
| WatcherInfoBase ret = null; |
| |
| // final watcher will either be under "parent", or will be under the root, |
| // depending on args passed in |
| WatcherInfoBase newParent = (parent != null) ? |
| parent : watcherList; |
| |
| // look to see if the desired one already exists |
| WatcherInfoBase existingChild = newParent.getOrCreateChildren().get(watcherKey); |
| if (existingChild != null) |
| { |
| ret = existingChild; |
| } |
| else |
| { |
| switch (type) |
| { |
| case PROPERTY: |
| ret = new PropertyWatcherInfo(problems, sourceNode, eventNames); |
| break; |
| case STATIC_PROPERTY: |
| ret = new StaticPropertyWatcherInfo(problems, sourceNode, eventNames); |
| break; |
| case FUNCTION: |
| ret = new FunctionWatcherInfo(problems, sourceNode, eventNames); |
| break; |
| case XML: |
| ret = new XMLWatcherInfo(problems, sourceNode, eventNames); |
| break; |
| default: |
| assert false; |
| break; |
| } |
| ret.isRoot = (parent == null); |
| newParent.children.put(watcherKey, ret); |
| } |
| |
| // make sure if we re-use an existing one that it is the correct type |
| assert objIsType(ret, type); |
| return ret; |
| } |
| |
| @SuppressWarnings("incomplete-switch") |
| private boolean objIsType(WatcherInfoBase obj, WatcherType type) |
| { |
| boolean ret = false; |
| |
| switch (type) |
| { |
| case PROPERTY: ret = obj instanceof PropertyWatcherInfo; break; |
| case STATIC_PROPERTY: ret = obj instanceof StaticPropertyWatcherInfo; break; |
| case FUNCTION: ret = obj instanceof FunctionWatcherInfo; break; |
| case XML: ret = obj instanceof XMLWatcherInfo; break; |
| } |
| return ret; |
| } |
| |
| /** |
| * just for debugging |
| */ |
| @Override |
| public String toString() |
| { |
| StringBuilder sb = new StringBuilder(); |
| |
| if (bindingInfoSet.isEmpty()) |
| { |
| sb.append("<no binding info"); |
| } |
| else |
| { |
| sb.append("bindingInfo:\n"); |
| boolean first = true; |
| for (BindingInfo info : bindingInfoSet) |
| { |
| if (!first) sb.append("\n"); |
| sb.append(" " + info ); |
| first = false; |
| } |
| } |
| |
| if (watcherList.getChildren() != null) |
| { |
| sb.append("\nwatcher chains: "); |
| for (Entry<Object, WatcherInfoBase> ent : watcherList.getChildren()) |
| { |
| sb.append("\n" + ent.getValue().dump(" ")); |
| } |
| } |
| else |
| { |
| sb.append("\n<no watcher chains>"); |
| } |
| return sb.toString(); |
| } |
| |
| /*************************** private methods ************************/ |
| |
| private void assignWatchersToSlots() |
| { |
| int curIndex = -1; |
| curIndex = watcherList.assignIndex(curIndex); |
| numWatchers = curIndex; |
| } |
| |
| /** |
| * Find all the two way binding relationships, and put them into this.twoWayPairs |
| */ |
| private void makeTwoWayMap() |
| { |
| assert twoWayPairs == null; |
| twoWayPairs = new LinkedHashMap<Integer, Integer>(); |
| |
| |
| for (BindingInfo bindingInfo : this.getBindingInfo()) |
| { |
| if (bindingInfo.getTwoWayCounterpart() >= 0) // if this one part of a two way expression |
| { |
| if ((twoWayPairs.get(bindingInfo.getIndex())==null) && |
| (twoWayPairs.get(bindingInfo.getTwoWayCounterpart()))==null) |
| { |
| twoWayPairs.put(bindingInfo.getIndex(), bindingInfo.getTwoWayCounterpart()); |
| } |
| } |
| } |
| } |
| |
| // just for debugging |
| private boolean isValid() |
| { |
| int i=0; |
| for (BindingInfo info : bindingInfoSet) |
| { |
| if (info.getIndex() != i) return false; |
| ++i; |
| } |
| return true; |
| } |
| } |