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 com.google.common.base.Splitter;
17import com.google.common.base.Strings;
18
19import org.apache.commons.io.FileUtils;
20
21import android.databinding.tool.reflection.ModelAnalyzer;
22import android.databinding.tool.reflection.ModelClass;
23import android.databinding.tool.reflection.SdkUtil;
24import android.databinding.tool.reflection.TypeUtil;
25import android.databinding.tool.util.L;
26
27import java.io.File;
28import java.io.IOException;
29import java.net.MalformedURLException;
30import java.net.URL;
31import java.net.URLClassLoader;
32import java.util.HashMap;
33import java.util.List;
34import java.util.Map;
35
36public class JavaAnalyzer extends ModelAnalyzer {
37    public static final Map<String, Class> PRIMITIVE_TYPES;
38    static {
39        PRIMITIVE_TYPES = new HashMap<String, Class>();
40        PRIMITIVE_TYPES.put("boolean", boolean.class);
41        PRIMITIVE_TYPES.put("byte", byte.class);
42        PRIMITIVE_TYPES.put("short", short.class);
43        PRIMITIVE_TYPES.put("char", char.class);
44        PRIMITIVE_TYPES.put("int", int.class);
45        PRIMITIVE_TYPES.put("long", long.class);
46        PRIMITIVE_TYPES.put("float", float.class);
47        PRIMITIVE_TYPES.put("double", double.class);
48    }
49
50    private HashMap<String, JavaClass> mClassCache = new HashMap<String, JavaClass>();
51
52    private final ClassLoader mClassLoader;
53
54    public JavaAnalyzer(ClassLoader classLoader) {
55        setInstance(this);
56        mClassLoader = classLoader;
57    }
58
59    @Override
60    public JavaClass loadPrimitive(String className) {
61        Class clazz = PRIMITIVE_TYPES.get(className);
62        if (clazz == null) {
63            return null;
64        } else {
65            return new JavaClass(clazz);
66        }
67    }
68
69    @Override
70    protected ModelClass[] getObservableFieldTypes() {
71        return new ModelClass[0];
72    }
73
74    @Override
75    public ModelClass findClass(String className, Map<String, String> imports) {
76        // TODO handle imports
77        JavaClass loaded = mClassCache.get(className);
78        if (loaded != null) {
79            return loaded;
80        }
81        L.d("trying to load class %s from %s", className, mClassLoader.toString());
82        loaded = loadPrimitive(className);
83        if (loaded == null) {
84            try {
85                if (className.startsWith("[") && className.contains("L")) {
86                    int indexOfL = className.indexOf('L');
87                    JavaClass baseClass = (JavaClass) findClass(
88                            className.substring(indexOfL + 1, className.length() - 1), null);
89                    String realClassName = className.substring(0, indexOfL + 1) +
90                            baseClass.mClass.getCanonicalName() + ';';
91                    loaded = new JavaClass(Class.forName(realClassName, false, mClassLoader));
92                    mClassCache.put(className, loaded);
93                } else {
94                    loaded = loadRecursively(className);
95                    mClassCache.put(className, loaded);
96                }
97
98            } catch (Throwable t) {
99//                L.e(t, "cannot load class " + className);
100            }
101        }
102        // expr visitor may call this to resolve statics. Sometimes, it is OK not to find a class.
103        if (loaded == null) {
104            return null;
105        }
106        L.d("loaded class %s", loaded.mClass.getCanonicalName());
107        return loaded;
108    }
109
110    @Override
111    public ModelClass findClass(Class classType) {
112        return new JavaClass(classType);
113    }
114
115    @Override
116    public TypeUtil createTypeUtil() {
117        return new JavaTypeUtil();
118    }
119
120    private JavaClass loadRecursively(String className) throws ClassNotFoundException {
121        try {
122            L.d("recursively checking %s", className);
123            return new JavaClass(mClassLoader.loadClass(className));
124        } catch (ClassNotFoundException ex) {
125            int lastIndexOfDot = className.lastIndexOf(".");
126            if (lastIndexOfDot == -1) {
127                throw ex;
128            }
129            return loadRecursively(className.substring(0, lastIndexOfDot) + "$" + className
130                    .substring(lastIndexOfDot + 1));
131        }
132    }
133
134    private static String loadAndroidHome() {
135        Map<String, String> env = System.getenv();
136        for (Map.Entry<String, String> entry : env.entrySet()) {
137            L.d("%s %s", entry.getKey(), entry.getValue());
138        }
139        if(env.containsKey("ANDROID_HOME")) {
140            return env.get("ANDROID_HOME");
141        }
142        // check for local.properties file
143        File folder = new File(".").getAbsoluteFile();
144        while (folder != null && folder.exists()) {
145            File f = new File(folder, "local.properties");
146            if (f.exists() && f.canRead()) {
147                try {
148                    for (String line : FileUtils.readLines(f)) {
149                        List<String> keyValue = Splitter.on('=').splitToList(line);
150                        if (keyValue.size() == 2) {
151                            String key = keyValue.get(0).trim();
152                            if (key.equals("sdk.dir")) {
153                                return keyValue.get(1).trim();
154                            }
155                        }
156                    }
157                } catch (IOException ignored) {}
158            }
159            folder = folder.getParentFile();
160        }
161
162        return null;
163    }
164
165    public static void initForTests() {
166        String androidHome = loadAndroidHome();
167        if (Strings.isNullOrEmpty(androidHome) || !new File(androidHome).exists()) {
168            throw new IllegalStateException(
169                    "you need to have ANDROID_HOME set in your environment"
170                            + " to run compiler tests");
171        }
172        File androidJar = new File(androidHome + "/platforms/android-21/android.jar");
173        if (!androidJar.exists() || !androidJar.canRead()) {
174            throw new IllegalStateException(
175                    "cannot find android jar at " + androidJar.getAbsolutePath());
176        }
177        // now load android data binding library as well
178
179        try {
180            ClassLoader classLoader = new URLClassLoader(new URL[]{androidJar.toURI().toURL()},
181                    ModelAnalyzer.class.getClassLoader());
182            new JavaAnalyzer(classLoader);
183        } catch (MalformedURLException e) {
184            throw new RuntimeException("cannot create class loader", e);
185        }
186
187        SdkUtil.initialize(8, new File(androidHome));
188    }
189}
190