org.eclipse.wb.internal.rcp.databinding.parser.BindingContextClassLoaderInitializer.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.wb.internal.rcp.databinding.parser.BindingContextClassLoaderInitializer.java

Source

/*******************************************************************************
 * Copyright (c) 2011 Google, Inc.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Google, Inc. - initial API and implementation
 *******************************************************************************/
package org.eclipse.wb.internal.rcp.databinding.parser;

import com.google.common.collect.Maps;

import org.eclipse.wb.internal.core.utils.asm.ToBytesClassAdapter;
import org.eclipse.wb.internal.core.utils.execution.ExecutionUtils;
import org.eclipse.wb.internal.core.utils.execution.RunnableEx;
import org.eclipse.wb.internal.core.utils.reflect.CompositeClassLoader;
import org.eclipse.wb.internal.core.utils.reflect.IByteCodeProcessor;
import org.eclipse.wb.internal.core.utils.reflect.IClassLoaderInitializer;
import org.eclipse.wb.internal.core.utils.reflect.ProjectClassLoader;
import org.eclipse.wb.internal.core.utils.reflect.ReflectionUtils;

import net.sf.cglib.core.ReflectUtils;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodAdapter;
import org.objectweb.asm.MethodVisitor;

import java.io.InputStream;
import java.util.List;
import java.util.Map;

/**
 * Initialize class loader for use bindings on runtime.
 * 
 * @author lobas_av
 * @coverage bindings.rcp.parser
 */
public final class BindingContextClassLoaderInitializer implements IClassLoaderInitializer {
    private static final Map<ClassLoader, Object> CLASS_LOADER_TO_THREAD_LOCAL = Maps.newHashMap();
    ////////////////////////////////////////////////////////////////////////////
    //
    // Constructor
    //
    ////////////////////////////////////////////////////////////////////////////
    public static final IClassLoaderInitializer INSTANCE = new BindingContextClassLoaderInitializer();

