SimpleBeanSchemaHandler.java

package space.sunqian.common.object.data.handlers;

import space.sunqian.annotations.Nonnull;
import space.sunqian.annotations.Nullable;
import space.sunqian.common.object.data.ObjectSchemaParser;
import space.sunqian.common.invoke.Invocable;

import java.lang.reflect.Method;
import java.util.Objects;

/**
 * This is an implementation of {@link ObjectSchemaParser.Handler} which basically follows the <a
 * href="https://www.oracle.com/java/technologies/javase/javabeans-spec.html">JavaBeans</a> style, inheriting from
 * {@link AbstractObjectSchemaHandler} and overriding the {@link AbstractObjectSchemaHandler#resolveAccessor(Method)}
 * method.
 * <p>
 * This implementation resolves {@code getXxx} or {@code isXxx} methods as getters and {@code setXxx} methods as setters
 * according to lower camel case naming conventions.
 *
 * @author sunqian
 */
public class SimpleBeanSchemaHandler extends AbstractObjectSchemaHandler {

    @Override
    protected @Nullable AccessorInfo resolveAccessor(@Nonnull Method method) {
        int parameterCount = method.getParameterCount();
        switch (parameterCount) {
            case 0:// maybe getter
                return tryGetter(method);
            case 1:// maybe setter
                return trySetter(method);
        }
        return null;
    }

    private @Nullable AccessorInfo tryGetter(@Nonnull Method method) {
        Class<?> returnType = method.getReturnType();
        if (Objects.equals(returnType, void.class)) {
            return null;
        }
        String propertyName = propertyNameFromGetter(method);
        if (propertyName == null) {
            return null;
        }
        return new AccessorInfoImpl(propertyName, Invocable.of(method), true);
    }

    private @Nullable String propertyNameFromGetter(@Nonnull Method method) {
        // getter's name should be getXxx or isXxx
        String methodName = method.getName();
        if (methodName.length() > 3 && methodName.startsWith("get")) {
            if (!Character.isUpperCase(methodName.charAt(3))) {
                return null;
            }
            if (methodName.length() == 4) {
                return String.valueOf(Character.toLowerCase(methodName.charAt(3)));
            }
            if (Character.isUpperCase(methodName.charAt(4))) {
                return methodName.substring(3);
            } else {
                return Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4);
            }
        }
        if (methodName.length() > 2 && methodName.startsWith("is")) {
            if (!Character.isUpperCase(methodName.charAt(2))) {
                return null;
            }
            if (methodName.length() == 3) {
                return String.valueOf(Character.toLowerCase(methodName.charAt(2)));
            }
            if (Character.isUpperCase(methodName.charAt(3))) {
                return methodName.substring(2);
            } else {
                return Character.toLowerCase(methodName.charAt(2)) + methodName.substring(3);
            }
        }
        return null;
    }

    private @Nullable AccessorInfo trySetter(@Nonnull Method method) {
        String propertyName = propertyNameFromSetter(method);
        if (propertyName == null) {
            return null;
        }
        return new AccessorInfoImpl(propertyName, Invocable.of(method), false);
    }

    private @Nullable String propertyNameFromSetter(@Nonnull Method method) {
        // setter's name should be setXxx
        String methodName = method.getName();
        if (methodName.length() > 3 && methodName.startsWith("set")) {
            if (!Character.isUpperCase(methodName.charAt(3))) {
                return null;
            }
            if (methodName.length() == 4) {
                return String.valueOf(Character.toLowerCase(methodName.charAt(3)));
            }
            if (Character.isUpperCase(methodName.charAt(4))) {
                return methodName.substring(3);
            } else {
                return Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4);
            }
        }
        return null;
    }

    private static final class AccessorInfoImpl implements AccessorInfo {

        private final @Nonnull String name;
        private final @Nonnull Invocable accessor;
        private final boolean isGetter;

        private AccessorInfoImpl(@Nonnull String name, @Nonnull Invocable accessor, boolean isGetter) {
            this.name = name;
            this.accessor = accessor;
            this.isGetter = isGetter;
        }

        @Override
        public @Nonnull String propertyName() {
            return name;
        }

        @Override
        public @Nonnull Invocable accessor() {
            return accessor;
        }

        @Override
        public boolean isGetter() {
            return isGetter;
        }
    }
}