Java 杂谈

Immutable类生成器

2019-01-28  本文已影响133人  蚊子squirrel

在项目开发过程中,有时需要将类定义成不可变(Immutable)类型,例如在一些暴露给第三方的接口参数对象,对于复杂多层次的自定义类,手工编写Immutable类是个繁琐且容易出错的工作,为此写了一个Immutable自动生成工具。

1. mutable(可变)和immutable(不可变)类型的区别
String var = "hello world";  
var.toUpperCase();  

toUpperCase()方法不会改变var中包含的数据“Hello word”。而是创建一个新的String对象并将其初始化为“HELLO WORLD”,然后返回这个新对象的引用。

2.mutable和immutable类型的优缺点
3.如何构造一个immutable类
4.复杂自定义类Immutable生成器

简单的类,可以通过第3节方法手动编写,但如果field中有大量自定义类,这些自定义类还包含很多自定义类filed,那么手工编写Immutable将会变得非常繁琐,会充斥大量的get方法以及赋值操作,很容易出错,另外与Array、List、Map等类型时,对象的深度拷贝都需要特殊处理。为此写一个Imuutable类的自动生成工具,具体代码如下:

package com.wwb.utils;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

import org.springframework.util.ClassUtils;

import com.google.common.base.Function;

/**
 * 不可变类(Immutable)生成工具类 功能:
 * 1.支持原生类型、Date、BigDecimal、Map、Set、List
 * 2.支持自定义类自动生成不可变类 3.支持集合类生成真正不可变集合
 * 
 * 限制:
 * 1.不支持不同包下的同名类
 * 2.不支持多层参数集合类,例如List<List<CustomizeClass>>,仅支持一层
 * 
 * 使用方法:
 * 1.ImmutableClassTool tool = new ImmutableClassTool();
 * 2.tool.addIngoreClass(xxx.class);//指定忽略的类型
 * 3.tool.addIngoreProperty("zzz");//指定忽略的属性名 4.String classStr = tool.generate(CustomizeClass.class);
 * 
 * @author wwb 2019-1-19
 */
public class ImmutableClassTool {
    private static final String CLASS_PREFIX = "Immutable";
    private static final String LINE_BREAK = "\n";
    private Set<String> unGeneratedClass = new HashSet<String>();
    private Set<String> generatedClass = new HashSet<String>();
    private Set<String> packageSet = new HashSet<String>();

    private Set<Class<?>> ignoreClass = new HashSet<Class<?>>();// 忽略的类
    private Set<String> ignoreProperty = new HashSet<String>();// 忽略的属性名

    public void addIngoreClass(Class<?> cls) {
        ignoreClass.add(cls);
    }

    public void addIngoreProperty(String propertyName) {
        ignoreProperty.add(propertyName);
    }

    private String generataImmutaleClass(Class<?> cls) throws ClassNotFoundException, IntrospectionException {
        if (generatedClass.contains(cls.getName())) {
            return LINE_BREAK;
        }
        StringBuilder builder = new StringBuilder();
        String srcClassName = cls.getSimpleName();
        String className = CLASS_PREFIX + srcClassName;
        builder.append("\n@Getter\n");
        builder.append("public class ");
        builder.append(className).append(" {\n");

        /* generate all fileds */
        builder.append(this.generateFields(cls));

        /* generate constructor */
        builder.append(this.generateConstructor(cls));
        builder.append("  }\n");
        generatedClass.add(cls.getName());
        unGeneratedClass.remove(cls.getName());
        return builder.toString();
    }

    public String generate(Class<?> cls) throws ClassNotFoundException, IntrospectionException {
        StringBuilder builder = new StringBuilder();
        String srcClassName = cls.getSimpleName();
        String className = CLASS_PREFIX + srcClassName;
        builder.append("\n@Getter\n");
        builder.append("public class ");
        builder.append(className).append(" {\n");

        builder.append(generateFields(cls));
        builder.append(generateConstructor(cls));
        builder.append(generateInnerImmutableClass());

        builder.insert(0, generateComment(cls));
        builder.insert(0, generatePackageExpr(cls));
        builder.append("}\n");
        return builder.toString();
    }