    private BindingContextClassLoaderInitializer() {
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // IClassLoaderInitializer
    //
    ////////////////////////////////////////////////////////////////////////////
    public void initialize(final ClassLoader classLoader) {
        ExecutionUtils.runIgnore(new RunnableEx() {
            public void run() throws Exception {
                createDefaultBean(configureBindings(classLoader));
            }
        });
        ExecutionUtils.runIgnore(new RunnableEx() {
            public void run() throws Exception {
                setDefaultRealm(classLoader);
            }
        });
    }

    public void deinitialize(final ClassLoader classLoader) {
        ExecutionUtils.runIgnore(new RunnableEx() {
            public void run() throws Exception {
                Object threadLocal = CLASS_LOADER_TO_THREAD_LOCAL.remove(classLoader);
                ReflectionUtils.invokeMethod(threadLocal, "remove()");
            }
        });
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // Realm
    //
    ////////////////////////////////////////////////////////////////////////////
    private static void setDefaultRealm(ClassLoader classLoader) throws Exception {
        // prepare Display
        Class<?> displayClass = classLoader.loadClass("org.eclipse.swt.widgets.Display");
        Object display = ReflectionUtils.invokeMethod(displayClass, "getDefault()");
        // create Realm
        Class<?> swtObservables = classLoader.loadClass("org.eclipse.jface.databinding.swt.SWTObservables");
        Object realm = ReflectionUtils.invokeMethod(swtObservables, "getRealm(org.eclipse.swt.widgets.Display)",
                display);
        // set default Realm
        Class<?> realmClass = classLoader.loadClass("org.eclipse.core.databinding.observable.Realm");
        ReflectionUtils.invokeMethod(realmClass, "setDefault(org.eclipse.core.databinding.observable.Realm)",
                realm);
        CLASS_LOADER_TO_THREAD_LOCAL.put(classLoader, ReflectionUtils.getFieldObject(realmClass, "defaultRealm"));
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // Bean/Observable
    //
    ////////////////////////////////////////////////////////////////////////////
    private void createDefaultBean(ProjectClassLoader projectClassLoader) throws Exception {
        // prepare DefaultBean bytes
        ClassLoader localClassLoader = getClass().getClassLoader();
        InputStream stream = localClassLoader
                .getResourceAsStream("org/eclipse/wb/internal/rcp/databinding/parser/DefaultBean.class");
        byte[] bytes = IOUtils.toByteArray(stream);
        stream.close();
        // inject DefaultBean to project class loader
        ReflectUtils.defineClass("org.eclipse.wb.internal.rcp.databinding.parser.DefaultBean", bytes,
                projectClassLoader);
    }

    private static ProjectClassLoader configureBindings(ClassLoader classLoader) {
        // prepare project class loader
        CompositeClassLoader compositeClassLoader = (CompositeClassLoader) classLoader;
        List<?> classLoaders = compositeClassLoader.getClassLoaders();
        ProjectClassLoader projectClassLoader = (ProjectClassLoader) classLoaders.get(classLoaders.size() - 1);
        // add bytecode processor
        projectClassLoader.add(new IByteCodeProcessor() {
            public void initialize(ProjectClassLoader classLoader) {
            }

            public byte[] process(String className, byte[] bytes) {
                if ("org.eclipse.core.databinding.beans.BeansObservables".equals(className)) {
                    return transformBindings(bytes);
                }
                if ("org.eclipse.core.databinding.observable.list.ObservableList".equals(className)) {
                    return transformObserves(bytes,
                            "(Lorg/eclipse/core/databinding/observable/Realm;Ljava/util/List;Ljava/lang/Object;)V",
                            "EMPTY_LIST", "Ljava/util/List;");
                }
                if ("org.eclipse.core.databinding.observable.set.ObservableSet".equals(className)) {
                    return transformObserves(bytes,
                            "(Lorg/eclipse/core/databinding/observable/Realm;Ljava/util/Set;Ljava/lang/Object;)V",
                            "EMPTY_SET", "Ljava/util/Set;");
                }
                return bytes;
            }
        });
        return projectClassLoader;
    }

    /**
     * Inject to all public static methods contains parameters {@code Object} bean and {@code String}
     * property:
     * 
     * <pre>
      * public static %ReturnType% %method%(..., Object bean, ..., String property, ....) {
      *     if (bean == null) {
      *         bean = DefaultBean.INSTANCE;
      *         property = "foo"; // "fooList"; // "fooSet";
      *     }
      *     %FOO_BODY_PART%
      * }
      * </pre>
     */
    private static byte[] transformBindings(byte[] bytes) {
        ClassReader classReader = new ClassReader(bytes);
        ToBytesClassAdapter codeRewriter = new ToBytesClassAdapter(ClassWriter.COMPUTE_MAXS) {
            @Override
            public MethodVisitor visitMethod(int access, String name, String desc, String signature,
                    String[] exceptions) {
                MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
                if ((access & ACC_PUBLIC) != 0 && (access & ACC_STATIC) != 0) {
                    // find indexes for bean object argument and bean property argument
                    final int[] indexes = { -1, -1 };
                    String[] parameters = StringUtils.split(StringUtils.substringBetween(desc, "(", ")"), ";");
                    for (int i = 0; i < parameters.length; i++) {
                        String parameter = parameters[i];
                        if (indexes[0] == -1 && parameter.equals("Ljava/lang/Object")) {
                            indexes[0] = i;
                        } else if (indexes[0] != -1 && indexes[1] == -1 && parameter.equals("Ljava/lang/String")) {
                            indexes[1] = i;
                        }
                    }
                    if (indexes[0] != -1 && indexes[1] != -1) {
                        // prepare bean property name
                        final String[] property = { "foo" };
                        if (name.endsWith("List")) {
                            property[0] = "fooList";
                        } else if (name.endsWith("Set")) {
                            property[0] = "fooSet";
                        }
                        return new MethodAdapter(mv) {
                            @Override
                            public void visitCode() {
                                visitVarInsn(ALOAD, indexes[0]);
                                Label label = new Label();
                                visitJumpInsn(IFNONNULL, label);
                                visitFieldInsn(GETSTATIC,
                                        "org/eclipse/wb/internal/rcp/databinding/parser/DefaultBean", "INSTANCE",
                                        "Lorg/eclipse/wb/internal/rcp/databinding/parser/DefaultBean;");
                                visitVarInsn(ASTORE, indexes[0]);
                                visitLdcInsn(property[0]);
                                visitVarInsn(ASTORE, indexes[1]);
                                visitLabel(label);
                                super.visitCode();
                            }
                        };
                    }
                }
                return mv;
            }
        };
        classReader.accept(codeRewriter, 0);
        return codeRewriter.toByteArray();
    }

    /**
     * Inject to constructor with {@code methodDescriptor} signature additional code:
     * 
     * <pre>
      * constructor(Realm, %Type% argument, Object) {
      *    if (argument == null) {
      *        argument = Collections.EMPTY_%field%;
      *    }
      *    %FOO_BODY_PART%
      * }
      * </pre>
     */
    private static byte[] transformObserves(byte[] bytes, final String methodDescriptor, final String field,
            final String fieldType) {
        ClassReader classReader = new ClassReader(bytes);
        ToBytesClassAdapter codeRewriter = new ToBytesClassAdapter(ClassWriter.COMPUTE_MAXS) {
            @Override
            public MethodVisitor visitMethod(int access, String name, String desc, String signature,
                    String[] exceptions) {
                MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
                if (name.equals("<init>") && desc.equals(methodDescriptor)) {
                    return new MethodAdapter(mv) {
                        @Override
                        public void visitCode() {
                            visitVarInsn(ALOAD, 2);
                            Label label = new Label();
                            visitJumpInsn(IFNONNULL, label);
                            visitFieldInsn(GETSTATIC, "java/util/Collections", field, fieldType);
                            visitVarInsn(ASTORE, 2);
                            visitLabel(label);
                            super.visitCode();
                        }
                    };
                }
                return mv;
            }
        };
        classReader.accept(codeRewriter, 0);
        return codeRewriter.toByteArray();
    }
}