com.autsia.codefreeze.impl.CGLIBCodeFreeze.java Source code

Java tutorial

Introduction

Here is the source code for com.autsia.codefreeze.impl.CGLIBCodeFreeze.java

Source

/*
 *    Copyright 2016 Dmytro Titov
 *
 *    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 com.autsia.codefreeze.impl;

import com.autsia.codefreeze.CodeFreeze;
import com.autsia.codefreeze.impl.callbacks.DelegatingMethodInterceptor;
import com.autsia.codefreeze.impl.callbacks.EqualsMethodInterceptor;
import com.autsia.codefreeze.impl.callbacks.ExceptionMethodInterceptor;
import com.autsia.codefreeze.impl.callbacks.FreezingMethodInterceptor;
import com.autsia.codefreeze.impl.filters.ImmutabilityCallbackFilter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.Factory;

import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * CGLIB-based implementation
 */
public class CGLIBCodeFreeze implements CodeFreeze {

    private ConcurrentHashMap<Class, Factory> factories = new ConcurrentHashMap<>();
    private ConcurrentHashMap<Class, Boolean> enhanceableMap = new ConcurrentHashMap<>();

    /**
     * {@inheritDoc}
     */
    public <T> T freeze(T bean) {
        if (bean == null) {
            return null;
        }

        try {
            if (bean instanceof List) {
                return proxifyList((List) bean);
            }

            if (bean instanceof Set) {
                return proxifySet((Set) bean);
            }

            if (bean instanceof Map) {
                return proxifyMap((Map) bean);
            }

            return proxifyBean(bean);
        } catch (InstantiationException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * {@inheritDoc}
     */
    public boolean isEnhanceable(Class<?> type) {
        if (enhanceableMap.containsKey(type)) {
            return enhanceableMap.get(type);
        }
        boolean isEnhanceable = !Modifier.isFinal(type.getModifiers()) && hasParameterlessConstructor(type);
        enhanceableMap.putIfAbsent(type, isEnhanceable);
        return isEnhanceable;
    }

    private boolean hasParameterlessConstructor(Class<?> returnType) {
        return Collection.class.isAssignableFrom(returnType) || Map.class.isAssignableFrom(returnType) || Arrays
                .stream(returnType.getConstructors()).anyMatch(c -> c.getGenericParameterTypes().length == 0);
    }

    @SuppressWarnings("unchecked")
    private <T> T proxifyList(List list) throws InstantiationException, IllegalAccessException {
        ImmutableList.Builder<Object> builder = ImmutableList.builder();
        list.stream().forEach(bean -> builder.add(freeze(bean)));
        return (T) builder.build();
    }

    @SuppressWarnings("unchecked")
    private <T> T proxifySet(Set set) throws InstantiationException, IllegalAccessException {
        ImmutableSet.Builder<Object> builder = ImmutableSet.builder();
        set.stream().forEach(bean -> builder.add(freeze(bean)));
        return (T) builder.build();
    }

    @SuppressWarnings("unchecked")
    private <T> T proxifyMap(Map map) throws InstantiationException, IllegalAccessException {
        ImmutableMap.Builder<Object, Object> builder = ImmutableMap.builder();
        map.keySet().stream().forEach(key -> builder.put(freeze(key), freeze(map.get(key))));
        return (T) builder.build();
    }

    @SuppressWarnings("unchecked")
    private <T> T proxifyBean(T bean) throws IllegalAccessException, InstantiationException {
        if (!isEnhanceable(bean.getClass())) {
            return bean;
        }
        Factory factory = getFactory(bean.getClass());
        Callback[] callbacks = getCallbacks(bean);
        T newInstance = (T) factory.newInstance(callbacks);
        ExceptionMethodInterceptor exceptionCallback = (ExceptionMethodInterceptor) callbacks[1];
        // By default the ExceptionMethodInterceptor is not active to allow calling setters in class constructor
        // After class instance creation it should be immediately activated
        exceptionCallback.setActive(true);
        return newInstance;
    }

    private <T> Callback[] getCallbacks(T bean) {
        return new Callback[] { new EqualsMethodInterceptor(bean), new ExceptionMethodInterceptor(bean),
                new DelegatingMethodInterceptor(bean), new FreezingMethodInterceptor(this, bean) };
    }

    private Factory getFactory(Class<?> classToProxify) throws IllegalAccessException, InstantiationException {
        if (factories.containsKey(classToProxify)) {
            return factories.get(classToProxify);
        }
        Factory factory = createFactory(classToProxify);
        factories.putIfAbsent(classToProxify, factory);
        return factory;
    }

    private Factory createFactory(Class<?> classToProxify) throws IllegalAccessException, InstantiationException {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(classToProxify);
        enhancer.setCallbackFilter(new ImmutabilityCallbackFilter(this, classToProxify));
        enhancer.setCallbackTypes(new Class[] { EqualsMethodInterceptor.class, ExceptionMethodInterceptor.class,
                DelegatingMethodInterceptor.class, FreezingMethodInterceptor.class });
        Class proxyClass = enhancer.createClass();
        return (Factory) proxyClass.newInstance();
    }

}