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;
18
19import android.databinding.tool.expr.Dependency;
20import android.databinding.tool.expr.Expr;
21import android.databinding.tool.expr.ExprModel;
22import android.databinding.tool.expr.IdentifierExpr;
23import android.databinding.tool.processing.Scope;
24import android.databinding.tool.processing.scopes.FileScopeProvider;
25import android.databinding.tool.store.Location;
26import android.databinding.tool.store.ResourceBundle;
27import android.databinding.tool.store.ResourceBundle.BindingTargetBundle;
28import android.databinding.tool.util.L;
29import android.databinding.tool.util.Preconditions;
30import android.databinding.tool.writer.LayoutBinderWriter;
31import android.databinding.tool.writer.LayoutBinderWriterKt;
32
33import com.android.annotations.Nullable;
34
35import java.util.ArrayList;
36import java.util.Collections;
37import java.util.Comparator;
38import java.util.HashMap;
39import java.util.HashSet;
40import java.util.List;
41
42/**
43 * Keeps all information about the bindings per layout file
44 */
45public class LayoutBinder implements FileScopeProvider {
46    private static final Comparator<BindingTarget> COMPARE_FIELD_NAME = new Comparator<BindingTarget>() {
47        @Override
48        public int compare(BindingTarget first, BindingTarget second) {
49            final String fieldName1 = LayoutBinderWriterKt.getFieldName(first);
50            final String fieldName2 = LayoutBinderWriterKt.getFieldName(second);
51            return fieldName1.compareTo(fieldName2);
52        }
53    };
54
55    /*
56    * val pkg: String, val projectPackage: String, val baseClassName: String,
57        val layoutName:String, val lb: LayoutExprBinding*/
58    private final ExprModel mExprModel;
59    private final ExpressionParser mExpressionParser;
60    private final List<BindingTarget> mBindingTargets;
61    private final List<BindingTarget> mSortedBindingTargets;
62    private String mModulePackage;
63    private final HashMap<String, String> mUserDefinedVariables = new HashMap<String, String>();
64
65    private LayoutBinderWriter mWriter;
66    private ResourceBundle.LayoutFileBundle mBundle;
67    private static final String[] sJavaLangClasses = {
68            "Deprecated",
69            "Override",
70            "SafeVarargs",
71            "SuppressWarnings",
72            "Appendable",
73            "AutoCloseable",
74            "CharSequence",
75            "Cloneable",
76            "Comparable",
77            "Iterable",
78            "Readable",
79            "Runnable",
80            "Thread.UncaughtExceptionHandler",
81            "Boolean",
82            "Byte",
83            "Character",
84            "Character.Subset",
85            "Character.UnicodeBlock",
86            "Class",
87            "ClassLoader",
88            "Compiler",
89            "Double",
90            "Enum",
91            "Float",
92            "InheritableThreadLocal",
93            "Integer",
94            "Long",
95            "Math",
96            "Number",
97            "Object",
98            "Package",
99            "Process",
100            "ProcessBuilder",
101            "Runtime",
102            "RuntimePermission",
103            "SecurityManager",
104            "Short",
105            "StackTraceElement",
106            "StrictMath",
107            "String",
108            "StringBuffer",
109            "StringBuilder",
110            "System",
111            "Thread",
112            "ThreadGroup",
113            "ThreadLocal",
114            "Throwable",
115            "Void",
116            "Thread.State",
117            "ArithmeticException",
118            "ArrayIndexOutOfBoundsException",
119            "ArrayStoreException",
120            "ClassCastException",
121            "ClassNotFoundException",
122            "CloneNotSupportedException",
123            "EnumConstantNotPresentException",
124            "Exception",
125            "IllegalAccessException",
126            "IllegalArgumentException",
127            "IllegalMonitorStateException",
128            "IllegalStateException",
129            "IllegalThreadStateException",
130            "IndexOutOfBoundsException",
131            "InstantiationException",
132            "InterruptedException",
133            "NegativeArraySizeException",
134            "NoSuchFieldException",
135            "NoSuchMethodException",
136            "NullPointerException",
137            "NumberFormatException",
138            "ReflectiveOperationException",
139            "RuntimeException",
140            "SecurityException",
141            "StringIndexOutOfBoundsException",
142            "TypeNotPresentException",
143            "UnsupportedOperationException",
144            "AbstractMethodError",
145            "AssertionError",
146            "ClassCircularityError",
147            "ClassFormatError",
148            "Error",
149            "ExceptionInInitializerError",
150            "IllegalAccessError",
151            "IncompatibleClassChangeError",
152            "InstantiationError",
153            "InternalError",
154            "LinkageError",
155            "NoClassDefFoundError",
156            "NoSuchFieldError",
157            "NoSuchMethodError",
158            "OutOfMemoryError",
159            "StackOverflowError",
160            "ThreadDeath",
161            "UnknownError",
162            "UnsatisfiedLinkError",
163            "UnsupportedClassVersionError",
164            "VerifyError",
165            "VirtualMachineError",
166    };
167
168    public LayoutBinder(ResourceBundle.LayoutFileBundle layoutBundle) {
169        try {
170            Scope.enter(this);
171            mExprModel = new ExprModel();
172            mExpressionParser = new ExpressionParser(mExprModel);
173            mBindingTargets = new ArrayList<BindingTarget>();
174            mBundle = layoutBundle;
175            mModulePackage = layoutBundle.getModulePackage();
176            HashSet<String> names = new HashSet<String>();
177            // copy over data.
178            for (ResourceBundle.VariableDeclaration variable : mBundle.getVariables()) {
179                addVariable(variable.name, variable.type, variable.location, variable.declared);
180                names.add(variable.name);
181            }
182
183            for (ResourceBundle.NameTypeLocation userImport : mBundle.getImports()) {
184                mExprModel.addImport(userImport.name, userImport.type, userImport.location);
185                names.add(userImport.name);
186            }
187            if (!names.contains("context")) {
188                mExprModel.builtInVariable("context", "android.content.Context",
189                        "getRoot().getContext()");
190                names.add("context");
191            }
192            for (String javaLangClass : sJavaLangClasses) {
193                mExprModel.addImport(javaLangClass, "java.lang." + javaLangClass, null);
194            }
195            // First resolve all the View fields
196            // Ensure there are no conflicts with variable names
197            for (BindingTargetBundle targetBundle : mBundle.getBindingTargetBundles()) {
198                try {
199                    Scope.enter(targetBundle);
200                    final BindingTarget bindingTarget = createBindingTarget(targetBundle);
201                    if (bindingTarget.getId() != null) {
202                        final String fieldName = LayoutBinderWriterKt.
203                                getReadableName(bindingTarget);
204                        if (names.contains(fieldName)) {
205                            L.w("View field %s collides with a variable or import", fieldName);
206                        } else {
207                            names.add(fieldName);
208                            mExprModel.viewFieldExpr(bindingTarget);
209                        }
210                    }
211                } finally {
212                    Scope.exit();
213                }
214            }
215
216            for (BindingTarget bindingTarget : mBindingTargets) {
217                try {
218                    Scope.enter(bindingTarget.mBundle);
219                    final String className = getPackage() + "." + getClassName();
220                    for (BindingTargetBundle.BindingBundle bindingBundle : bindingTarget.mBundle
221                            .getBindingBundleList()) {
222                        try {
223                            Scope.enter(bindingBundle.getValueLocation());
224                            Expr expr = parse(bindingBundle.getExpr(),
225                                    bindingBundle.getValueLocation(),
226                                    bindingTarget);
227                            bindingTarget.addBinding(bindingBundle.getName(), expr);
228                            if (bindingBundle.isTwoWay()) {
229                                bindingTarget.addInverseBinding(bindingBundle.getName(), expr,
230                                        className);
231                            }
232                        } finally {
233                            Scope.exit();
234                        }
235                    }
236                    // resolve callbacks first because they introduce local variables.
237                    bindingTarget.resolveCallbackParams();
238                    bindingTarget.resolveTwoWayExpressions();
239                    bindingTarget.resolveMultiSetters();
240                    bindingTarget.resolveListeners();
241                } finally {
242                    Scope.exit();
243                }
244            }
245            mSortedBindingTargets = new ArrayList<BindingTarget>(mBindingTargets);
246            Collections.sort(mSortedBindingTargets, COMPARE_FIELD_NAME);
247        } finally {
248            Scope.exit();
249        }
250    }
251
252    public void resolveWhichExpressionsAreUsed() {
253        List<Expr> used = new ArrayList<Expr>();
254        for (BindingTarget target : mBindingTargets) {
255            for (Binding binding : target.getBindings()) {
256                binding.getExpr().markAsUsed();
257                used.add(binding.getExpr());
258            }
259        }
260        while (!used.isEmpty()) {
261            Expr e = used.remove(used.size() - 1);
262            for (Dependency dep : e.getDependencies()) {
263                if (!dep.getOther().isUsed()) {
264                    used.add(dep.getOther());
265                    dep.getOther().markAsUsed();
266                }
267            }
268        }
269    }
270
271    public IdentifierExpr addVariable(String name, String type, Location location,
272            boolean declared) {
273        Preconditions.check(!mUserDefinedVariables.containsKey(name),
274                "%s has already been defined as %s", name, type);
275        final IdentifierExpr id = mExprModel.identifier(name);
276        id.setUserDefinedType(type);
277        id.enableDirectInvalidation();
278        if (location != null) {
279            id.addLocation(location);
280        }
281        mUserDefinedVariables.put(name, type);
282        if (declared) {
283            id.setDeclared();
284        }
285        return id;
286    }
287
288    public HashMap<String, String> getUserDefinedVariables() {
289        return mUserDefinedVariables;
290    }
291
292    public BindingTarget createBindingTarget(ResourceBundle.BindingTargetBundle targetBundle) {
293        final BindingTarget target = new BindingTarget(targetBundle);
294        mBindingTargets.add(target);
295        target.setModel(mExprModel);
296        return target;
297    }
298
299    public Expr parse(String input, @Nullable Location locationInFile, BindingTarget target) {
300        final Expr parsed = mExpressionParser.parse(input, locationInFile, target);
301        parsed.setBindingExpression(true);
302        return parsed;
303    }
304
305    public List<BindingTarget> getBindingTargets() {
306        return mBindingTargets;
307    }
308
309    public List<BindingTarget> getSortedTargets() {
310        return mSortedBindingTargets;
311    }
312
313    public boolean isEmpty() {
314        return mExprModel.size() == 0;
315    }
316
317    public ExprModel getModel() {
318        return mExprModel;
319    }
320
321    private void ensureWriter() {
322        if (mWriter == null) {
323            mWriter = new LayoutBinderWriter(this);
324        }
325    }
326
327    public void sealModel() {
328        mExprModel.seal();
329    }
330
331    public String writeViewBinderBaseClass(boolean forLibrary) {
332        ensureWriter();
333        return mWriter.writeBaseClass(forLibrary);
334    }
335
336    public String writeViewBinder(int minSdk) {
337        ensureWriter();
338        Preconditions.checkNotNull(getPackage(), "package cannot be null");
339        Preconditions.checkNotNull(getClassName(), "base class name cannot be null");
340        return mWriter.write(minSdk);
341    }
342
343    public String getPackage() {
344        return mBundle.getBindingClassPackage();
345    }
346
347    public boolean isMerge() {
348        return mBundle.isMerge();
349    }
350
351    public String getModulePackage() {
352        return mModulePackage;
353    }
354
355    public String getLayoutname() {
356        return mBundle.getFileName();
357    }
358
359    public String getImplementationName() {
360        if (hasVariations()) {
361            return mBundle.getBindingClassName() + mBundle.getConfigName() + "Impl";
362        } else {
363            return mBundle.getBindingClassName();
364        }
365    }
366
367    public String getClassName() {
368        return mBundle.getBindingClassName();
369    }
370
371    public String getTag() {
372        return mBundle.getDirectory() + "/" + mBundle.getFileName();
373    }
374
375    public boolean hasVariations() {
376        return mBundle.hasVariations();
377    }
378
379    @Override
380    public String provideScopeFilePath() {
381        return mBundle.getAbsoluteFilePath();
382    }
383}
384