Android Open Source - probe Dex Proxy Builder






From Project

Back to project page probe.

License

The source code is released under:

Apache License

If you think the Android project probe listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/*
 * Copyright (C) 2014 Lucas Rocha//from  w  w  w.  j  a v a  2  s .  c  o m
 *
 * This code is based on bits and pieces of DexMaker's ProxyBuilder.
 *
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.lucasr.probe;

import android.content.Context;
import android.graphics.Canvas;
import android.view.View;

import com.google.dexmaker.Code;
import com.google.dexmaker.Comparison;
import com.google.dexmaker.DexMaker;
import com.google.dexmaker.FieldId;
import com.google.dexmaker.Label;
import com.google.dexmaker.Local;
import com.google.dexmaker.MethodId;
import com.google.dexmaker.TypeId;

import java.io.IOException;

import static java.lang.reflect.Modifier.PRIVATE;
import static java.lang.reflect.Modifier.PUBLIC;
import static org.lucasr.probe.ViewProxyBuilder.CONSTRUCTOR_ARG_TYPES;

/**
 * {@link org.lucasr.probe.DexProxyBuilder} is used by {@link org.lucasr.probe.ProbeViewFactory}
 * to dynamically wrap the inflated {@link android.view.View} instances for a given
 * {@link org.lucasr.probe.Probe}.
 *
 * @see org.lucasr.probe.Probe
 * @see org.lucasr.probe.ViewProxyBuilder
 * @see org.lucasr.probe.ProbeViewFactory
 */
final class DexProxyBuilder {
    private enum ViewMethod {
        ON_MEASURE("onMeasure"),
        ON_LAYOUT("onLayout"),
        DRAW("draw"),
        ON_DRAW("onDraw"),
        REQUEST_LAYOUT("requestLayout"),
        FORCE_LAYOUT("forceLayout"),
        SET_MEASURED_DIMENSION("setMeasuredDimension"),
        SET_INTERCEPTOR("setInterceptor");

        private final String mMethodName;
        private final String mInvokeMethodName;

        private ViewMethod(String methodName) {
            mMethodName = methodName;
            mInvokeMethodName = "invoke" + Character.toUpperCase(methodName.charAt(0)) +
                    methodName.substring(1);
        }

        String getName() {
            return mMethodName;
        }

        String getInvokeName() {
            return mInvokeMethodName;
        }
    }

    private static final String DEX_CACHE_DIRECTORY = "probe";

    private static final String FIELD_NAME_INTERCEPTOR = "mInterceptor";

    private static final TypeId<Canvas> CANVAS_TYPE = TypeId.get(Canvas.class);
    private static final TypeId<Interceptor> INTERCEPTOR_TYPE = TypeId.get(Interceptor.class);
    private static final TypeId<View> VIEW_TYPE = TypeId.get(View.class);
    private static final TypeId<ViewProxy> INTERCEPTABLE_VIEW_TYPE =
            TypeId.get(ViewProxy.class);
    private static final TypeId<Void> VOID_TYPE = TypeId.get(void.class);

    private DexProxyBuilder() {
    }

    /**
     * Generates class field that holds a reference to the associated
     * {@link org.lucasr.probe.Interceptor} instance and the {@link android.view.View} constructor.
     */
    private static <T, G extends T> void generateConstructorAndFields(DexMaker dexMaker,
                                                                      TypeId<G> generatedType,
                                                                      TypeId<T> baseType) {
        final FieldId<G, Interceptor> interceptorField =
                generatedType.getField(INTERCEPTOR_TYPE, FIELD_NAME_INTERCEPTOR);
        dexMaker.declare(interceptorField, PRIVATE, null);

        final TypeId<?>[] types = classArrayToTypeArray(CONSTRUCTOR_ARG_TYPES);
        final MethodId<?, ?> constructor = generatedType.getConstructor(types);
        final Code constructorCode = dexMaker.declare(constructor, PUBLIC);

        final Local<?>[] params = new Local[types.length];
        for (int i = 0; i < params.length; ++i) {
            params[i] = constructorCode.getParameter(i, types[i]);
        }

        final MethodId<T, ?> superConstructor = baseType.getConstructor(types);
        final Local<G> thisRef = constructorCode.getThis(generatedType);
        constructorCode.invokeDirect(superConstructor, null, thisRef, params);
        constructorCode.returnVoid();
    }