    /**
     * generate fields
     * 
     * @param cls
     * @return
     */
    private String generateFields(Class<?> cls) {
        /* generate all fileds */
        StringBuilder builder = new StringBuilder();
        Field[] fields = cls.getDeclaredFields();
        for (Field field : fields) {
            if (hasGetMethod(field, cls)) {
                builder.append("  private final ");
                builder.append(getFieldType(field));
                builder.append(" ");
                builder.append(field.getName()).append(";\n");
            }
        }
        return builder.toString();
    }

    /**
     * generate constructor
     * 
     * @param cls
     * @return
     * @throws IntrospectionException
     */
    private String generateConstructor(Class<?> cls) throws IntrospectionException {
        StringBuilder builder = new StringBuilder();
        String srcClassName = cls.getSimpleName();
        String className = CLASS_PREFIX + srcClassName;
        Field[] fields = cls.getDeclaredFields();

        String paramName = srcClassName.substring(0, 1).toLowerCase() + srcClassName.substring(1);
        builder.append("  public ").append(className).append("(").append(srcClassName).append(" ").append(paramName)
                .append("){\n");
        for (Field filed : fields) {
            if (hasGetMethod(filed, cls)) {
                builder.append("     this.").append(filed.getName()).append(" = ");
                builder.append(generateGetExpr(filed, paramName, cls)).append(";\n");

            }
        }
        builder.append("    }\n");
        return builder.toString();
    }

    private String generateInnerImmutableClass() throws ClassNotFoundException, IntrospectionException {
        /* generate immutable class */
        StringBuilder builder = new StringBuilder();
        while (unGeneratedClass.size() > 0) {
            HashSet<String> unGeneratedClassCopy = new HashSet<String>();
            unGeneratedClassCopy.addAll(unGeneratedClass);
            for (String clsName : unGeneratedClassCopy) {
                builder.append(generataImmutaleClass(Class.forName(clsName)));
            }
        }
        return builder.toString();
    }

    private boolean isImmutable(Class<?> type) {
        if (ClassUtils.isPrimitiveOrWrapper(type) || type.isAssignableFrom(String.class)
                || type.isAssignableFrom(BigDecimal.class) || type.isAssignableFrom(Date.class)
                || type.isAssignableFrom(Map.class) || type.isAssignableFrom(List.class)
                || type.isAssignableFrom(Set.class)) {
            return true;
        } else if (type.isArray() && isImmutable(type.getComponentType())) {
            return true;
        } else if (this.ignoreClass.contains(type)) {
            return true;
        }
        return false;
    }

    private boolean hasGetMethod(Field filed, Class<?> cls) {
        String filedName = filed.getName();
        try {
            PropertyDescriptor descriptor = new PropertyDescriptor(filedName, cls);
            return descriptor.getReadMethod() != null;
        } catch (IntrospectionException e) {
            return false;
        }

    }

