All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.wl4g.infra.common.bean.ConfigBeanUtils Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2017 ~ 2025 the original author or authors. James Wong 
 *
 * 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.wl4g.infra.common.bean;

import static com.wl4g.infra.common.lang.Assert2.notNullOf;
import static com.wl4g.infra.common.reflect.ReflectionUtils2.findField;
import static com.wl4g.infra.common.reflect.ReflectionUtils2.isCompatibleType;
import static com.wl4g.infra.common.reflect.ReflectionUtils2.makeAccessible;
import static com.wl4g.infra.common.reflect.TypeUtils2.isSimpleCollectionType;
import static com.wl4g.infra.common.reflect.TypeUtils2.isSimpleType;
import static java.lang.String.format;
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
import static org.apache.commons.lang3.StringUtils.startsWithAny;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

import javax.annotation.Nullable;
import javax.validation.constraints.NotNull;

import com.wl4g.infra.common.reflect.ReflectionUtils2.FieldFilter;

/**
 * {@link ConfigBeanUtils}
 * 
 * @author James Wong <[email protected]>
 * @version 2022-04-20 v3.0.0
 * @since v3.0.0
 */
public abstract class ConfigBeanUtils {

    /**
     * The copies default configuration object properties deep to the destObj
     * configuration object, overriding as needed.
     * 
     * @param initObj
     * @param destObj
     * @param defaultObj
     * @return
     * @throws IllegalArgumentException
     * @throws IllegalAccessException
     */
    public static @NotNull  T configureWithDefault(
            @NotNull I initObj,
            @NotNull T destObj,
            @NotNull D defaultObj) throws IllegalArgumentException, IllegalAccessException {
        notNullOf(initObj, "initObj");
        notNullOf(destObj, "destObj");
        notNullOf(defaultObj, "defaultObj");

        if (initObj.getClass() != destObj.getClass()) {
            throw new IllegalArgumentException(format(
                    "The destObj configuration object must be of the exact same type as the initial configuration object. %s != %s",
                    destObj.getClass(), initObj.getClass()));
        }

        deepCopyFieldStateWithInit(initObj, destObj, defaultObj, BeanUtils2.DEFAULT_FIELD_FILTER,
                (
                        @Nullable Object init,
                        @NotNull Object dest,
                        @NotNull Field tf,
                        @NotNull Field sf,
                        @Nullable Object defaultPropertyValue) -> {

                    if (nonNull(defaultPropertyValue)) {
                        Object initObjPropertyValue = tf.get(init);
                        Object destObjPropertyValue = tf.get(dest);
                        boolean flag = false;
                        if (isNull(initObjPropertyValue)) {
                            if (isNull(destObjPropertyValue)) {
                                flag = true;
                            }
                        } else {
                            if ((isNull(destObjPropertyValue) || destObjPropertyValue.equals(initObjPropertyValue))
                                    && !defaultPropertyValue.equals(initObjPropertyValue)) {
                                flag = true;
                            }
                        }
                        if (flag) {
                            tf.setAccessible(true);
                            tf.set(dest, defaultPropertyValue);
                        }
                    }
                });

        return destObj;
    }

