Home | History | Annotate | Download | only in java
      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 
     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     public ClassLoader getClassLoader() {
     70         return mClassLoader;
     71     }
     72 
     73     @Override
     74     protected ModelClass[] getObservableFieldTypes() {
     75         return new ModelClass[0];
     76     }
     77 
     78     @Override
     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
    115     public ModelClass findClass(Class classType) {
    116         return new JavaClass(classType);
    117     }
    118 
    119     @Override
    120     public TypeUtil createTypeUtil() {
    121         return new JavaTypeUtil();
    122     }
    123 
    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 
    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 
    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