    /**
     * Generates the {@link android.view.View#onMeasure(int, int)} method for the proxy class.
     */
    private static <T, G extends T> void generateOnMeasureMethod(DexMaker dexMaker,
                                                                 TypeId<G> generatedType,
                                                                 TypeId<T> baseType) {
        final FieldId<G, Interceptor> interceptorField =
                generatedType.getField(INTERCEPTOR_TYPE, FIELD_NAME_INTERCEPTOR);

        final String methodName = ViewMethod.ON_MEASURE.getName();

        final MethodId<T, Void> superMethod = baseType.getMethod(VOID_TYPE, methodName, TypeId.INT,
                TypeId.INT);
        final MethodId<Interceptor, Void> onMeasureMethod =
                INTERCEPTOR_TYPE.getMethod(VOID_TYPE, methodName, VIEW_TYPE, TypeId.INT, TypeId.INT);

        final MethodId<G, Void> methodId = generatedType.getMethod(VOID_TYPE, methodName,
                TypeId.INT, TypeId.INT);
        final Code code = dexMaker.declare(methodId, PUBLIC);

        final Local<G> localThis = code.getThis(generatedType);
        final Local<Interceptor> nullInterceptor = code.newLocal(INTERCEPTOR_TYPE);
        final Local<Interceptor> localInterceptor = code.newLocal(INTERCEPTOR_TYPE);
        final Local<Integer> localWidth = code.getParameter(0, TypeId.INT);
        final Local<Integer> localHeight = code.getParameter(1, TypeId.INT);

        code.iget(interceptorField, localInterceptor, localThis);
        code.loadConstant(nullInterceptor, null);

        // Interceptor is not null, call it.
        final Label interceptorNullCase = new Label();
        code.compare(Comparison.EQ, interceptorNullCase, nullInterceptor, localInterceptor);
        code.invokeVirtual(onMeasureMethod, null, localInterceptor, localThis,
                localWidth, localHeight);
        code.returnVoid();

        // Interceptor is null, call super method.
        code.mark(interceptorNullCase);
        code.invokeSuper(superMethod, null, localThis, localWidth, localHeight);
        code.returnVoid();

        final MethodId<G, Void> callsSuperMethod = generatedType.getMethod(VOID_TYPE,
                ViewMethod.ON_MEASURE.getInvokeName(), TypeId.INT, TypeId.INT);

        final Code superCode = dexMaker.declare(callsSuperMethod, PUBLIC);

        final Local<G> superThis = superCode.getThis(generatedType);
        final Local<Integer> superLocalWidth = superCode.getParameter(0, TypeId.INT);
        final Local<Integer> superLocalHeight = superCode.getParameter(1, TypeId.INT);
        superCode.invokeSuper(superMethod, null, superThis, superLocalWidth, superLocalHeight);
        superCode.returnVoid();
    }

