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