    private String generateGetExpr(Field field, String paramName, Class<?> cls) throws IntrospectionException {
        StringBuilder builder = new StringBuilder();
        String filedName = field.getName();

        Class<?> type = field.getType();
        String getMethodExpr = null;
        PropertyDescriptor descriptor = new PropertyDescriptor(filedName, cls);
        Method readMethod = descriptor.getReadMethod();
        if (readMethod != null) {
            getMethodExpr = paramName + "." + readMethod.getName() + "()";
        }

        if (ClassUtils.isPrimitiveOrWrapper(type) || type.isAssignableFrom(String.class)
                || type.isAssignableFrom(BigDecimal.class) || this.ignoreProperty.contains(field.getName())) {
            builder.append(getMethodExpr);
        } else if (type.isAssignableFrom(Date.class)) {
            builder.append("(Date)").append(getMethodExpr).append(".clone()");
        } else if (type.isArray()) {
            builder.append(getArrayTransformMethod(type, getMethodExpr));
        } else if (type.isAssignableFrom(Map.class)) {
            builder.append("ImmutableMap.copyOf(");
            Type genericType = field.getGenericType();
            if (genericType instanceof ParameterizedType) {
                Class<?> paramClass = (Class<?>) ((ParameterizedType) genericType).getActualTypeArguments()[1];
                if (isImmutable(paramClass)) {
                    builder.append(getMethodExpr);
                } else {
                    builder.append(getGuavaTransformMethod(paramClass, getMethodExpr, Map.class));
                }
            }
            builder.append(")");
            this.packageSet.add("com.google.common.collect.ImmutableMap");
        } else if (type.isAssignableFrom(List.class)) {
            builder.append("ImmutableList.copyOf(");
            Type genericType = field.getGenericType();
            if (genericType instanceof ParameterizedType) {
                Class<?> paramClass = (Class<?>) ((ParameterizedType) genericType).getActualTypeArguments()[0];
                if (isImmutable(paramClass)) {
                    builder.append(getMethodExpr);
                } else {
                    builder.append(getGuavaTransformMethod(paramClass, getMethodExpr, List.class));
                }
            }
            builder.append(")");
            this.packageSet.add("com.google.common.collect.ImmutableList");
        } else if (type.isAssignableFrom(Set.class)) {
            builder.append("ImmutableSet.copyOf(");
            Type genericType = field.getGenericType();
            if (genericType instanceof ParameterizedType) {
                Class<?> paramClass = (Class<?>) ((ParameterizedType) genericType).getActualTypeArguments()[0];
                if (isImmutable(paramClass)) {
                    builder.append(getMethodExpr);
                } else {
                    builder.append(getGuavaTransformMethod(paramClass, getMethodExpr, Set.class));
                }
            }
            builder.append(")");
            this.packageSet.add("com.google.common.collect.ImmutableSet");
        } else {
            builder.append("new ");
            builder.append(CLASS_PREFIX);
            builder.append(field.getType().getSimpleName());
            builder.append("(").append(getMethodExpr).append(")");
        }
        return builder.toString();
    }

    private String generatePackageExpr(Class<?> cls) {
        StringBuilder builder = new StringBuilder();
        builder.append("package ").append(cls.getPackage().getName()).append(";\n\n");
        for (String pack : this.packageSet) {
            builder.append("import ").append(pack).append(";\n");
        }
        builder.append("import lombok.Getter;\n");
        return builder.toString();
    }

    private String getFieldType(Field field) {
        Class<?> type = field.getType();
        if (ClassUtils.isPrimitiveOrWrapper(type)) {
            return type.getSimpleName();
        } else if (type.isAssignableFrom(String.class) || type.isAssignableFrom(BigDecimal.class)
                || type.isAssignableFrom(Date.class)) {
            this.packageSet.add(type.getName());
            return type.getSimpleName();
        } else if (type.isAssignableFrom(Map.class) || type.isAssignableFrom(List.class)
                || type.isAssignableFrom(Set.class)) {
            this.packageSet.add(type.getName());
            Type genericType = field.getGenericType();

            if (genericType instanceof ParameterizedType) {
                Type[] actualTypeArguments = ((ParameterizedType) genericType).getActualTypeArguments();
                if (type.isAssignableFrom(Map.class)) {
                    Class<?> keyClass = (Class<?>) actualTypeArguments[0];
                    Class<?> valueClass = (Class<?>) actualTypeArguments[1];
                    return "Map<" + keyClass.getSimpleName() + "," + getParameterClass(valueClass) + ">";
                }
                if (type.isAssignableFrom(List.class)) {
                    return "List<" + getParameterClass((Class<?>) actualTypeArguments[0]) + ">";
                }
                if (type.isAssignableFrom(Set.class)) {
                    return "Set<" + getParameterClass((Class<?>) actualTypeArguments[0]) + ">";
                }
            }
            return genericType.toString();
        } else if (this.ignoreProperty.contains(field.getName())) {
            this.packageSet.add(type.getName());
            return type.getSimpleName();
        } else {
            return getParameterClass(type);
        }
    }

    private String getParameterClass(Class<?> parameterClass) {
        if (isImmutable(parameterClass)) {
            return parameterClass.getSimpleName();
        } else {
            String clsName = parameterClass.isArray() ? parameterClass.getComponentType().getName()
                    : parameterClass.getName();
            this.unGeneratedClass.add(clsName);
            this.packageSet.add(clsName);
            return CLASS_PREFIX + parameterClass.getSimpleName();
        }
    }

