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 */
16package android.databinding.tool.expr;
17
18import android.databinding.tool.BindingTarget;
19import android.databinding.tool.reflection.ModelAnalyzer;
20import android.databinding.tool.reflection.ModelClass;
21import android.databinding.tool.writer.KCode;
22import android.databinding.tool.writer.LayoutBinderWriterKt;
23
24import java.util.HashMap;
25import java.util.List;
26import java.util.Map;
27
28public class ResourceExpr extends Expr {
29
30    private final static Map<String, String> RESOURCE_TYPE_TO_R_OBJECT;
31    static {
32        RESOURCE_TYPE_TO_R_OBJECT = new HashMap<String, String>();
33        RESOURCE_TYPE_TO_R_OBJECT.put("colorStateList", "color  ");
34        RESOURCE_TYPE_TO_R_OBJECT.put("dimenOffset", "dimen  ");
35        RESOURCE_TYPE_TO_R_OBJECT.put("dimenSize", "dimen  ");
36        RESOURCE_TYPE_TO_R_OBJECT.put("intArray", "array  ");
37        RESOURCE_TYPE_TO_R_OBJECT.put("stateListAnimator", "animator  ");
38        RESOURCE_TYPE_TO_R_OBJECT.put("stringArray", "array  ");
39        RESOURCE_TYPE_TO_R_OBJECT.put("typedArray", "array");
40    }
41    // lazily initialized
42    private Map<String, ModelClass> mResourceToTypeMapping;
43
44    protected final String mPackage;
45
46    protected final String mResourceType;
47
48    protected final String mResourceId;
49
50    protected final BindingTarget mTarget;
51
52    public ResourceExpr(BindingTarget target, String packageName, String resourceType,
53            String resourceName, List<Expr> args) {
54        super(args);
55        mTarget = target;
56        if ("android".equals(packageName)) {
57            mPackage = "android.";
58        } else {
59            mPackage = "";
60        }
61        mResourceType = resourceType;
62        mResourceId = resourceName;
63    }
64
65    private Map<String, ModelClass> getResourceToTypeMapping(ModelAnalyzer modelAnalyzer) {
66        if (mResourceToTypeMapping == null) {
67            final Map<String, String> imports = getModel().getImports();
68            mResourceToTypeMapping = new HashMap<String, ModelClass>();
69            mResourceToTypeMapping.put("anim", modelAnalyzer.findClass("android.view.animation.Animation",
70                            imports));
71            mResourceToTypeMapping.put("animator", modelAnalyzer.findClass("android.animation.Animator",
72                            imports));
73            mResourceToTypeMapping.put("colorStateList",
74                            modelAnalyzer.findClass("android.content.res.ColorStateList",
75                                    imports));
76            mResourceToTypeMapping.put("drawable", modelAnalyzer.findClass("android.graphics.drawable.Drawable",
77                            imports));
78            mResourceToTypeMapping.put("stateListAnimator",
79                            modelAnalyzer.findClass("android.animation.StateListAnimator",
80                                    imports));
81            mResourceToTypeMapping.put("transition", modelAnalyzer.findClass("android.transition.Transition",
82                            imports));
83            mResourceToTypeMapping.put("typedArray", modelAnalyzer.findClass("android.content.res.TypedArray",
84                            imports));
85            mResourceToTypeMapping.put("interpolator",
86                            modelAnalyzer.findClass("android.view.animation.Interpolator", imports));
87            mResourceToTypeMapping.put("bool", modelAnalyzer.findClass(boolean.class));
88            mResourceToTypeMapping.put("color", modelAnalyzer.findClass(int.class));
89            mResourceToTypeMapping.put("dimenOffset", modelAnalyzer.findClass(int.class));
90            mResourceToTypeMapping.put("dimenSize", modelAnalyzer.findClass(int.class));
91            mResourceToTypeMapping.put("id", modelAnalyzer.findClass(int.class));
92            mResourceToTypeMapping.put("integer", modelAnalyzer.findClass(int.class));
93            mResourceToTypeMapping.put("layout", modelAnalyzer.findClass(int.class));
94            mResourceToTypeMapping.put("dimen", modelAnalyzer.findClass(float.class));
95            mResourceToTypeMapping.put("fraction", modelAnalyzer.findClass(float.class));
96            mResourceToTypeMapping.put("intArray", modelAnalyzer.findClass(int[].class));
97            mResourceToTypeMapping.put("string", modelAnalyzer.findClass(String.class));
98            mResourceToTypeMapping.put("stringArray", modelAnalyzer.findClass(String[].class));
99        }
100        return mResourceToTypeMapping;
101    }
102
103    @Override
104    protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) {
105        final Map<String, ModelClass> mapping = getResourceToTypeMapping(
106                modelAnalyzer);
107        final ModelClass modelClass = mapping.get(mResourceType);
108        if (modelClass != null) {
109            return modelClass;
110        }
111        if ("plurals".equals(mResourceType)) {
112            if (getChildren().isEmpty()) {
113                return modelAnalyzer.findClass(int.class);
114            } else {
115                return modelAnalyzer.findClass(String.class);
116            }
117        }
118        return modelAnalyzer.findClass(mResourceType, getModel().getImports());
119    }
120
121    @Override
122    protected List<Dependency> constructDependencies() {
123        return constructDynamicChildrenDependencies();
124    }
125
126    @Override
127    protected String computeUniqueKey() {
128        String base = toString();
129        String view = "";
130        if (requiresView()) {
131            view = LayoutBinderWriterKt.getFieldName(mTarget);
132        }
133        return join(base, view, computeChildrenKey());
134    }
135
136    @Override
137    protected KCode generateCode() {
138        return new KCode(toJava());
139    }
140
141    @Override
142    public Expr cloneToModel(ExprModel model) {
143        String pkg = mPackage.isEmpty() ? "" : "android";
144        return model.resourceExpr(mTarget, pkg, mResourceType, mResourceId,
145                cloneToModel(model, getChildren()));
146    }
147
148    public String getResourceId() {
149        return mPackage + "R." + getResourceObject() + "." + mResourceId;
150    }
151
152    @Override
153    public String getInvertibleError() {
154        return "Resources may not be the target of a two-way binding expression: " +
155                computeUniqueKey();
156    }
157
158    private boolean requiresView() {
159        return !mTarget.isBinder() && !("anim".equals(mResourceType) ||
160                "animator".equals(mResourceType) ||
161                "id".equals(mResourceType) ||
162                "interpolator".equals(mResourceType) ||
163                "layout".equals(mResourceType) ||
164                "stateListAnimator".equals(mResourceType) ||
165                "transition".equals(mResourceType));
166    }
167
168    public String toJava() {
169        final String context = "getRoot().getContext()";
170        final String viewName = requiresView() ? LayoutBinderWriterKt.getFieldName(mTarget) :
171                "getRoot()";
172        final String resources = viewName + ".getResources()";
173        final String resourceName = mPackage + "R." + getResourceObject() + "." + mResourceId;
174        if ("anim".equals(mResourceType)) return "android.view.animation.AnimationUtils.loadAnimation(" + context + ", " + resourceName + ")";
175        if ("animator".equals(mResourceType)) return "android.animation.AnimatorInflater.loadAnimator(" + context + ", " + resourceName + ")";
176        if ("bool".equals(mResourceType)) return resources + ".getBoolean(" + resourceName + ")";
177        if ("color".equals(mResourceType)) return "android.databinding.DynamicUtil.getColorFromResource(" + viewName + ", " + resourceName + ")";
178        if ("colorStateList".equals(mResourceType)) return "android.databinding.DynamicUtil.getColorStateListFromResource(" + viewName + ", " + resourceName + ")";
179        if ("dimen".equals(mResourceType)) return resources + ".getDimension(" + resourceName + ")";
180        if ("dimenOffset".equals(mResourceType)) return resources + ".getDimensionPixelOffset(" + resourceName + ")";
181        if ("dimenSize".equals(mResourceType)) return resources + ".getDimensionPixelSize(" + resourceName + ")";
182        if ("drawable".equals(mResourceType)) return "android.databinding.DynamicUtil.getDrawableFromResource(" + viewName + ", " + resourceName + ")";
183        if ("fraction".equals(mResourceType)) {
184            String base = getChildCode(0, "1");
185            String pbase = getChildCode(1, "1");
186            return resources + ".getFraction(" + resourceName + ", " + base + ", " + pbase +
187                    ")";
188        }
189        if ("id".equals(mResourceType)) return resourceName;
190        if ("intArray".equals(mResourceType)) return resources + ".getIntArray(" + resourceName + ")";
191        if ("integer".equals(mResourceType)) return resources + ".getInteger(" + resourceName + ")";
192        if ("interpolator".equals(mResourceType))  return "android.view.animation.AnimationUtils.loadInterpolator(" + context + ", " + resourceName + ")";
193        if ("layout".equals(mResourceType)) return resourceName;
194        if ("plurals".equals(mResourceType)) {
195            if (getChildren().isEmpty()) {
196                return resourceName;
197            } else {
198                return makeParameterCall(resources, resourceName, "getQuantityString");
199            }
200        }
201        if ("stateListAnimator".equals(mResourceType)) return "android.animation.AnimatorInflater.loadStateListAnimator(" + context + ", " + resourceName + ")";
202        if ("string".equals(mResourceType)) return makeParameterCall(resources, resourceName, "getString");
203        if ("stringArray".equals(mResourceType)) return resources + ".getStringArray(" + resourceName + ")";
204        if ("transition".equals(mResourceType)) return "android.transition.TransitionInflater.from(" + context + ").inflateTransition(" + resourceName + ")";
205        if ("typedArray".equals(mResourceType)) return resources + ".obtainTypedArray(" + resourceName + ")";
206        final String property = Character.toUpperCase(mResourceType.charAt(0)) +
207                mResourceType.substring(1);
208        return resources + ".get" + property + "(" + resourceName + ")";
209
210    }
211
212    private String getChildCode(int childIndex, String defaultValue) {
213        if (getChildren().size() <= childIndex) {
214            return defaultValue;
215        } else {
216            return getChildren().get(childIndex).toCode().generate();
217        }
218    }
219
220    private String makeParameterCall(String resources, String resourceName, String methodCall) {
221        StringBuilder sb = new StringBuilder(resources);
222        sb.append('.').append(methodCall).append("(").append(resourceName);
223        for (Expr expr : getChildren()) {
224            sb.append(", ").append(expr.toCode().generate());
225        }
226        sb.append(")");
227        return sb.toString();
228    }
229
230    private String getResourceObject() {
231        String rFileObject = RESOURCE_TYPE_TO_R_OBJECT.get(mResourceType);
232        if (rFileObject == null) {
233            rFileObject = mResourceType;
234        }
235        return rFileObject;
236    }
237
238    @Override
239    public String toString() {
240        if (mPackage == null) {
241            return "@" + mResourceType + "/" + mResourceId;
242        } else {
243            return "@" + "android:" + mResourceType + "/" + mResourceId;
244        }
245    }
246}
247