Home | History | Annotate | Download | only in littlemock
      1 /*
      2  * Copyright (C) 2011 Google Inc.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
      5  * use this file except in compliance with the License. You may obtain a copy of
      6  * the License at
      7  *
      8  * http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
     12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
     13  * License for the specific language governing permissions and limitations under
     14  * the License.
     15  */
     16 
     17 package com.google.testing.littlemock;
     18 
     19 import java.io.File;
     20 import java.lang.reflect.Field;
     21 import java.util.ArrayList;
     22 import java.util.List;
     23 
     24 /**
     25  * Utility class for helping guess the application data directory.
     26  */
     27 public class AppDataDirGuesser {
     28 
     29   /** A single default instance of app data dir guesser, for overriding if you really need to. */
     30   private static volatile AppDataDirGuesser sInjectableInstance = new AppDataDirGuesser();
     31 
     32   public static void setInstance(AppDataDirGuesser instance) {
     33     sInjectableInstance = instance;
     34   }
     35 
     36   public static AppDataDirGuesser getsInstance() {
     37     return sInjectableInstance;
     38   }
     39 
     40   public File guessSuitableDirectoryForGeneratedClasses() {
     41     try {
     42       ClassLoader classLoader = AppDataDirGuesser.class.getClassLoader();
     43       // Check that we have an instance of the PathClassLoader.
     44       Class<?> clazz = Class.forName("dalvik.system.PathClassLoader");
     45       clazz.cast(classLoader);
     46       // Use the toString() method to calculate the data directory.
     47       String pathFromThisClassLoader =
     48           getPathFromPathClassLoader(classLoader, clazz);
     49       File[] results = guessPath(pathFromThisClassLoader);
     50       if (results.length > 0) {
     51         return results[0];
     52       }
     53     } catch (ClassCastException e) {
     54       // Fall through, we will return null.
     55     } catch (ClassNotFoundException e) {
     56       // Fall through, we will return null.
     57     }
     58     return null;
     59   }
     60 
     61   private String getPathFromPathClassLoader(
     62       ClassLoader classLoader, Class<?> pathClassLoaderClass) {
     63     // Prior to ICS, we can simply read the "path" field of the
     64     // PathClassLoader.
     65     try {
     66       Field pathField = pathClassLoaderClass.getDeclaredField("path");
     67       pathField.setAccessible(true);
     68       return (String) pathField.get(classLoader);
     69     } catch (NoSuchFieldException e) {
     70       // Ignore and fall back on parsing the output of toString below
     71     } catch (IllegalAccessException e) {
     72       // Ignore and fall back on parsing the output of toString below
     73     } catch (ClassCastException e) {
     74       // Ignore and fall back on parsing the output of toString below
     75     }
     76 
     77     // Parsing toString() method: yuck.  But no other way to get the path.
     78     // Strip out the bit between square brackets, that's our path.
     79     String result = classLoader.toString();
     80     int index = result.lastIndexOf('[');
     81     result = (index == -1) ? result : result.substring(index + 1);
     82     index = result.indexOf(']');
     83     return (index == -1) ? result : result.substring(0, index);
     84   }
     85 
     86   // @VisibleForTesting
     87   File[] guessPath(String input) {
     88     List<File> results = new ArrayList<File>();
     89     for (String potential : splitPathList(input)) {
     90       if (!potential.startsWith("/data/app/")) {
     91         continue;
     92       }
     93       int start = "/data/app/".length();
     94       int end = potential.lastIndexOf(".apk");
     95       if (end != potential.length() - 4) {
     96         continue;
     97       }
     98       int dash = potential.indexOf("-");
     99       if (dash != -1) {
    100         end = dash;
    101       }
    102       String packageName = potential.substring(start, end);
    103       File dataDir = new File("/data/data/" + packageName);
    104       if (isWriteableDirectory(dataDir)) {
    105         File cacheDir = new File(dataDir, "cache");
    106         if (fileOrDirExists(cacheDir) || makeDirectory(cacheDir)) {
    107           if (isWriteableDirectory(cacheDir)) {
    108             results.add(cacheDir);
    109           }
    110         }
    111       }
    112     }
    113 
    114     return results.toArray(new File[results.size()]);
    115   }
    116 
    117   // @VisibleForTesting
    118   static String[] splitPathList(String input) {
    119     String trimmed = input;
    120     if (input.startsWith("dexPath=")) {
    121       int start = "dexPath=".length();
    122       int end = input.indexOf(',');
    123 
    124       trimmed = (end == -1) ? input.substring(start) :
    125           input.substring(start, end);
    126     }
    127 
    128     return trimmed.split(":");
    129   }
    130 
    131   // @VisibleForTesting
    132   boolean fileOrDirExists(File file) {
    133       return file.exists();
    134   }
    135 
    136   // @VisibleForTesting
    137   boolean makeDirectory(File file) {
    138       return file.mkdir();
    139   }
    140 
    141   // @VisibleForTesting
    142   boolean isWriteableDirectory(File file) {
    143     return file.isDirectory() && file.canWrite();
    144   }
    145 }
    146