Codebase list mozc / debian/1.12.1599.102-1 android / patches / ProxyBuilder.java.diff
debian/1.12.1599.102-1

Tree @debian/1.12.1599.102-1 (Download .tar.gz)

ProxyBuilder.java.diff @debian/1.12.1599.102-1raw · history · blame

--- a/ProxyBuilder.java
+++ b/ProxyBuilder.java
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
-package com.google.dexmaker.stock;
+package org.mozc.android.inputmethod.japanese.testing.mocking;
 
+import org.mozc.android.inputmethod.japanese.testing.VisibilityProxy;
 import com.google.dexmaker.Code;
 import com.google.dexmaker.Comparison;
 import com.google.dexmaker.DexMaker;
@@ -24,7 +25,15 @@
 import com.google.dexmaker.Local;
 import com.google.dexmaker.MethodId;
 import com.google.dexmaker.TypeId;
+import com.google.dexmaker.dx.dex.DexFormat;
+
+import dalvik.system.DexClassLoader;
+import dalvik.system.DexFile;
+
+import junit.framework.AssertionFailedError;
+
 import java.io.File;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
@@ -40,9 +49,12 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
 
 /**
  * Creates dynamic proxies of concrete classes.
@@ -234,7 +246,7 @@
         @SuppressWarnings("unchecked") // we only populate the map with matching types
         Class<? extends T> proxyClass = (Class) generatedProxyClasses.get(baseClass);
         if (proxyClass != null
-                && proxyClass.getClassLoader().getParent() == parentClassLoader
+                && proxyClass.getClassLoader() == parentClassLoader
                 && interfaces.equals(asSet(proxyClass.getInterfaces()))) {
             return proxyClass; // cache hit!
         }
@@ -244,21 +256,19 @@
         String generatedName = getMethodNameForProxyOf(baseClass);
         TypeId<? extends T> generatedType = TypeId.get("L" + generatedName + ";");
         TypeId<T> superType = TypeId.get(baseClass);
-        generateConstructorsAndFields(dexMaker, generatedType, superType, baseClass);
+        generateConstructorsAndFields(
+            dexMaker, generatedType, superType, baseClass, parentClassLoader);
         Method[] methodsToProxy = getMethodsToProxyRecursive();
         generateCodeForAllMethods(dexMaker, generatedType, methodsToProxy, superType);
         dexMaker.declare(generatedType, generatedName + ".generated", PUBLIC, superType,
                 getInterfacesAsTypeIds());
-        ClassLoader classLoader = dexMaker.generateAndLoad(parentClassLoader, dexCache);
+
         try {
-            proxyClass = loadClass(classLoader, generatedName);
+            proxyClass = loadClass(parentClassLoader, generatedName, dexMaker.generate(), dexCache);
         } catch (IllegalAccessError e) {
             // Thrown when the base class is not accessible.
             throw new UnsupportedOperationException(
                     "cannot proxy inaccessible class " + baseClass, e);
-        } catch (ClassNotFoundException e) {
-            // Should not be thrown, we're sure to have generated this class.
-            throw new AssertionError(e);
         }
         setMethodsStaticField(proxyClass, methodsToProxy);
         generatedProxyClasses.put(baseClass, proxyClass);
@@ -267,9 +277,43 @@
 
     // The type cast is safe: the generated type will extend the base class type.
     @SuppressWarnings("unchecked")
-    private Class<? extends T> loadClass(ClassLoader classLoader, String generatedName)
-            throws ClassNotFoundException {
-        return (Class<? extends T>) classLoader.loadClass(generatedName);
+    private Class<? extends T> loadClass(
+            ClassLoader classLoader, String generatedName, byte[] dex, File dexCache)
+            throws IOException {
+        File result = File.createTempFile("Generated", ".jar", dexCache);
+        result.deleteOnExit();
+        JarOutputStream jarOut = new JarOutputStream(new FileOutputStream(result));
+        jarOut.putNextEntry(new JarEntry(DexFormat.DEX_IN_JAR_NAME));
+        jarOut.write(dex);
+        jarOut.closeEntry();
+        jarOut.close();
+
+        DexFile dexFile = DexFile.loadDex(
+            result.getPath(), generateOutputName(result, dexCache), 0);
+
+        return dexFile.loadClass(generatedName, classLoader);
+    }
+
+    private static String generateOutputName(File sourcePath, File outputDir) {
+        try {
+            return VisibilityProxy.invokeStaticByName(
+                DexClassLoader.class, "generateOutputName",
+                sourcePath.getPath(), outputDir.getAbsolutePath());
+        } catch (InvocationTargetException e) {
+          throw new AssertionError(e);
+        } catch (AssertionFailedError e) {
+          // Ignoring method not found.
+        }
+
+        try {
+            Class<?> dexPathListClass = Class.forName("dalvik.system.DexPathList");
+            return VisibilityProxy.invokeStaticByName(
+                dexPathListClass, "optimizedPathFor", sourcePath, outputDir);
+        } catch (ClassNotFoundException e) {
+            throw new AssertionError(e);
+        } catch (InvocationTargetException e) {
+            throw new AssertionError(e);
+        }
     }
 
     private static RuntimeException launderCause(InvocationTargetException e) {
@@ -545,7 +589,8 @@
     }
 
     private static <T, G extends T> void generateConstructorsAndFields(DexMaker dexMaker,
-            TypeId<G> generatedType, TypeId<T> superType, Class<T> superClass) {
+        TypeId<G> generatedType, TypeId<T> superType, Class<T> superClass,
+        ClassLoader targetClassLoader) {
         TypeId<InvocationHandler> handlerType = TypeId.get(InvocationHandler.class);
         TypeId<Method[]> methodArrayType = TypeId.get(Method[].class);
         FieldId<G, InvocationHandler> handlerField = generatedType.getField(
@@ -558,6 +603,15 @@
             if (constructor.getModifiers() == Modifier.FINAL) {
                 continue;
             }
+            if (Modifier.isPrivate(constructor.getModifiers())) {
+                continue;
+            }
+            if (!Modifier.isPublic(constructor.getModifiers()) &&
+                !Modifier.isProtected(constructor.getModifiers()) &&
+                constructor.getDeclaringClass().getClassLoader() != targetClassLoader) {
+                continue;
+            }
+
             TypeId<?>[] types = classArrayToTypeArray(constructor.getParameterTypes());
             MethodId<?, ?> method = generatedType.getConstructor(types);
             Code constructorCode = dexMaker.declare(method, PUBLIC);
@@ -603,6 +657,8 @@
             getMethodsToProxy(methodsToProxy, seenFinalMethods, c);
         }
 
+        methodsToProxy.removeAll(seenFinalMethods);
+
         Method[] results = new Method[methodsToProxy.size()];
         int i = 0;
         for (MethodSetEntry entry : methodsToProxy) {
@@ -625,10 +681,22 @@
                 // Skip static methods, overriding them has no effect.
                 continue;
             }
-            if (method.getName().equals("finalize") && method.getParameterTypes().length == 0) {
+            if (isFinalizeMethod(method) || isToStringMethod(method) || isEqualsMethod(method)) {
                 // Skip finalize method, it's likely important that it execute as normal.
                 continue;
             }
+            if (Modifier.isPrivate(method.getModifiers())) {
+                // Don't mock private methods.
+                continue;
+            }
+            if (!Modifier.isPublic(method.getModifiers()) &&
+                !Modifier.isProtected(method.getModifiers()) &&
+                (method.getDeclaringClass().getClassLoader() != parentClassLoader ||
+                 !baseClass.getPackage().equals(method.getDeclaringClass().getPackage()))) {
+                // For package private methods, we need to check if the generated class can access to the
+                // original class. If not, we don't mock it.
+                continue;
+            }
             MethodSetEntry entry = new MethodSetEntry(method);
             if (seenFinalMethods.contains(entry)) {
                 // This method is final in a child class.
@@ -643,8 +711,26 @@
         }
     }
 
+    private static boolean isFinalizeMethod(Method method) {
+      return method.getName().equals("finalize") &&
+             method.getParameterTypes().length == 0;
+    }
+
+    private static boolean isToStringMethod(Method method) {
+      return method.getName().equals("toString") &&
+             method.getParameterTypes().length == 0;
+    }
+
+    private static boolean isEqualsMethod(Method method) {
+      if (!method.getName().equals("equals")) {
+        return false;
+      }
+      Class<?>[] parameterTypes = method.getParameterTypes();
+      return parameterTypes.length == 1 && parameterTypes[0] == Object.class;
+    }
+
     private static <T> String getMethodNameForProxyOf(Class<T> clazz) {
-        return clazz.getSimpleName() + "_Proxy";
+        return clazz.getName().replace('.', '/') + "$$_Proxy";
     }
 
     private static TypeId<?>[] classArrayToTypeArray(Class<?>[] input) {
@@ -742,14 +828,12 @@
     private static class MethodSetEntry {
         private final String name;
         private final Class<?>[] paramTypes;
-        private final Class<?> returnType;
         private final Method originalMethod;
 
         public MethodSetEntry(Method method) {
             originalMethod = method;
             name = method.getName();
             paramTypes = method.getParameterTypes();
-            returnType = method.getReturnType();
         }
 
         @Override
@@ -757,7 +841,6 @@
             if (o instanceof MethodSetEntry) {
                 MethodSetEntry other = (MethodSetEntry) o;
                 return name.equals(other.name)
-                        && returnType.equals(other.returnType)
                         && Arrays.equals(paramTypes, other.paramTypes);
             }
             return false;
@@ -767,7 +850,6 @@
         public int hashCode() {
             int result = 17;
             result += 31 * result + name.hashCode();
-            result += 31 * result + returnType.hashCode();
             result += 31 * result + Arrays.hashCode(paramTypes);
             return result;
         }