    private String getGuavaTransformMethod(Class<?> paramClass, String getMethodExpr, Class<?> collectionType) {
        StringBuilder builder = new StringBuilder();
        if (collectionType.equals(List.class)) {
            builder.append("ImmutableClassTool.transformList(");
        } else if (collectionType.equals(Set.class)) {
            builder.append("ImmutableClassTool.transformSet(");
        } else if (collectionType.equals(Map.class)) {
            builder.append("ImmutableClassTool.transformMapValues(");
        } else {
            return "";
        }

        builder.append(getMethodExpr);
        builder.append(", new Function<");
        builder.append(paramClass.getSimpleName());
        builder.append(",");
        builder.append(getParameterClass(paramClass));
        builder.append(">(){\n     @Override\n     public " + getParameterClass(paramClass) + " apply("
                + paramClass.getSimpleName() + " input) {\n       return new " + getParameterClass(paramClass)
                + "(input);\n     }})");
        this.packageSet.add("com.google.common.base.Function");
        this.packageSet.add(this.getClass().getName());
        this.packageSet.add(paramClass.getName());
        return builder.toString();
    }

    private String getArrayTransformMethod(Class<?> paramClass, String getMethodExpr) {
        if (paramClass.isArray()) {
            Class<?> elementType = paramClass.getComponentType();
            if (!this.isImmutable(elementType)) {
                StringBuilder builder = new StringBuilder();
                builder.append("ImmutableClassTool.transformArray(");
                builder.append(getMethodExpr);
                builder.append(",");
                builder.append(getParameterClass(elementType));
                builder.append(".class, new Function<");
                builder.append(elementType.getSimpleName());
                builder.append(",");
                builder.append(getParameterClass(elementType));
                builder.append(">(){\n     @Override\n     public " + getParameterClass(elementType) + " apply("
                        + elementType.getSimpleName() + " input) {\n       return new " + getParameterClass(elementType)
                        + "(input);\n     }})");
                this.packageSet.add("com.google.common.base.Function");
                this.packageSet.add(elementType.getName());
                this.packageSet.add(this.getClass().getName());
                return builder.toString();
            }
        }
        return getMethodExpr;
    }

    private String generateComment(Class<?> cls) {
        StringBuilder builder = new StringBuilder();
        builder.append("\n/**\n * Immutable class of ");
        builder.append(cls.getName());
        builder.append("\n * generated by ImmutableClassTool\n */");
        return builder.toString();
    }

    @SuppressWarnings("unchecked")
    public static <F, T> T[] transformArray(F[] fromArray, Class<T> type, Function<? super F, ? extends T> function) {
        T[] toArray = (T[]) Array.newInstance(type, fromArray.length);
        for (int i = 0; i < fromArray.length; i++) {
            toArray[i] = function.apply(fromArray[i]);
        }
        return toArray;
    }

    public static <F, T> List<T> transformList(List<F> fromList, Function<? super F, ? extends T> function) {
        List<T> list = (fromList instanceof LinkedList) ? new LinkedList<T>() : new ArrayList<T>(fromList.size());
        for (F f : fromList) {
            list.add(function.apply(f));
        }
        return list;
    }

    public static <F, T> Set<T> transformSet(Set<F> fromSet, Function<? super F, ? extends T> function) {
        Set<T> set = (fromSet instanceof TreeSet) ? new TreeSet<T>() : new HashSet<T>(fromSet.size());
        for (F f : fromSet) {
            set.add(function.apply(f));
        }
        return set;
    }

    public static <K, V1, V2> Map<K, V2> transformMapValues(Map<K, V1> fromMap, Function<? super V1, V2> function) {
        Map<K, V2> map = (fromMap instanceof TreeMap) ? new TreeMap<K, V2>() : new HashMap<K, V2>();

        for (Entry<K, V1> entry : fromMap.entrySet()) {
            map.put(entry.getKey(), function.apply(entry.getValue()));
        }
        return map;
    }

}


上一篇下一篇

猜你喜欢

热点阅读