1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *      http://www.apache.org/licenses/LICENSE-2.0
7 * Unless required by applicable law or agreed to in writing, software
8 * distributed under the License is distributed on an "AS IS" BASIS,
9 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 * See the License for the specific language governing permissions and
11 * limitations under the License.
12 */
13
14package android.databinding.tool.reflection.java;
15
16import android.databinding.tool.reflection.ModelAnalyzer;
17import android.databinding.tool.reflection.ModelClass;
18import android.databinding.tool.reflection.SdkUtil;
19import android.databinding.tool.reflection.TypeUtil;
20import android.databinding.tool.util.L;
21
22import java.io.File;
23import java.net.MalformedURLException;
24import java.net.URL;
25import java.net.URLClassLoader;
26import java.util.HashMap;
27import java.util.Map;
28
29public class JavaAnalyzer extends ModelAnalyzer {
30    public static final Map<String, Class> PRIMITIVE_TYPES;
31    static {
32        PRIMITIVE_TYPES = new HashMap<String, Class>();
33        PRIMITIVE_TYPES.put("boolean", boolean.class);
34        PRIMITIVE_TYPES.put("byte", byte.class);
35        PRIMITIVE_TYPES.put("short", short.class);
36        PRIMITIVE_TYPES.put("char", char.class);
37        PRIMITIVE_TYPES.put("int", int.class);
38        PRIMITIVE_TYPES.put("long", long.class);
39        PRIMITIVE_TYPES.put("float", float.class);
40        PRIMITIVE_TYPES.put("double", double.class);
41    }
42
43    private HashMap<String, JavaClass> mClassCache = new HashMap<String, JavaClass>();
44
45    private final ClassLoader mClassLoader;
46
47    public JavaAnalyzer(ClassLoader classLoader) {
48        setInstance(this);
49        mClassLoader = classLoader;
50    }
51
52    @Override
53    public JavaClass loadPrimitive(String className) {
54        Class clazz = PRIMITIVE_TYPES.get(className);
55        if (clazz == null) {
56            return null;
57        } else {
58            return new JavaClass(clazz);
59        }
60    }
61
62    @Override
63    protected ModelClass[] getObservableFieldTypes() {
64        return new ModelClass[0];
65    }
66
67    @Override
68    public ModelClass findClass(String className, Map<String, String> imports) {
69        // TODO handle imports
70        JavaClass loaded = mClassCache.get(className);
71        if (loaded != null) {
72            return loaded;
73        }
74        L.d("trying to load class %s from %s", className, mClassLoader.toString());
75        loaded = loadPrimitive(className);
76        if (loaded == null) {
77            try {
78                if (className.startsWith("[") && className.contains("L")) {
79                    int indexOfL = className.indexOf('L');
80                    JavaClass baseClass = (JavaClass) findClass(
81                            className.substring(indexOfL + 1, className.length() - 1), null);
82                    String realClassName = className.substring(0, indexOfL + 1) +
83                            baseClass.mClass.getCanonicalName() + ';';
84                    loaded = new JavaClass(Class.forName(realClassName, false, mClassLoader));
85                    mClassCache.put(className, loaded);
86                } else {
87                    loaded = loadRecursively(className);
88                    mClassCache.put(className, loaded);
89                }
90
91            } catch (Throwable t) {
92//                L.e(t, "cannot load class " + className);
93            }
94        }
95        // expr visitor may call this to resolve statics. Sometimes, it is OK not to find a class.
96        if (loaded == null) {
97            return null;
98        }
99        L.d("loaded class %s", loaded.mClass.getCanonicalName());
100        return loaded;
101    }
102
103    @Override
104    public ModelClass findClass(Class classType) {
105        return new JavaClass(classType);
106    }
107
108    @Override
109    public TypeUtil createTypeUtil() {
110        return new JavaTypeUtil();
111    }
112
113    private JavaClass loadRecursively(String className) throws ClassNotFoundException {
114        try {
115            L.d("recursively checking %s", className);
116            return new JavaClass(mClassLoader.loadClass(className));
117        } catch (ClassNotFoundException ex) {
118            int lastIndexOfDot = className.lastIndexOf(".");
119            if (lastIndexOfDot == -1) {
120                throw ex;
121            }
122            return loadRecursively(className.substring(0, lastIndexOfDot) + "$" + className
123                    .substring(lastIndexOfDot + 1));
124        }
125    }
126
127    public static void initForTests() {
128        Map<String, String> env = System.getenv();
129        for (Map.Entry<String, String> entry : env.entrySet()) {
130            L.d("%s %s", entry.getKey(), entry.getValue());
131        }
132        String androidHome = env.get("ANDROID_HOME");
133        if (androidHome == null) {
134            throw new IllegalStateException(
135                    "you need to have ANDROID_HOME set in your environment"
136                            + " to run compiler tests");
137        }
138        File androidJar = new File(androidHome + "/platforms/android-21/android.jar");
139        if (!androidJar.exists() || !androidJar.canRead()) {
140            throw new IllegalStateException(
141                    "cannot find android jar at " + androidJar.getAbsolutePath());
142        }
143        // now load android data binding library as well
144
145        try {
146            ClassLoader classLoader = new URLClassLoader(new URL[]{androidJar.toURI().toURL()},
147                    ModelAnalyzer.class.getClassLoader());
148            new JavaAnalyzer(classLoader);
149        } catch (MalformedURLException e) {
150            throw new RuntimeException("cannot create class loader", e);
151        }
152
153        SdkUtil.initialize(8, new File(androidHome));
154    }
155}
156