    /**
     * Generates the {@link android.view.View#onLayout(boolean, int, int, int, int)} method
     * for the proxy class.
     */
    private static <T, G extends T> void generateOnLayoutMethod(DexMaker dexMaker,
                                                                TypeId<G> generatedType,
                                                                TypeId<T> baseType) {
        final FieldId<G, Interceptor> interceptorField =
                generatedType.getField(INTERCEPTOR_TYPE, FIELD_NAME_INTERCEPTOR);

        final String methodName = ViewMethod.ON_LAYOUT.getName();

        final MethodId<T, Void> superMethod = baseType.getMethod(VOID_TYPE, methodName,
                TypeId.BOOLEAN, TypeId.INT, TypeId.INT, TypeId.INT, TypeId.INT);
        final MethodId<Interceptor, Void> onLayoutMethod =
                INTERCEPTOR_TYPE.getMethod(VOID_TYPE, methodName, VIEW_TYPE, TypeId.BOOLEAN,
                        TypeId.INT, TypeId.INT, TypeId.INT, TypeId.INT);

        final MethodId<G, Void> methodId = generatedType.getMethod(VOID_TYPE, methodName,
                TypeId.BOOLEAN, TypeId.INT, TypeId.INT, TypeId.INT, TypeId.INT);
        final Code code = dexMaker.declare(methodId, PUBLIC);

        final Local<G> localThis = code.getThis(generatedType);
        final Local<Interceptor> nullInterceptor = code.newLocal(INTERCEPTOR_TYPE);
        final Local<Interceptor> localInterceptor = code.newLocal(INTERCEPTOR_TYPE);
        final Local<Boolean> localChanged = code.getParameter(0, TypeId.BOOLEAN);
        final Local<Integer> localLeft = code.getParameter(1, TypeId.INT);
        final Local<Integer> localTop = code.getParameter(2, TypeId.INT);
        final Local<Integer> localRight = code.getParameter(3, TypeId.INT);
        final Local<Integer> localBottom = code.getParameter(4, TypeId.INT);

        code.iget(interceptorField, localInterceptor, localThis);
        code.loadConstant(nullInterceptor, null);

        // Interceptor is not null, call it.
        final Label interceptorNullCase = new Label();
        code.compare(Comparison.EQ, interceptorNullCase, nullInterceptor, localInterceptor);
        code.invokeVirtual(onLayoutMethod, null, localInterceptor, localThis, localChanged,
                localLeft, localTop, localRight, localBottom);
        code.returnVoid();

        // Interceptor is null, call super method.
        code.mark(interceptorNullCase);
        code.invokeSuper(superMethod, null, localThis, localChanged, localLeft, localTop,
                localRight, localBottom);
        code.returnVoid();

        final MethodId<G, Void> callsSuperMethod = generatedType.getMethod(VOID_TYPE,
                ViewMethod.ON_LAYOUT.getInvokeName(), TypeId.BOOLEAN, TypeId.INT, TypeId.INT,
                TypeId.INT, TypeId.INT);

        final Code superCode = dexMaker.declare(callsSuperMethod, PUBLIC);

        final Local<G> superThis = superCode.getThis(generatedType);
        final Local<Boolean> superLocalChanged = superCode.getParameter(0, TypeId.BOOLEAN);
        final Local<Integer> superLocalLeft = superCode.getParameter(1, TypeId.INT);
        final Local<Integer> superLocalTop = superCode.getParameter(2, TypeId.INT);
        final Local<Integer> superLocalRight = superCode.getParameter(3, TypeId.INT);
        final Local<Integer> superLocalBottom = superCode.getParameter(4, TypeId.INT);
        superCode.invokeSuper(superMethod, null, superThis, superLocalChanged, superLocalLeft,
                superLocalTop, superLocalRight, superLocalBottom);
        superCode.returnVoid();
    }

    /**
     * Generates the {@link android.view.View#draw(android.graphics.Canvas)} method for the proxy class.
     */
    private static <T, G extends T> void generateDrawMethod(DexMaker dexMaker,
                                                            TypeId<G> generatedType,
                                                            TypeId<T> baseType,
                                                            ViewMethod viewMethod) {
        final FieldId<G, Interceptor> interceptorField =
                generatedType.getField(INTERCEPTOR_TYPE, FIELD_NAME_INTERCEPTOR);

        final String methodName = viewMethod.getName();

        final MethodId<T, Void> superMethod = baseType.getMethod(VOID_TYPE, methodName, CANVAS_TYPE);
        final MethodId<Interceptor, Void> drawMethod =
                INTERCEPTOR_TYPE.getMethod(VOID_TYPE, methodName, VIEW_TYPE, CANVAS_TYPE);

        final MethodId<G, Void> methodId = generatedType.getMethod(VOID_TYPE, methodName, CANVAS_TYPE);
        final Code code = dexMaker.declare(methodId, PUBLIC);

        final Local<G> localThis = code.getThis(generatedType);
        final Local<Interceptor> nullInterceptor = code.newLocal(INTERCEPTOR_TYPE);
        final Local<Interceptor> localInterceptor = code.newLocal(INTERCEPTOR_TYPE);
        final Local<Canvas> localCanvas = code.getParameter(0, CANVAS_TYPE);

        code.iget(interceptorField, localInterceptor, localThis);
        code.loadConstant(nullInterceptor, null);

        // Interceptor is not null, call it.
        final Label interceptorNullCase = new Label();
        code.compare(Comparison.EQ, interceptorNullCase, nullInterceptor, localInterceptor);
        code.invokeVirtual(drawMethod, null, localInterceptor, localThis, localCanvas);
        code.returnVoid();

        // Interceptor is null, call super method.
        code.mark(interceptorNullCase);
        code.invokeSuper(superMethod, null, localThis, localCanvas);
        code.returnVoid();

        final MethodId<G, Void> callsSuperMethod =
                generatedType.getMethod(VOID_TYPE, viewMethod.getInvokeName(), CANVAS_TYPE);

        final Code superCode = dexMaker.declare(callsSuperMethod, PUBLIC);

        final Local<G> superThis = superCode.getThis(generatedType);
        final Local<Canvas> superLocalCanvas = superCode.getParameter(0, CANVAS_TYPE);
        superCode.invokeSuper(superMethod, null, superThis, superLocalCanvas);
        superCode.returnVoid();
    }

    /**
     * Generates the {@link android.view.View#onDraw(android.graphics.Canvas)} method for the proxy class.
     */
    private static <T, G extends T> void generateDrawMethods(DexMaker dexMaker,
                                                             TypeId<G> generatedType,
                                                             TypeId<T> baseType) {
        generateDrawMethod(dexMaker, generatedType, baseType, ViewMethod.DRAW);
        generateDrawMethod(dexMaker, generatedType, baseType, ViewMethod.ON_DRAW);
    }

    /**
     * Generates the {@link android.view.View#requestLayout()} method for the proxy class.
     */
    private static <T, G extends T> void generateRequestLayoutMethod(DexMaker dexMaker,
                                                                     TypeId<G> generatedType,
                                                                     TypeId<T> baseType) {
        final FieldId<G, Interceptor> interceptorField =
                generatedType.getField(INTERCEPTOR_TYPE, FIELD_NAME_INTERCEPTOR);

        final String methodName = ViewMethod.REQUEST_LAYOUT.getName();

        final MethodId<T, Void> superMethod = baseType.getMethod(VOID_TYPE, methodName);
        final MethodId<Interceptor, Void> requestLayoutMethod =
                INTERCEPTOR_TYPE.getMethod(VOID_TYPE, methodName, VIEW_TYPE);

        final MethodId<?, ?> methodId = generatedType.getMethod(VOID_TYPE, methodName);
        final Code code = dexMaker.declare(methodId, PUBLIC);

        final Local<G> localThis = code.getThis(generatedType);
        final Local<Interceptor> nullInterceptor = code.newLocal(INTERCEPTOR_TYPE);
        final Local<Interceptor> localInterceptor = code.newLocal(INTERCEPTOR_TYPE);

        code.iget(interceptorField, localInterceptor, localThis);
        code.loadConstant(nullInterceptor, null);

        // Interceptor is not null, call it.
        final Label interceptorNullCase = new Label();
        code.compare(Comparison.EQ, interceptorNullCase, nullInterceptor, localInterceptor);
        code.invokeVirtual(requestLayoutMethod, null, localInterceptor, localThis);
        code.returnVoid();

        // Interceptor is null, call super method.
        code.mark(interceptorNullCase);
        code.invokeSuper(superMethod, null, localThis);
        code.returnVoid();

        final MethodId<G, Void> callsSuperMethod =
                generatedType.getMethod(VOID_TYPE, ViewMethod.REQUEST_LAYOUT.getInvokeName());

        final Code superCode = dexMaker.declare(callsSuperMethod, PUBLIC);

        final Local<G> superThis = superCode.getThis(generatedType);
        superCode.invokeSuper(superMethod, null, superThis);
        superCode.returnVoid();
    }

