1/*
2 * Copyright (C) 2016 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.processing.ErrorMessages;
20import android.databinding.tool.processing.Scope;
21import android.databinding.tool.store.Location;
22import android.databinding.tool.util.L;
23import android.databinding.tool.util.Preconditions;
24
25import java.util.ArrayList;
26import java.util.List;
27import java.util.Map;
28
29/**
30 * Callbacks are evaluated when event happens, not when execute pending is run. To separate their
31 * expressions, we provide a separate model for them that extends the main model. This allows them
32 * to introduce their own variables etc. without mixing them with other expressions.
33 */
34public class CallbackExprModel extends ExprModel {
35    // used for imports and other stuff.
36    final ExprModel mOriginal;
37    final List<CallbackArgExpr> mArguments = new ArrayList<CallbackArgExpr>();
38    public CallbackExprModel(ExprModel original) {
39        mOriginal = original;
40    }
41
42    @Override
43    public Map<String, String> getImports() {
44        return mOriginal.getImports();
45    }
46
47    @Override
48    public StaticIdentifierExpr addImport(String alias, String type, Location location) {
49        return mOriginal.addImport(alias, type, location);
50    }
51
52    @Override
53    public <T extends Expr> T register(T expr) {
54        // locations are only synced to main model so we need to sync overselves here.
55        setCurrentLocationInFile(mOriginal.getCurrentLocationInFile());
56        setCurrentParserContext(mOriginal.getCurrentParserContext());
57        return super.register(expr);
58    }
59
60    @Override
61    public void seal() {
62        // ensure all types are calculated
63        for (Expr expr : mExprMap.values()) {
64            expr.getResolvedType();
65            expr.markAsUsedInCallback();
66        }
67        markSealed();
68        // we do not resolve dependencies for these expression because they are resolved via
69        // ExecutionPath and should not interfere with the main expr model's dependency graph.
70    }
71
72    @Override
73    public IdentifierExpr identifier(String name) {
74        CallbackArgExpr arg = findArgByName(name);
75        if (arg != null) {
76            return arg;
77        }
78        IdentifierExpr id = new IdentifierExpr(name);
79        final Expr existing = mExprMap.get(id.getUniqueKey());
80        if (existing == null) {
81             // this is not a method variable reference. register it in the main model
82            final IdentifierExpr identifier = mOriginal.identifier(name);
83            mExprMap.put(identifier.getUniqueKey(), identifier);
84            identifier.markAsUsedInCallback();
85            return identifier;
86        }
87        return (IdentifierExpr) existing;
88    }
89
90    private CallbackArgExpr findArgByName(String name) {
91        for (CallbackArgExpr arg : mArguments) {
92            if (name.equals(arg.getName())) {
93                return arg;
94            }
95        }
96        return null;
97    }
98
99    public CallbackArgExpr callbackArg(String name) {
100        Preconditions.checkNull(findArgByName(name),
101                ErrorMessages.DUPLICATE_CALLBACK_ARGUMENT, name);
102        final CallbackArgExpr id = new CallbackArgExpr(mArguments.size(), name);
103        final CallbackArgExpr added = register(id);
104        mArguments.add(added);
105
106        try {
107            Scope.enter(added);
108            IdentifierExpr identifierWithSameName = mOriginal.findIdentifier(name);
109            if (identifierWithSameName != null) {
110                L.w(ErrorMessages.CALLBACK_VARIABLE_NAME_CLASH, name, name,
111                        identifierWithSameName.getUserDefinedType());
112            }
113        } finally {
114            Scope.exit();
115        }
116        return added;
117    }
118
119    public int getArgCount() {
120        return mArguments.size();
121    }
122
123    public List<CallbackArgExpr> getArguments() {
124        return mArguments;
125    }
126}
127