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.reflection;
17
18import android.databinding.tool.reflection.annotation.AnnotationAnalyzer;
19import android.databinding.tool.util.L;
20import android.databinding.tool.util.Preconditions;
21
22import java.util.Map;
23
24import javax.annotation.processing.ProcessingEnvironment;
25
26/**
27 * This is the base class for several implementations of something that
28 * acts like a ClassLoader. Different implementations work with the Annotation
29 * Processor, ClassLoader, and an Android Studio plugin.
30 */
31public abstract class ModelAnalyzer {
32
33    public static final String[] LIST_CLASS_NAMES = {
34            "java.util.List",
35            "android.util.SparseArray",
36            "android.util.SparseBooleanArray",
37            "android.util.SparseIntArray",
38            "android.util.SparseLongArray",
39            "android.util.LongSparseArray",
40            "android.support.v4.util.LongSparseArray",
41    };
42
43    public static final String MAP_CLASS_NAME = "java.util.Map";
44
45    public static final String STRING_CLASS_NAME = "java.lang.String";
46
47    public static final String OBJECT_CLASS_NAME = "java.lang.Object";
48
49    public static final String OBSERVABLE_CLASS_NAME = "android.databinding.Observable";
50
51    public static final String OBSERVABLE_LIST_CLASS_NAME = "android.databinding.ObservableList";
52
53    public static final String OBSERVABLE_MAP_CLASS_NAME = "android.databinding.ObservableMap";
54
55    public static final String[] OBSERVABLE_FIELDS = {
56            "android.databinding.ObservableBoolean",
57            "android.databinding.ObservableByte",
58            "android.databinding.ObservableChar",
59            "android.databinding.ObservableShort",
60            "android.databinding.ObservableInt",
61            "android.databinding.ObservableLong",
62            "android.databinding.ObservableFloat",
63            "android.databinding.ObservableDouble",
64            "android.databinding.ObservableField",
65            "android.databinding.ObservableParcelable",
66    };
67
68    public static final String VIEW_DATA_BINDING =
69            "android.databinding.ViewDataBinding";
70
71    public static final String VIEW_STUB_CLASS_NAME = "android.view.ViewStub";
72
73    private ModelClass[] mListTypes;
74    private ModelClass mMapType;
75    private ModelClass mStringType;
76    private ModelClass mObjectType;
77    private ModelClass mObservableType;
78    private ModelClass mObservableListType;
79    private ModelClass mObservableMapType;
80    private ModelClass[] mObservableFieldTypes;
81    private ModelClass mViewBindingType;
82    private ModelClass mViewStubType;
83
84    private static ModelAnalyzer sAnalyzer;
85
86    protected void setInstance(ModelAnalyzer analyzer) {
87        sAnalyzer = analyzer;
88    }
89
90    public ModelClass findCommonParentOf(ModelClass modelClass1,
91            ModelClass modelClass2) {
92        ModelClass curr = modelClass1;
93        while (curr != null && !curr.isAssignableFrom(modelClass2)) {
94            curr = curr.getSuperclass();
95        }
96        if (curr == null) {
97            if (modelClass1.isObject() && modelClass2.isInterface()) {
98                return modelClass1;
99            } else if (modelClass2.isObject() && modelClass1.isInterface()) {
100                return modelClass2;
101            }
102
103            ModelClass primitive1 = modelClass1.unbox();
104            ModelClass primitive2 = modelClass2.unbox();
105            if (!modelClass1.equals(primitive1) || !modelClass2.equals(primitive2)) {
106                return findCommonParentOf(primitive1, primitive2);
107            }
108        }
109        Preconditions.checkNotNull(curr,
110                "must be able to find a common parent for " + modelClass1 + " and " + modelClass2);
111        return curr;
112
113    }
114
115    public abstract ModelClass loadPrimitive(String className);
116
117    public static ModelAnalyzer getInstance() {
118        return sAnalyzer;
119    }
120
121    public static void setProcessingEnvironment(ProcessingEnvironment processingEnvironment) {
122        if (sAnalyzer != null) {
123            throw new IllegalStateException("processing env is already created, you cannot "
124                    + "change class loader after that");
125        }
126        L.d("setting processing env to %s", processingEnvironment);
127        AnnotationAnalyzer annotationAnalyzer = new AnnotationAnalyzer(processingEnvironment);
128        sAnalyzer = annotationAnalyzer;
129    }
130
131    /**
132     * Takes a raw className (potentially w/ generics and arrays) and expands definitions using
133     * the import statements.
134     * <p>
135     * For instance, this allows user to define variables
136     * <variable type="User" name="user"/>
137     * if they previously imported User.
138     * <import name="com.example.User"/>
139     */
140    public String applyImports(String className, Map<String, String> imports) {
141        className = className.trim();
142        int numDimensions = 0;
143        String generic = null;
144        // handle array
145        while (className.endsWith("[]")) {
146            numDimensions++;
147            className = className.substring(0, className.length() - 2);
148        }
149        // handle generics
150        final int lastCharIndex = className.length() - 1;
151        if ('>' == className.charAt(lastCharIndex)) {
152            // has generic.
153            int open = className.indexOf('<');
154            if (open == -1) {
155                L.e("un-matching generic syntax for %s", className);
156                return className;
157            }
158            generic = applyImports(className.substring(open + 1, lastCharIndex), imports);
159            className = className.substring(0, open);
160        }
161        int dotIndex = className.indexOf('.');
162        final String qualifier;
163        final String rest;
164        if (dotIndex == -1) {
165            qualifier = className;
166            rest = null;
167        } else {
168            qualifier = className.substring(0, dotIndex);
169            rest = className.substring(dotIndex); // includes dot
170        }
171        final String expandedQualifier = imports.get(qualifier);
172        String result;
173        if (expandedQualifier != null) {
174            result = rest == null ? expandedQualifier : expandedQualifier + rest;
175        } else {
176            result = className; // no change
177        }
178        // now append back dimension and generics
179        if (generic != null) {
180            result = result + "<" + applyImports(generic, imports) + ">";
181        }
182        while (numDimensions-- > 0) {
183            result = result + "[]";
184        }
185        return result;
186    }
187
188    public String getDefaultValue(String className) {
189        if ("int".equals(className)) {
190            return "0";
191        }
192        if ("short".equals(className)) {
193            return "0";
194        }
195        if ("long".equals(className)) {
196            return "0L";
197        }
198        if ("float".equals(className)) {
199            return "0f";
200        }
201        if ("double".equals(className)) {
202            return "0.0";
203        }
204        if ("boolean".equals(className)) {
205            return "false";
206        }
207        if ("char".equals(className)) {
208            return "'\\u0000'";
209        }
210        if ("byte".equals(className)) {
211            return "0";
212        }
213        return "null";
214    }
215
216    public abstract ModelClass findClass(String className, Map<String, String> imports);
217
218    public abstract ModelClass findClass(Class classType);
219
220    public abstract TypeUtil createTypeUtil();
221
222    ModelClass[] getListTypes() {
223        if (mListTypes == null) {
224            mListTypes = new ModelClass[LIST_CLASS_NAMES.length];
225            for (int i = 0; i < mListTypes.length; i++) {
226                final ModelClass modelClass = findClass(LIST_CLASS_NAMES[i], null);
227                if (modelClass != null) {
228                    mListTypes[i] = modelClass.erasure();
229                }
230            }
231        }
232        return mListTypes;
233    }
234
235    public ModelClass getMapType() {
236        if (mMapType == null) {
237            mMapType = loadClassErasure(MAP_CLASS_NAME);
238        }
239        return mMapType;
240    }
241
242    ModelClass getStringType() {
243        if (mStringType == null) {
244            mStringType = findClass(STRING_CLASS_NAME, null);
245        }
246        return mStringType;
247    }
248
249    ModelClass getObjectType() {
250        if (mObjectType == null) {
251            mObjectType = findClass(OBJECT_CLASS_NAME, null);
252        }
253        return mObjectType;
254    }
255
256    ModelClass getObservableType() {
257        if (mObservableType == null) {
258            mObservableType = findClass(OBSERVABLE_CLASS_NAME, null);
259        }
260        return mObservableType;
261    }
262
263    ModelClass getObservableListType() {
264        if (mObservableListType == null) {
265            mObservableListType = loadClassErasure(OBSERVABLE_LIST_CLASS_NAME);
266        }
267        return mObservableListType;
268    }
269
270    ModelClass getObservableMapType() {
271        if (mObservableMapType == null) {
272            mObservableMapType = loadClassErasure(OBSERVABLE_MAP_CLASS_NAME);
273        }
274        return mObservableMapType;
275    }
276
277    ModelClass getViewDataBindingType() {
278        if (mViewBindingType == null) {
279            mViewBindingType = findClass(VIEW_DATA_BINDING, null);
280        }
281        return mViewBindingType;
282    }
283
284    protected ModelClass[] getObservableFieldTypes() {
285        if (mObservableFieldTypes == null) {
286            mObservableFieldTypes = new ModelClass[OBSERVABLE_FIELDS.length];
287            for (int i = 0; i < OBSERVABLE_FIELDS.length; i++) {
288                mObservableFieldTypes[i] = loadClassErasure(OBSERVABLE_FIELDS[i]);
289            }
290        }
291        return mObservableFieldTypes;
292    }
293
294    ModelClass getViewStubType() {
295        if (mViewStubType == null) {
296            mViewStubType = findClass(VIEW_STUB_CLASS_NAME, null);
297        }
298        return mViewStubType;
299    }
300
301    private ModelClass loadClassErasure(String className) {
302        return findClass(className, null).erasure();
303    }
304}
305