    /**
     * Generates the {@link android.view.View#forceLayout()} method for the proxy class.
     */
    private static <T, G extends T> void generateForceLayoutMethod(DexMaker dexMaker,
                                                                   TypeId<G> generatedType,
                                                                   TypeId<T> baseType) {
        final FieldId<G, Interceptor> interceptorField =
                generatedType.getField(INTERCEPTOR_TYPE, FIELD_NAME_INTERCEPTOR);

        final String methodName = ViewMethod.FORCE_LAYOUT.getName();

        final MethodId<T, Void> superMethod = baseType.getMethod(VOID_TYPE, methodName);
        final MethodId<Interceptor, Void> forceLayoutMethod =
                INTERCEPTOR_TYPE.getMethod(VOID_TYPE, methodName, VIEW_TYPE);

        final MethodId<?, ?> methodId = generatedType.getMethod(VOID_TYPE, methodName);
        final Code code = dexMaker.declare(methodId, PUBLIC);

        final Local<G> localThis = code.getThis(generatedType);
        final Local<Interceptor> nullInterceptor = code.newLocal(INTERCEPTOR_TYPE);
        final Local<Interceptor> localInterceptor = code.newLocal(INTERCEPTOR_TYPE);

        code.iget(interceptorField, localInterceptor, localThis);
        code.loadConstant(nullInterceptor, null);

        // Interceptor is not null, call it.
        final Label interceptorNullCase = new Label();
        code.compare(Comparison.EQ, interceptorNullCase, nullInterceptor, localInterceptor);
        code.invokeVirtual(forceLayoutMethod, null, localInterceptor, localThis);
        code.returnVoid();

        // Interceptor is null, call super method.
        code.mark(interceptorNullCase);
        code.invokeSuper(superMethod, null, localThis);
        code.returnVoid();

        final MethodId<G, Void> callsSuperMethod =
                generatedType.getMethod(VOID_TYPE, ViewMethod.FORCE_LAYOUT.getInvokeName());

        final Code superCode = dexMaker.declare(callsSuperMethod, PUBLIC);

        final Local<G> superThis = superCode.getThis(generatedType);
        superCode.invokeSuper(superMethod, null, superThis);
        superCode.returnVoid();
    }

    /**
     * Generates the {@link android.view.View#setMeasuredDimension(int, int)} method for
     * the proxy class.
     */
    private static <T, G extends T> void generateSetMeasuredDimension(DexMaker dexMaker,
                                                                      TypeId<G> generatedType,
                                                                      TypeId<T> baseType) {
        final String methodName = ViewMethod.SET_MEASURED_DIMENSION.getName();

        final MethodId<T, Void> superMethod = baseType.getMethod(VOID_TYPE, methodName, TypeId.INT,
                TypeId.INT);

        final MethodId<G, Void> callsSuperMethod = generatedType.getMethod(VOID_TYPE,
                ViewMethod.SET_MEASURED_DIMENSION.getInvokeName(), TypeId.INT, TypeId.INT);

        final Code code = dexMaker.declare(callsSuperMethod, PUBLIC);

        final Local<G> localThis = code.getThis(generatedType);
        final Local<Integer> localWidth = code.getParameter(0, TypeId.INT);
        final Local<Integer> localHeight = code.getParameter(1, TypeId.INT);
        code.invokeSuper(superMethod, null, localThis, localWidth, localHeight);
        code.returnVoid();
    }

