1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.databinding.tool.expr;
18
19import android.databinding.tool.BindingTarget;
20import android.databinding.tool.CallbackWrapper;
21import android.databinding.tool.InverseBinding;
22import android.databinding.tool.reflection.ModelAnalyzer;
23import android.databinding.tool.reflection.ModelClass;
24import android.databinding.tool.reflection.ModelMethod;
25import android.databinding.tool.store.Location;
26import android.databinding.tool.util.L;
27import android.databinding.tool.util.Preconditions;
28import android.databinding.tool.writer.ExprModelExt;
29import android.databinding.tool.writer.FlagSet;
30
31import org.antlr.v4.runtime.ParserRuleContext;
32import org.jetbrains.annotations.Nullable;
33
34import java.util.ArrayList;
35import java.util.Arrays;
36import java.util.BitSet;
37import java.util.HashMap;
38import java.util.List;
39import java.util.Map;
40import java.util.concurrent.atomic.AtomicInteger;
41
42public class ExprModel {
43
44    Map<String, Expr> mExprMap = new HashMap<String, Expr>();
45
46    List<Expr> mBindingExpressions = new ArrayList<Expr>();
47
48    private int mInvalidateableFieldLimit = 0;
49
50    private int mRequirementIdCount = 0;
51
52    // each arg list receives a unique id even if it is the same arguments and method.
53    private int mArgListIdCounter = 0;
54
55    private static final String TRUE_KEY_SUFFIX = "== true";
56    private static final String FALSE_KEY_SUFFIX = "== false";
57
58    /**
59     * Any expression can be invalidated by invalidating this flag.
60     */
61    private BitSet mInvalidateAnyFlags;
62    private int mInvalidateAnyFlagIndex;
63
64    /**
65     * Used by code generation. Keeps the list of expressions that are waiting to be evaluated.
66     */
67    private List<Expr> mPendingExpressions;
68
69    /**
70     * Used for converting flags into identifiers while debugging.
71     */
72    private String[] mFlagMapping;
73
74    private int mFlagBucketCount;// how many buckets we use to identify flags
75
76    private List<Expr> mObservables;
77
78    private boolean mSealed = false;
79
80    private Map<String, String> mImports = new HashMap<String, String>();
81
82    private ParserRuleContext mCurrentParserContext;
83    private Location mCurrentLocationInFile;
84
85    private Map<String, CallbackWrapper> mCallbackWrappers = new HashMap<String, CallbackWrapper>();
86
87    private AtomicInteger mCallbackIdCounter = new AtomicInteger();
88
89    private ExprModelExt mExt = new ExprModelExt();
90
91    /**
92     * Adds the expression to the list of expressions and returns it.
93     * If it already exists, returns existing one.
94     *
95     * @param expr The new parsed expression
96     * @return The expression itself or another one if the same thing was parsed before
97     */
98    public <T extends Expr> T register(T expr) {
99        Preconditions.check(!mSealed, "Cannot add expressions to a model after it is sealed");
100        Location location = null;
101        if (mCurrentParserContext != null) {
102            location = new Location(mCurrentParserContext);
103            location.setParentLocation(mCurrentLocationInFile);
104        }
105        //noinspection unchecked
106        T existing = (T) mExprMap.get(expr.getUniqueKey());
107        if (existing != null) {
108            Preconditions.check(expr.getParents().isEmpty(),
109                    "If an expression already exists, it should've never been added to a parent,"
110                            + "if thats the case, somewhere we are creating an expression w/o"
111                            + "calling expression model");
112            // tell the expr that it is being swapped so that if it was added to some other expr
113            // as a parent, those can swap their references
114            expr.onSwappedWith(existing);
115            if (location != null) {
116                existing.addLocation(location);
117            }
118            return existing;
119        }
120        mExprMap.put(expr.getUniqueKey(), expr);
121        expr.setModel(this);
122        if (location != null) {
123            expr.addLocation(location);
124        }
125        return expr;
126    }
127
128    protected void markSealed() {
129        mSealed = true;
130    }
131
132    public ExprModelExt getExt() {
133        return mExt;
134    }
135
136    public int obtainCallbackId() {
137        return mCallbackIdCounter.incrementAndGet();
138    }
139
140    public void setCurrentParserContext(ParserRuleContext currentParserContext) {
141        mCurrentParserContext = currentParserContext;
142    }
143
144    public ParserRuleContext getCurrentParserContext() {
145        return mCurrentParserContext;
146    }
147
148    public Location getCurrentLocationInFile() {
149        return mCurrentLocationInFile;
150    }
151
152    public Map<String, Expr> getExprMap() {
153        return mExprMap;
154    }
155
156    public int size() {
157        return mExprMap.size();
158    }
159
160    public ComparisonExpr comparison(String op, Expr left, Expr right) {
161        return register(new ComparisonExpr(op, left, right));
162    }
163
164    public InstanceOfExpr instanceOfOp(Expr expr, String type) {
165        return register(new InstanceOfExpr(expr, type));
166    }
167
168    public FieldAccessExpr field(Expr parent, String name) {
169        return register(new FieldAccessExpr(parent, name));
170    }
171
172    public FieldAccessExpr observableField(Expr parent, String name) {
173        return register(new ObservableFieldExpr(parent, name));
174    }
175
176    public MethodReferenceExpr methodReference(Expr parent, String name) {
177        return register(new MethodReferenceExpr(parent, name));
178    }
179
180    public SymbolExpr symbol(String text, Class type) {
181        return register(new SymbolExpr(text, type));
182    }
183
184    public TernaryExpr ternary(Expr pred, Expr ifTrue, Expr ifFalse) {
185        return register(new TernaryExpr(pred, ifTrue, ifFalse));
186    }
187
188    public IdentifierExpr identifier(String name) {
189        return register(new IdentifierExpr(name));
190    }
191
192    public StaticIdentifierExpr staticIdentifier(String name) {
193        return register(new StaticIdentifierExpr(name));
194    }
195
196    public BuiltInVariableExpr builtInVariable(String name, String type, String accessCode) {
197        return register(new BuiltInVariableExpr(name, type, accessCode));
198    }
199
200    public ViewFieldExpr viewFieldExpr(BindingTarget bindingTarget) {
201        return register(new ViewFieldExpr(bindingTarget));
202    }
203
204    /**
205     * Creates a static identifier for the given class or returns the existing one.
206     */
207    public StaticIdentifierExpr staticIdentifierFor(final ModelClass modelClass) {
208        final String type = modelClass.getCanonicalName();
209        // check for existing
210        StaticIdentifierExpr id = findStaticIdentifierExpr(type);
211        if (id != null) {
212            return id;
213        }
214
215        // does not exist. Find a name for it.
216        int cnt = 0;
217        int dotIndex = type.lastIndexOf(".");
218        String baseName;
219        Preconditions.check(dotIndex < type.length() - 1, "Invalid type %s", type);
220        if (dotIndex == -1) {
221            baseName = type;
222        } else {
223            baseName = type.substring(dotIndex + 1);
224        }
225        while (true) {
226            String candidate = cnt == 0 ? baseName : baseName + cnt;
227            if (!mImports.containsKey(candidate)) {
228                return addImport(candidate, type, null);
229            }
230            cnt ++;
231            Preconditions.check(cnt < 100, "Failed to create an import for " + type);
232        }
233    }
234
235    @Nullable
236    private StaticIdentifierExpr findStaticIdentifierExpr(String type) {
237        for (Expr expr : mExprMap.values()) {
238            if (expr instanceof StaticIdentifierExpr) {
239                StaticIdentifierExpr id = (StaticIdentifierExpr) expr;
240                if (id.getUserDefinedType().equals(type)) {
241                    return id;
242                }
243            }
244        }
245        return null;
246    }
247
248    public MethodCallExpr methodCall(Expr target, String name, List<Expr> args) {
249        return register(new MethodCallExpr(target, name, args));
250    }
251
252    public MathExpr math(Expr left, String op, Expr right) {
253        return register(new MathExpr(left, op, right));
254    }
255
256    public TernaryExpr logical(Expr left, String op, Expr right) {
257        if ("&&".equals(op)) {
258            // left && right
259            // left ? right : false
260            return register(new TernaryExpr(left, right, symbol("false", boolean.class)));
261        } else {
262            // left || right
263            // left ? true : right
264            return register(new TernaryExpr(left, symbol("true", boolean.class), right));
265        }
266    }
267
268    public BitShiftExpr bitshift(Expr left, String op, Expr right) {
269        return register(new BitShiftExpr(left, op, right));
270    }
271
272    public UnaryExpr unary(String op, Expr expr) {
273        return register(new UnaryExpr(op, expr));
274    }
275
276    public Expr resourceExpr(BindingTarget target, String packageName, String resourceType,
277            String resourceName, List<Expr> args) {
278        return register(new ResourceExpr(target, packageName, resourceType, resourceName, args));
279    }
280
281    public Expr bracketExpr(Expr variableExpr, Expr argExpr) {
282        return register(new BracketExpr(variableExpr, argExpr));
283    }
284
285    public Expr castExpr(String type, Expr expr) {
286        return register(new CastExpr(type, expr));
287    }
288
289    public TwoWayListenerExpr twoWayListenerExpr(InverseBinding inverseBinding) {
290        return register(new TwoWayListenerExpr(inverseBinding));
291    }
292    public List<Expr> getBindingExpressions() {
293        return mBindingExpressions;
294    }
295
296    public StaticIdentifierExpr addImport(String alias, String type, Location location) {
297        String existing = mImports.get(alias);
298        if (existing != null) {
299            if (existing.equals(type)) {
300                final StaticIdentifierExpr id = findStaticIdentifierExpr(type);
301                Preconditions.checkNotNull(id, "Missing import expression although it is"
302                        + " registered");
303                return id;
304            } else {
305                L.e("%s has already been defined as %s but trying to re-define as %s", alias,
306                        existing, type);
307            }
308        }
309
310        final StaticIdentifierExpr id = staticIdentifier(alias);
311        L.d("adding import %s as %s klass: %s", type, alias, id.getClass().getSimpleName());
312        id.setUserDefinedType(type);
313        if (location != null) {
314            id.addLocation(location);
315        }
316        mImports.put(alias, type);
317        return id;
318    }
319
320    public Map<String, String> getImports() {
321        return mImports;
322    }
323
324    /**
325     * The actual thingy that is set on the binding target.
326     *
327     * Input must be already registered
328     */
329    public Expr bindingExpr(Expr bindingExpr) {
330        Preconditions.check(mExprMap.containsKey(bindingExpr.getUniqueKey()),
331                "Main expression should already be registered");
332        if (!mBindingExpressions.contains(bindingExpr)) {
333            mBindingExpressions.add(bindingExpr);
334        }
335        return bindingExpr;
336    }
337
338    public void removeExpr(Expr expr) {
339        Preconditions.check(!mSealed, "Can't modify the expression list after sealing the model.");
340        mBindingExpressions.remove(expr);
341        mExprMap.remove(expr.getUniqueKey());
342    }
343
344    public List<Expr> getObservables() {
345        return mObservables;
346    }
347
348    /**
349     * Give id to each expression. Will be useful if we serialize.
350     */
351    public void seal() {
352        L.d("sealing model");
353        List<Expr> notifiableExpressions = new ArrayList<Expr>();
354        //ensure class analyzer. We need to know observables at this point
355        final ModelAnalyzer modelAnalyzer = ModelAnalyzer.getInstance();
356        updateExpressions(modelAnalyzer);
357
358        int counter = 0;
359        final Iterable<Expr> observables = filterObservables(modelAnalyzer);
360        List<String> flagMapping = new ArrayList<String>();
361        mObservables = new ArrayList<Expr>();
362        for (Expr expr : observables) {
363            // observables gets initial ids
364            flagMapping.add(expr.getUniqueKey());
365            expr.setId(counter++);
366            mObservables.add(expr);
367            notifiableExpressions.add(expr);
368            L.d("observable %s", expr.getUniqueKey());
369        }
370
371        // non-observable identifiers gets next ids
372        final Iterable<Expr> nonObservableIds = filterNonObservableIds(modelAnalyzer);
373        for (Expr expr : nonObservableIds) {
374            flagMapping.add(expr.getUniqueKey());
375            expr.setId(counter++);
376            notifiableExpressions.add(expr);
377            L.d("non-observable %s", expr.getUniqueKey());
378        }
379
380        // descendants of observables gets following ids
381        for (Expr expr : observables) {
382            for (Expr parent : expr.getParents()) {
383                if (parent.hasId()) {
384                    continue;// already has some id, means observable
385                }
386                // only fields earn an id
387                if (parent instanceof FieldAccessExpr) {
388                    FieldAccessExpr fae = (FieldAccessExpr) parent;
389                    L.d("checking field access expr %s. getter: %s", fae,fae.getGetter());
390                    // FAE#getter might be null if it is used only in a callback.
391                    if (fae.getGetter() != null && fae.isDynamic()
392                            && fae.getGetter().canBeInvalidated()) {
393                        flagMapping.add(parent.getUniqueKey());
394                        parent.setId(counter++);
395                        notifiableExpressions.add(parent);
396                        L.d("notifiable field %s : %s for %s : %s", parent.getUniqueKey(),
397                                Integer.toHexString(System.identityHashCode(parent)),
398                                expr.getUniqueKey(),
399                                Integer.toHexString(System.identityHashCode(expr)));
400                    }
401                }
402            }
403        }
404
405        // now all 2-way bound view fields
406        for (Expr expr : mExprMap.values()) {
407            if (expr instanceof FieldAccessExpr) {
408                FieldAccessExpr fieldAccessExpr = (FieldAccessExpr) expr;
409                if (fieldAccessExpr.getTarget() instanceof ViewFieldExpr) {
410                    flagMapping.add(fieldAccessExpr.getUniqueKey());
411                    fieldAccessExpr.setId(counter++);
412                }
413            }
414        }
415
416        // non-dynamic binding expressions receive some ids so that they can be invalidated
417        L.d("list of binding expressions");
418        for (int i = 0; i < mBindingExpressions.size(); i++) {
419            L.d("[%d] %s", i, mBindingExpressions.get(i));
420        }
421        // we don't assign ids to constant binding expressions because now invalidateAll has its own
422        // flag.
423
424        for (Expr expr : notifiableExpressions) {
425            expr.enableDirectInvalidation();
426        }
427
428        // make sure all dependencies are resolved to avoid future race conditions
429        for (Expr expr : mExprMap.values()) {
430            expr.getDependencies();
431        }
432        mInvalidateAnyFlagIndex = counter ++;
433        flagMapping.add("INVALIDATE ANY");
434        mInvalidateableFieldLimit = counter;
435        BitSet invalidateableFlags = new BitSet();
436        for (int i = 0; i < mInvalidateableFieldLimit; i++) {
437            invalidateableFlags.set(i, true);
438        }
439
440        // make sure all dependencies are resolved to avoid future race conditions
441        for (Expr expr : mExprMap.values()) {
442            if (expr.isConditional()) {
443                L.d("requirement id for %s is %d", expr, counter);
444                expr.setRequirementId(counter);
445                flagMapping.add(expr.getUniqueKey() + FALSE_KEY_SUFFIX);
446                flagMapping.add(expr.getUniqueKey() + TRUE_KEY_SUFFIX);
447                counter += 2;
448            }
449        }
450        BitSet conditionalFlags = new BitSet();
451        for (int i = mInvalidateableFieldLimit; i < counter; i++) {
452            conditionalFlags.set(i, true);
453        }
454        mRequirementIdCount = (counter - mInvalidateableFieldLimit) / 2;
455
456        // everybody gets an id
457        for (Map.Entry<String, Expr> entry : mExprMap.entrySet()) {
458            final Expr value = entry.getValue();
459            if (!value.hasId()) {
460                value.setId(counter++);
461            }
462        }
463
464        mFlagMapping = new String[flagMapping.size()];
465        flagMapping.toArray(mFlagMapping);
466
467        mFlagBucketCount = 1 + (getTotalFlagCount() / FlagSet.sBucketSize);
468        mInvalidateAnyFlags = new BitSet();
469        mInvalidateAnyFlags.set(mInvalidateAnyFlagIndex, true);
470
471        for (Expr expr : mExprMap.values()) {
472            expr.getShouldReadFlagsWithConditionals();
473        }
474
475        for (Expr expr : mExprMap.values()) {
476            // ensure all types are calculated
477            expr.getResolvedType();
478        }
479
480        mSealed = true;
481    }
482
483    /**
484     * Run updateExpr on each binding expression until no new expressions are added.
485     * <p>
486     * Some expressions (e.g. field access) may replace themselves and add/remove new dependencies
487     * so we need to make sure each expression's update is called at least once.
488     */
489    private void updateExpressions(ModelAnalyzer modelAnalyzer) {
490        int startSize = -1;
491        while (startSize != mExprMap.size()) {
492            startSize = mExprMap.size();
493            ArrayList<Expr> exprs = new ArrayList<Expr>(mBindingExpressions);
494            for (Expr expr : exprs) {
495                expr.updateExpr(modelAnalyzer);
496            }
497        }
498    }
499
500    public int getFlagBucketCount() {
501        return mFlagBucketCount;
502    }
503
504    public int getTotalFlagCount() {
505        return mRequirementIdCount * 2 + mInvalidateableFieldLimit;
506    }
507
508    public int getInvalidateableFieldLimit() {
509        return mInvalidateableFieldLimit;
510    }
511
512    public String[] getFlagMapping() {
513        return mFlagMapping;
514    }
515
516    public String getFlag(int id) {
517        return mFlagMapping[id];
518    }
519
520    private List<Expr> filterNonObservableIds(final ModelAnalyzer modelAnalyzer) {
521        List<Expr> result = new ArrayList<Expr>();
522        for (Expr input : mExprMap.values()) {
523            if (input instanceof IdentifierExpr
524                    && !input.hasId()
525                    && !input.isObservable()
526                    && input.isDynamic()) {
527                result.add(input);
528            }
529        }
530        return result;
531    }
532
533    private Iterable<Expr> filterObservables(final ModelAnalyzer modelAnalyzer) {
534        List<Expr> result = new ArrayList<Expr>();
535        for (Expr input : mExprMap.values()) {
536            if (input.isObservable()) {
537                result.add(input);
538            }
539        }
540        return result;
541    }
542
543    public List<Expr> getPendingExpressions() {
544        if (mPendingExpressions == null) {
545            mPendingExpressions = new ArrayList<Expr>();
546            for (Expr expr : mExprMap.values()) {
547                // if an expression is NOT dynanic but has conditional dependants, still return it
548                // so that conditional flags can be set
549                if (!expr.isRead() && (expr.isDynamic() || expr.hasConditionalDependant())) {
550                    mPendingExpressions.add(expr);
551                }
552            }
553        }
554        return mPendingExpressions;
555    }
556
557    public boolean markBitsRead() {
558        // each has should read flags, we set them back on them
559        List<Expr> markedSomeFlagsRead = new ArrayList<Expr>();
560        for (Expr expr : filterShouldRead(getPendingExpressions())) {
561            expr.markFlagsAsRead(expr.getShouldReadFlags());
562            markedSomeFlagsRead.add(expr);
563        }
564        return pruneDone(markedSomeFlagsRead);
565    }
566
567    private boolean pruneDone(List<Expr> markedSomeFlagsAsRead) {
568        boolean marked = true;
569        List<Expr> markedAsReadList = new ArrayList<Expr>();
570        while (marked) {
571            marked = false;
572            for (Expr expr : mExprMap.values()) {
573                if (expr.isRead()) {
574                    continue;
575                }
576                if (expr.markAsReadIfDone()) {
577                    L.d("marked %s as read ", expr.getUniqueKey());
578                    marked = true;
579                    markedAsReadList.add(expr);
580                    markedSomeFlagsAsRead.remove(expr);
581                }
582            }
583        }
584        boolean elevated = false;
585        for (Expr markedAsRead : markedAsReadList) {
586            for (Dependency dependency : markedAsRead.getDependants()) {
587                if (dependency.getDependant().considerElevatingConditionals(markedAsRead)) {
588                    elevated = true;
589                }
590            }
591        }
592        if (!elevated) {
593            for (Expr partialRead : markedSomeFlagsAsRead) {
594                // even if all paths are not satisfied, we can elevate certain conditional
595                // dependencies if all of their paths are satisfied.
596                for (Dependency dependency : partialRead.getDependants()) {
597                    Expr dependant = dependency.getDependant();
598                    if (dependant.isConditional() && dependant.getAllCalculationPaths()
599                            .areAllPathsSatisfied(partialRead.mReadSoFar)) {
600                        if (dependant.considerElevatingConditionals(partialRead)) {
601                            elevated = true;
602                        }
603                    }
604                }
605            }
606        }
607        if (elevated) {
608            // some conditionals are elevated. We should re-calculate flags
609            for (Expr expr : getPendingExpressions()) {
610                if (!expr.isRead()) {
611                    expr.invalidateReadFlags();
612                }
613            }
614            mPendingExpressions = null;
615        }
616        return elevated;
617    }
618
619    private static boolean hasConditionalOrNestedCannotReadDependency(Expr expr) {
620        for (Dependency dependency : expr.getDependencies()) {
621            if (dependency.isConditional() || dependency.getOther().hasNestedCannotRead()) {
622                return true;
623            }
624        }
625        return false;
626    }
627
628    public static ArrayList<Expr> filterShouldRead(Iterable<Expr> exprs) {
629        ArrayList<Expr> result = new ArrayList<Expr>();
630        for (Expr expr : exprs) {
631            if (!expr.getShouldReadFlags().isEmpty() &&
632                    !hasConditionalOrNestedCannotReadDependency(expr)) {
633                result.add(expr);
634            }
635        }
636        return result;
637    }
638
639    /**
640     * May return null if flag is equal to invalidate any flag.
641     */
642    public Expr findFlagExpression(int flag) {
643        if (mInvalidateAnyFlags.get(flag)) {
644            return null;
645        }
646        final String key = mFlagMapping[flag];
647        if (mExprMap.containsKey(key)) {
648            return mExprMap.get(key);
649        }
650        int falseIndex = key.indexOf(FALSE_KEY_SUFFIX);
651        if (falseIndex > -1) {
652            final String trimmed = key.substring(0, falseIndex);
653            return mExprMap.get(trimmed);
654        }
655        int trueIndex = key.indexOf(TRUE_KEY_SUFFIX);
656        if (trueIndex > -1) {
657            final String trimmed = key.substring(0, trueIndex);
658            return mExprMap.get(trimmed);
659        }
660        // log everything we call
661        StringBuilder error = new StringBuilder();
662        error.append("cannot find flag:").append(flag).append("\n");
663        error.append("invalidate any flag:").append(mInvalidateAnyFlags).append("\n");
664        error.append("key:").append(key).append("\n");
665        error.append("flag mapping:").append(Arrays.toString(mFlagMapping));
666        L.e(error.toString());
667        return null;
668    }
669
670    public BitSet getInvalidateAnyBitSet() {
671        return mInvalidateAnyFlags;
672    }
673
674    public int getInvalidateAnyFlagIndex() {
675        return mInvalidateAnyFlagIndex;
676    }
677
678    public Expr argListExpr(Iterable<Expr> expressions) {
679        return register(new ArgListExpr(mArgListIdCounter ++, expressions));
680    }
681
682    public void setCurrentLocationInFile(Location location) {
683        mCurrentLocationInFile = location;
684    }
685
686    public Expr listenerExpr(Expr expression, String name, ModelClass listenerType,
687            ModelMethod listenerMethod) {
688        return register(new ListenerExpr(expression, name, listenerType, listenerMethod));
689    }
690
691    public FieldAssignmentExpr assignment(Expr target, String name, Expr value) {
692        return register(new FieldAssignmentExpr(target, name, value));
693    }
694
695    public Map<String, CallbackWrapper> getCallbackWrappers() {
696        return mCallbackWrappers;
697    }
698
699    public CallbackWrapper callbackWrapper(ModelClass klass, ModelMethod method) {
700        final String key = CallbackWrapper.uniqueKey(klass, method);
701        CallbackWrapper wrapper = mCallbackWrappers.get(key);
702        if (wrapper == null) {
703            wrapper = new CallbackWrapper(klass, method);
704            mCallbackWrappers.put(key, wrapper);
705        }
706        return wrapper;
707    }
708
709    public LambdaExpr lambdaExpr(Expr expr, CallbackExprModel callbackExprModel) {
710        return register(new LambdaExpr(expr, callbackExprModel));
711    }
712
713    public IdentifierExpr findIdentifier(String name) {
714        for (Expr expr : mExprMap.values()) {
715            if (expr instanceof IdentifierExpr && name.equals(((IdentifierExpr) expr).getName())) {
716                return (IdentifierExpr) expr;
717            }
718        }
719        return null;
720    }
721}
722