name.ikysil.beanpathdsl.dynamic.DynamicBeanPath.java Source code

Java tutorial

Introduction

Here is the source code for name.ikysil.beanpathdsl.dynamic.DynamicBeanPath.java

Source

/*
 * Copyright 2016 Illya Kysil <ikysil@ikysil.name>.
 *
 * 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 name.ikysil.beanpathdsl.dynamic;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
import javassist.util.proxy.MethodFilter;
import javassist.util.proxy.MethodHandler;
import javassist.util.proxy.ProxyFactory;
import name.ikysil.beanpathdsl.core.BeanPaths;
import org.apache.commons.beanutils.PropertyUtilsBean;

/**
 *
 * @author Illya Kysil <ikysil@ikysil.name>
 */
public final class DynamicBeanPath {

    private static final ThreadLocal<Set<BeanFactory>> FACTORIES = new ThreadLocal<Set<BeanFactory>>() {

        @Override
        protected Set<BeanFactory> initialValue() {
            ServiceLoader<BeanFactory> serviceLoader = ServiceLoader.load(BeanFactory.class);
            Set<BeanFactory> result = new HashSet<>();
            for (BeanFactory beanFactory : serviceLoader) {
                result.add(beanFactory);
            }
            return result;
        }

    };

    private DynamicBeanPath() {
    }

    /**
     * Reset current bean path and start from a bean of specified class.
     *
     * @param <T> type of the bean
     * @param clazz class of the bean
     * @return instance of the bean class instrumented to capture getters and setters invocations
     */
    public static <T> T root(Class<T> clazz) {
        BeanPaths.reset();
        return expr(clazz);
    }

    /**
     * Continue bean path construction from a bean of specified class.
     *
     * @param <T> type of the bean
     * @param clazz class of the bean
     * @return instance of the bean class instrumented to capture getters and setters invocations
     */
    @SuppressWarnings("unchecked")
    public static <T> T expr(Class<T> clazz) {
        try {
            T result;
            if (Modifier.isFinal(clazz.getModifiers())) {
                result = clazz.newInstance();
            } else {
                ProxyFactory pf = new ProxyFactory();
                if (clazz.isInterface()) {
                    pf.setSuperclass(Object.class);
                    pf.setInterfaces(new Class<?>[] { clazz });
                } else {
                    pf.setSuperclass(clazz);
                }
                pf.setFilter(new DefaultMethodFilter());
                result = (T) pf.create(new Class<?>[0], new Object[0], new DefaultMethodHandler(clazz));
            }
            return result;
        } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | IllegalArgumentException
                | InvocationTargetException ex) {
            throw new IllegalStateException(String.format("Can't instantiate %s", clazz.getName()), ex);
        }
    }

    /**
     * Return the current bean path. Parameter is ignored.
     *
     * @param o ignored
     * @return current bean path
     */
    public static String path(Object o) {
        return BeanPaths.path();
    }

    /**
     * Return the current bean path.
     *
     * @return current bean path
     */
    public static String path() {
        return BeanPaths.path();
    }

    private static class DefaultMethodFilter implements MethodFilter {

        private final Set<String> ignoredMethods = new HashSet<>();

        public DefaultMethodFilter() {
            ignoredMethods.add("finalize");
        }

        @Override
        public boolean isHandled(Method m) {
            return !ignoredMethods.contains(m.getName());
        }

    }

    private static class DefaultMethodHandler implements MethodHandler {

        private final Map<String, PropertyDescriptor> methodNameToPropertyDescriptor = new HashMap<>();

        public DefaultMethodHandler(Class<?> sourceClass) {
            populatePropertyDescriptors(sourceClass);
        }

        private void addPropertyDescriptor(Method method, PropertyDescriptor pd) {
            if (method == null) {
                return;
            }
            methodNameToPropertyDescriptor.put(method.getName(), pd);
        }

        private void populatePropertyDescriptors(Class<?> sourceClass) {
            PropertyUtilsBean propertyUtilsBean = new PropertyUtilsBean();
            PropertyDescriptor[] pds = propertyUtilsBean.getPropertyDescriptors(sourceClass);
            for (PropertyDescriptor pd : pds) {
                addPropertyDescriptor(pd.getReadMethod(), pd);
                addPropertyDescriptor(pd.getWriteMethod(), pd);
            }
        }

        @Override
        public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable {
            if (!extendCurrentPath(thisMethod)) {
                return proceed.invoke(self, args);
            }
            final Class<?> returnType = thisMethod.getReturnType();
            for (BeanFactory factory : FACTORIES.get()) {
                Object primitive = factory.createInstance(returnType);
                if (primitive != null) {
                    return primitive;
                }
            }
            if (returnType.isArray()) {
                return Array.newInstance(returnType.getComponentType(), 0);
            }
            return expr(returnType);
        }

        private boolean extendCurrentPath(Method m) throws IllegalStateException {
            final String methodName = m.getName();
            PropertyDescriptor pd = methodNameToPropertyDescriptor.get(methodName);
            if (pd == null) {
                return false;
            } else {
                BeanPaths.navigate(pd.getName());
                return true;
            }
        }

    }

}