    /**
     * Generates the {@link android.view.View#setMeasuredDimension(int, int)} method for
     * the proxy class.
     */
    private static <T, G extends T> void generateSetInterceptor(DexMaker dexMaker,
                                                                TypeId<G> generatedType,
                                                                TypeId<T> baseType) {
        final FieldId<G, Interceptor> interceptorField =
                generatedType.getField(INTERCEPTOR_TYPE, FIELD_NAME_INTERCEPTOR);

        final String methodName = ViewMethod.SET_INTERCEPTOR.getName();

        final MethodId<G, Void> methodId = generatedType.getMethod(VOID_TYPE, methodName,
                INTERCEPTOR_TYPE);
        final Code code = dexMaker.declare(methodId, PUBLIC);

        final Local<G> localThis = code.getThis(generatedType);
        final Local<Interceptor> localInterceptor = code.getParameter(0, INTERCEPTOR_TYPE);
        code.iput(interceptorField, localThis, localInterceptor);
        code.returnVoid();
    }

    private static <T> String getClassNameForProxyOf(Class<? extends T> clazz) {
        return clazz.getSimpleName() + "_Proxy";
    }

    private static TypeId<?>[] classArrayToTypeArray(Class<?>[] input) {
        final TypeId<?>[] result = new TypeId[input.length];
        for (int i = 0; i < input.length; ++i) {
            result[i] = TypeId.get(input[i]);
        }

        return result;
    }

    /**
     * Generates dynamic {@link android.view.View} proxy class.
     */
    @SuppressWarnings("unchecked")
    static <T, G extends T> Class<G> generateProxyClass(Context context, Class<T> baseClass)
            throws IOException {
        // Cache missed; generate the proxy class.
        final DexMaker dexMaker = new DexMaker();

        final String proxyClassName = getClassNameForProxyOf(baseClass);
        final TypeId<G> generatedType = TypeId.get("L" + proxyClassName + ";");
        final TypeId<T> baseType = TypeId.get(baseClass);

        generateConstructorAndFields(dexMaker, generatedType, baseType);
        generateOnMeasureMethod(dexMaker, generatedType, baseType);
        generateOnLayoutMethod(dexMaker, generatedType, baseType);
        generateDrawMethods(dexMaker, generatedType, baseType);
        generateRequestLayoutMethod(dexMaker, generatedType, baseType);
        generateForceLayoutMethod(dexMaker, generatedType, baseType);
        generateSetMeasuredDimension(dexMaker, generatedType, baseType);
        generateSetInterceptor(dexMaker, generatedType, baseType);

        dexMaker.declare(generatedType, proxyClassName + ".generated", PUBLIC, baseType,
                INTERCEPTABLE_VIEW_TYPE);

        final ClassLoader classLoader = dexMaker.generateAndLoad(context.getClassLoader(),
                context.getDir(DEX_CACHE_DIRECTORY, Context.MODE_PRIVATE));
        try {
            return (Class<G>) classLoader.loadClass(proxyClassName);
        } 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);
        }
    }
}




Java Source Code List

org.lucasr.probe.DexProxyBuilder.java
org.lucasr.probe.Filter.java
org.lucasr.probe.Interceptor.java
org.lucasr.probe.ProbeViewFactory.java
org.lucasr.probe.Probe.java
org.lucasr.probe.ViewClassUtil.java
org.lucasr.probe.ViewProxyBuilder.java
org.lucasr.probe.ViewProxy.java
org.lucasr.probe.interceptors.LayoutBoundsInterceptor.java
org.lucasr.probe.interceptors.OvermeasureInterceptor.java
org.lucasr.probe.sample.MainActivity.java