    /**
     * Calls the given callback on all fields of the destObj class, recursively
     * running the class hierarchy up to copy all declared fields.
* It will contain all the fields defined by all parent or superclasses. At * the same time, the destObj and the source object must be compatible. * * @param destObj * The destObj object to copy to * @param defaultObj * Default/Source object * @param ff * Field filter * @param fp * Customizable copyer * @throws IllegalArgumentException * @throws IllegalAccessException */ private static void deepCopyFieldStateWithInit( @Nullable Object initObj, @NotNull Object destObj, @NotNull Object defaultObj, @NotNull FieldFilter ff, @NotNull FieldProcessor2 fp) throws IllegalArgumentException, IllegalAccessException { if (!(destObj != null && defaultObj != null && ff != null && fp != null)) { throw new IllegalArgumentException("Target and source FieldFilter and FieldProcessor2 must not be null"); } // Check if the destObj is compatible with the source object Class destObjClass = destObj.getClass(), sourceClass = defaultObj.getClass(); if (!isCompatibleType(destObj.getClass(), defaultObj.getClass())) { throw new IllegalArgumentException( format("Incompatible the objects, destObj class: %s, source class: %s", destObjClass, sourceClass)); } Class destObjCls = destObj.getClass(); // [MARK0] do { doDeepCopyFieldsWithInit(destObjCls, initObj, destObj, defaultObj, ff, fp); } while ((destObjCls = destObjCls.getSuperclass()) != Object.class); } /** * Calls the given callback on all fields of the destObj class, recursively * running the class hierarchy up to copy all declared fields.
* Note: that it does not contain fields defined by the parent or super * class. At the same time, the destObj and the source object must be * compatible.
* Note: Attribute fields of parent and superclass are not included * * @param currentTargetClass * The level of the class currently copied to (upward recursion) * @param destObj * The destObj object to copy to * @param defaultObj * Source object * @param ff * Field filter * @param fp * Customizable copyer * @throws IllegalArgumentException * @throws IllegalAccessException */ private static void doDeepCopyFieldsWithInit( Class currentTargetClass, @Nullable Object initObj, @NotNull Object destObj, @NotNull Object defaultObj, @NotNull FieldFilter ff, @NotNull FieldProcessor2 fp) throws IllegalArgumentException, IllegalAccessException { if (isNull(currentTargetClass) || isNull(ff) || isNull(fp)) { throw new IllegalArgumentException( "Hierarchy current destObj class or source FieldFilter and FieldProcessor2 can't null"); } // Skip the current level copy. if (isNull(defaultObj) || isNull(destObj)) { return; } // Check is only required when the initial object is not empty. // @formatter:off // if (nonNull(initObj) && initObj.getClass() != destObj.getClass()) { // @formatter:on if (nonNull(initObj) && !destObj.getClass().isAssignableFrom(initObj.getClass())) { throw new IllegalArgumentException( format("The initial destObj object must be of the exact same type as the destObj object. %s != %s", initObj.getClass(), destObj.getClass())); } // Recursive traversal matching and processing Class sourceClass = defaultObj.getClass(); for (Field tf : currentTargetClass.getDeclaredFields()) { // Must be filtered over. // [BUGFIX]: for example when recursively getting // java.nio.charset.Charset, there will be an infinite loop stack // overflow (jvm8 defaults to 1024) if (Modifier.isFinal(tf.getModifiers()) || startsWithAny(tf.getDeclaringClass().getName(), "java.nio", "java.util", "org.apache.commons.lang", "org.springframework.util", "org.springframework.web.util", "org.springframework.boot.util")) { continue; } makeAccessible(tf); Object initObjPropertyValue = nonNull(initObj) ? tf.get(initObj) : null; Object destObjPropertyValue = tf.get(destObj); // See:[MARK0] Object defaultPropertyValue = null; Field sf = findField(sourceClass, tf.getName()); if (nonNull(sf)) { makeAccessible(sf); defaultPropertyValue = sf.get(defaultObj); } // Base type or collection type or enum? if (isSimpleType(tf.getType()) || isSimpleCollectionType(tf.getType()) || tf.getType().isEnum()) { // [MARK2] Filter matching property if (nonNull(fp) && ff.matches(tf)) { fp.doProcess(initObj, destObj, tf, sf, defaultPropertyValue); } } else { doDeepCopyFieldsWithInit(tf.getType(), initObjPropertyValue, destObjPropertyValue, defaultPropertyValue, ff, fp); } } } public static interface FieldProcessor2 { void doProcess( @Nullable Object initObj, @NotNull Object destObj, @NotNull Field tf, @NotNull Field sf, @Nullable Object defaultPropertyValue) throws IllegalArgumentException, IllegalAccessException; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy