197d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar/*
297d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar * Copyright (C) 2015 The Android Open Source Project
397d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar * Licensed under the Apache License, Version 2.0 (the "License");
497d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar * you may not use this file except in compliance with the License.
597d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar * You may obtain a copy of the License at
697d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar *      http://www.apache.org/licenses/LICENSE-2.0
797d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar * Unless required by applicable law or agreed to in writing, software
897d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar * distributed under the License is distributed on an "AS IS" BASIS,
997d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1097d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar * See the License for the specific language governing permissions and
1197d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar * limitations under the License.
1297d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar */
1397d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar
14fead9ca09b117136b35bc5bf137340a754f9edddGeorge Mountpackage android.databinding.tool.reflection.java;
1597d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar
164ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Guptaimport com.google.common.base.Splitter;
174ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Guptaimport com.google.common.base.Strings;
184ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta
19eebcbdd5d35e56a2c8ef37feeb65df46130d001dYigit Boyarimport org.apache.commons.io.FileUtils;
20eebcbdd5d35e56a2c8ef37feeb65df46130d001dYigit Boyar
21fead9ca09b117136b35bc5bf137340a754f9edddGeorge Mountimport android.databinding.tool.reflection.ModelAnalyzer;
22fead9ca09b117136b35bc5bf137340a754f9edddGeorge Mountimport android.databinding.tool.reflection.ModelClass;
23fead9ca09b117136b35bc5bf137340a754f9edddGeorge Mountimport android.databinding.tool.reflection.SdkUtil;
24fead9ca09b117136b35bc5bf137340a754f9edddGeorge Mountimport android.databinding.tool.reflection.TypeUtil;
25fead9ca09b117136b35bc5bf137340a754f9edddGeorge Mountimport android.databinding.tool.util.L;
2697d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar
2797d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyarimport java.io.File;
28eebcbdd5d35e56a2c8ef37feeb65df46130d001dYigit Boyarimport java.io.IOException;
2997d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyarimport java.net.MalformedURLException;
3097d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyarimport java.net.URL;
3197d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyarimport java.net.URLClassLoader;
3297d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyarimport java.util.HashMap;
334ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Guptaimport java.util.List;
3497d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyarimport java.util.Map;
3597d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar
3697d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyarpublic class JavaAnalyzer extends ModelAnalyzer {
372611838bffef5a009ca71e3e9e59a93f29b098edYigit Boyar    public static final Map<String, Class> PRIMITIVE_TYPES;
382611838bffef5a009ca71e3e9e59a93f29b098edYigit Boyar    static {
392611838bffef5a009ca71e3e9e59a93f29b098edYigit Boyar        PRIMITIVE_TYPES = new HashMap<String, Class>();
402611838bffef5a009ca71e3e9e59a93f29b098edYigit Boyar        PRIMITIVE_TYPES.put("boolean", boolean.class);
412611838bffef5a009ca71e3e9e59a93f29b098edYigit Boyar        PRIMITIVE_TYPES.put("byte", byte.class);
422611838bffef5a009ca71e3e9e59a93f29b098edYigit Boyar        PRIMITIVE_TYPES.put("short", short.class);
432611838bffef5a009ca71e3e9e59a93f29b098edYigit Boyar        PRIMITIVE_TYPES.put("char", char.class);
442611838bffef5a009ca71e3e9e59a93f29b098edYigit Boyar        PRIMITIVE_TYPES.put("int", int.class);
452611838bffef5a009ca71e3e9e59a93f29b098edYigit Boyar        PRIMITIVE_TYPES.put("long", long.class);
462611838bffef5a009ca71e3e9e59a93f29b098edYigit Boyar        PRIMITIVE_TYPES.put("float", float.class);
472611838bffef5a009ca71e3e9e59a93f29b098edYigit Boyar        PRIMITIVE_TYPES.put("double", double.class);
482611838bffef5a009ca71e3e9e59a93f29b098edYigit Boyar    }
4997d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar
50895b618d9c6e3deb56465d0759cda57f50c46214Yigit Boyar    private HashMap<String, JavaClass> mClassCache = new HashMap<String, JavaClass>();
5197d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar
5297d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar    private final ClassLoader mClassLoader;
5397d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar
54fa9fe12980ef1103fabe33bf5ff0e2f53042a204George Mount    public JavaAnalyzer(ClassLoader classLoader) {
5597d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar        setInstance(this);
5697d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar        mClassLoader = classLoader;
5797d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar    }
5897d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar
5997d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar    @Override
6097d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar    public JavaClass loadPrimitive(String className) {
6197d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar        Class clazz = PRIMITIVE_TYPES.get(className);
6297d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar        if (clazz == null) {
6397d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar            return null;
6497d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar        } else {
6597d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar            return new JavaClass(clazz);
6697d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar        }
6797d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar    }
6897d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar
6997d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar    @Override
70924fa7c597694ebc433fc0379d0015785351d1b7Yigit Boyar    protected ModelClass[] getObservableFieldTypes() {
71924fa7c597694ebc433fc0379d0015785351d1b7Yigit Boyar        return new ModelClass[0];
72924fa7c597694ebc433fc0379d0015785351d1b7Yigit Boyar    }
73924fa7c597694ebc433fc0379d0015785351d1b7Yigit Boyar
74924fa7c597694ebc433fc0379d0015785351d1b7Yigit Boyar    @Override
7597d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar    public ModelClass findClass(String className, Map<String, String> imports) {
7697d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar        // TODO handle imports
7797d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar        JavaClass loaded = mClassCache.get(className);
7897d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar        if (loaded != null) {
7997d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar            return loaded;
8097d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar        }
8197d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar        L.d("trying to load class %s from %s", className, mClassLoader.toString());
8297d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar        loaded = loadPrimitive(className);
8397d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar        if (loaded == null) {
8497d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar            try {
8597d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar                if (className.startsWith("[") && className.contains("L")) {
8697d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar                    int indexOfL = className.indexOf('L');
8797d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar                    JavaClass baseClass = (JavaClass) findClass(
8897d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar                            className.substring(indexOfL + 1, className.length() - 1), null);
8997d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar                    String realClassName = className.substring(0, indexOfL + 1) +
9097d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar                            baseClass.mClass.getCanonicalName() + ';';
9197d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar                    loaded = new JavaClass(Class.forName(realClassName, false, mClassLoader));
9297d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar                    mClassCache.put(className, loaded);
9397d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar                } else {
9497d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar                    loaded = loadRecursively(className);
9597d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar                    mClassCache.put(className, loaded);
9697d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar                }
9797d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar
9897d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar            } catch (Throwable t) {
9997d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar//                L.e(t, "cannot load class " + className);
10097d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar            }
10197d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar        }
10297d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar        // expr visitor may call this to resolve statics. Sometimes, it is OK not to find a class.
10397d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar        if (loaded == null) {
10497d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar            return null;
10597d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar        }
10697d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar        L.d("loaded class %s", loaded.mClass.getCanonicalName());
10797d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar        return loaded;
10897d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar    }
10997d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar
11097d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar    @Override
11197d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar    public ModelClass findClass(Class classType) {
11297d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar        return new JavaClass(classType);
11397d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar    }
11497d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar
11597d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar    @Override
11697d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar    public TypeUtil createTypeUtil() {
11797d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar        return new JavaTypeUtil();
11897d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar    }
11997d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar
12097d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar    private JavaClass loadRecursively(String className) throws ClassNotFoundException {
12197d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar        try {
12297d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar            L.d("recursively checking %s", className);
12397d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar            return new JavaClass(mClassLoader.loadClass(className));
12497d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar        } catch (ClassNotFoundException ex) {
12597d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar            int lastIndexOfDot = className.lastIndexOf(".");
12697d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar            if (lastIndexOfDot == -1) {
12797d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar                throw ex;
12897d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar            }
12997d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar            return loadRecursively(className.substring(0, lastIndexOfDot) + "$" + className
13097d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar                    .substring(lastIndexOfDot + 1));
13197d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar        }
13297d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar    }
13397d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar
134eebcbdd5d35e56a2c8ef37feeb65df46130d001dYigit Boyar    private static String loadAndroidHome() {
13597d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar        Map<String, String> env = System.getenv();
13697d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar        for (Map.Entry<String, String> entry : env.entrySet()) {
13797d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar            L.d("%s %s", entry.getKey(), entry.getValue());
13897d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar        }
139eebcbdd5d35e56a2c8ef37feeb65df46130d001dYigit Boyar        if(env.containsKey("ANDROID_HOME")) {
140eebcbdd5d35e56a2c8ef37feeb65df46130d001dYigit Boyar            return env.get("ANDROID_HOME");
141eebcbdd5d35e56a2c8ef37feeb65df46130d001dYigit Boyar        }
142eebcbdd5d35e56a2c8ef37feeb65df46130d001dYigit Boyar        // check for local.properties file
143eebcbdd5d35e56a2c8ef37feeb65df46130d001dYigit Boyar        File folder = new File(".").getAbsoluteFile();
144eebcbdd5d35e56a2c8ef37feeb65df46130d001dYigit Boyar        while (folder != null && folder.exists()) {
145eebcbdd5d35e56a2c8ef37feeb65df46130d001dYigit Boyar            File f = new File(folder, "local.properties");
146eebcbdd5d35e56a2c8ef37feeb65df46130d001dYigit Boyar            if (f.exists() && f.canRead()) {
147eebcbdd5d35e56a2c8ef37feeb65df46130d001dYigit Boyar                try {
148eebcbdd5d35e56a2c8ef37feeb65df46130d001dYigit Boyar                    for (String line : FileUtils.readLines(f)) {
1494ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta                        List<String> keyValue = Splitter.on('=').splitToList(line);
1504ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta                        if (keyValue.size() == 2) {
1514ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta                            String key = keyValue.get(0).trim();
152eebcbdd5d35e56a2c8ef37feeb65df46130d001dYigit Boyar                            if (key.equals("sdk.dir")) {
1534ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta                                return keyValue.get(1).trim();
154eebcbdd5d35e56a2c8ef37feeb65df46130d001dYigit Boyar                            }
155eebcbdd5d35e56a2c8ef37feeb65df46130d001dYigit Boyar                        }
156eebcbdd5d35e56a2c8ef37feeb65df46130d001dYigit Boyar                    }
157eebcbdd5d35e56a2c8ef37feeb65df46130d001dYigit Boyar                } catch (IOException ignored) {}
158eebcbdd5d35e56a2c8ef37feeb65df46130d001dYigit Boyar            }
159eebcbdd5d35e56a2c8ef37feeb65df46130d001dYigit Boyar            folder = folder.getParentFile();
160eebcbdd5d35e56a2c8ef37feeb65df46130d001dYigit Boyar        }
161eebcbdd5d35e56a2c8ef37feeb65df46130d001dYigit Boyar
162eebcbdd5d35e56a2c8ef37feeb65df46130d001dYigit Boyar        return null;
163eebcbdd5d35e56a2c8ef37feeb65df46130d001dYigit Boyar    }
164eebcbdd5d35e56a2c8ef37feeb65df46130d001dYigit Boyar
165eebcbdd5d35e56a2c8ef37feeb65df46130d001dYigit Boyar    public static void initForTests() {
166eebcbdd5d35e56a2c8ef37feeb65df46130d001dYigit Boyar        String androidHome = loadAndroidHome();
1674ba16229a40e9758db86d4fb1df5119fdcb8aa2aDeepanshu Gupta        if (Strings.isNullOrEmpty(androidHome) || !new File(androidHome).exists()) {
168fa9fe12980ef1103fabe33bf5ff0e2f53042a204George Mount            throw new IllegalStateException(
169fa9fe12980ef1103fabe33bf5ff0e2f53042a204George Mount                    "you need to have ANDROID_HOME set in your environment"
170fa9fe12980ef1103fabe33bf5ff0e2f53042a204George Mount                            + " to run compiler tests");
17197d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar        }
17297d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar        File androidJar = new File(androidHome + "/platforms/android-21/android.jar");
17397d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar        if (!androidJar.exists() || !androidJar.canRead()) {
17497d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar            throw new IllegalStateException(
17597d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar                    "cannot find android jar at " + androidJar.getAbsolutePath());
17697d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar        }
17797d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar        // now load android data binding library as well
17897d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar
17997d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar        try {
18097d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar            ClassLoader classLoader = new URLClassLoader(new URL[]{androidJar.toURI().toURL()},
18197d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar                    ModelAnalyzer.class.getClassLoader());
182fa9fe12980ef1103fabe33bf5ff0e2f53042a204George Mount            new JavaAnalyzer(classLoader);
18397d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar        } catch (MalformedURLException e) {
18497d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar            throw new RuntimeException("cannot create class loader", e);
18597d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar        }
18697d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar
18797d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar        SdkUtil.initialize(8, new File(androidHome));
18897d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar    }
18997d6ddf47f4ff1abb3ed5201ce5232163f5325b1Yigit Boyar}
190