de.escalon.hypermedia.action.ActionInputParameter.java Source code

Java tutorial

Introduction

Here is the source code for de.escalon.hypermedia.action.ActionInputParameter.java

Source

/*
 * Copyright (c) 2014. Escalon System-Entwicklung, Dietrich Schulten
 *
 * 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 de.escalon.hypermedia.action;

import de.escalon.hypermedia.DataType;
import org.jetbrains.annotations.Nullable;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.Property;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ValueConstants;

import java.lang.annotation.Annotation;
import java.util.*;

/**
 * Holds a method parameter value.
 *
 * @author Dietrich Schulten
 */
public class ActionInputParameter {

    public static final String MIN = "min";
    public static final String MAX = "max";
    public static final String STEP = "step";
    public static final String MIN_LENGTH = "minLength";
    public static final String MAX_LENGTH = "maxLength";
    public static final String PATTERN = "pattern";
    private final TypeDescriptor typeDescriptor;
    private final RequestBody requestBody;
    private final RequestParam requestParam;
    private final PathVariable pathVariable;
    private MethodParameter methodParameter;
    private Object value;
    private Boolean arrayOrCollection = null;
    private Input inputAnnotation;
    private Map<String, Object> inputConstraints = new HashMap<String, Object>();

    private ConversionService conversionService = new DefaultFormattingConversionService();

    /**
     * Creates input parameter descriptor.
     *
     * @param methodParameter   to describe
     * @param value             used during sample invocation
     * @param conversionService to apply to value
     */
    public ActionInputParameter(MethodParameter methodParameter, Object value,
            ConversionService conversionService) {
        this.methodParameter = methodParameter;
        this.value = value;
        this.requestBody = methodParameter.getParameterAnnotation(RequestBody.class);
        this.requestParam = methodParameter.getParameterAnnotation(RequestParam.class);
        this.pathVariable = methodParameter.getParameterAnnotation(PathVariable.class);
        // always determine input constraints,
        // might be a nested property which is neither requestBody, requestParam nor pathVariable
        this.inputAnnotation = methodParameter.getParameterAnnotation(Input.class);
        if (inputAnnotation != null) {
            putInputConstraint(MIN, Integer.MIN_VALUE, inputAnnotation.min());
            putInputConstraint(MAX, Integer.MAX_VALUE, inputAnnotation.max());
            putInputConstraint(MIN_LENGTH, Integer.MIN_VALUE, inputAnnotation.minLength());
            putInputConstraint(MAX_LENGTH, Integer.MAX_VALUE, inputAnnotation.maxLength());
            putInputConstraint(STEP, 0, inputAnnotation.step());
            putInputConstraint(PATTERN, "", inputAnnotation.pattern());
        }

        this.conversionService = conversionService;
        this.typeDescriptor = TypeDescriptor.nested(methodParameter, 0);
    }

    /**
     * Creates new ActionInputParameter with default formatting conversion service.
     *
     * @param methodParameter holding metadata about the parameter
     * @param value           during sample method invocation
     */
    public ActionInputParameter(MethodParameter methodParameter, Object value) {
        this(methodParameter, value, new DefaultFormattingConversionService());
    }

    private void putInputConstraint(String key, Object defaultValue, Object value) {
        if (!value.equals(defaultValue)) {
            inputConstraints.put(key, value);
        }
    }

    /**
     * The value of the parameter at invocation time.
     *
     * @return value, may be null
     */
    public Object getCallValue() {
        return value;
    }

    /**
     * The value of the parameter at invocation time, formatted according to conversion configuration.
     *
     * @return value, may be null
     */
    @Nullable
    public String getCallValueFormatted() {
        String ret;
        if (value == null) {
            ret = null;
        } else {
            ret = (String) conversionService.convert(value, typeDescriptor, TypeDescriptor.valueOf(String.class));
        }
        return ret;
    }

    /**
     * Gets parameter type for input field according to {@link Type} annotation.
     *
     * @return the type
     */
    public Type getHtmlInputFieldType() {
        final Type ret;
        if (inputAnnotation == null || inputAnnotation.value() == Type.FROM_JAVA) {
            if (isNumber()) {
                ret = Type.NUMBER;
            } else {
                ret = Type.TEXT;
            }
        } else {
            ret = inputAnnotation.value();
        }
        return ret;
    }

    public boolean isRequestBody() {
        return requestBody != null;
    }

    public boolean isRequestParam() {
        return requestParam != null;
    }

    public boolean isPathVariable() {
        return pathVariable != null;
    }

    public boolean hasInputConstraints() {
        return !inputConstraints.isEmpty();
    }

    public <T extends Annotation> T getAnnotation(Class<T> annotation) {
        return methodParameter.getParameterAnnotation(annotation);
    }

    public Object[] getPossibleValues(ActionDescriptor actionDescriptor) {
        try {
            Class<?> parameterType = getParameterType();
            Object[] possibleValues;
            Class<?> nested;
            if (Enum[].class.isAssignableFrom(parameterType)) {
                possibleValues = parameterType.getComponentType().getEnumConstants();
            } else if (Enum.class.isAssignableFrom(parameterType)) {
                possibleValues = parameterType.getEnumConstants();
            } else if (Collection.class.isAssignableFrom(parameterType)
                    && Enum.class.isAssignableFrom(nested = TypeDescriptor.nested(methodParameter, 1).getType())) {
                possibleValues = nested.getEnumConstants();
            } else {
                Select select = methodParameter.getParameterAnnotation(Select.class);
                if (select != null) {
                    Class<? extends Options> options = select.options();
                    Options instance = options.newInstance();
                    List<Object> from = new ArrayList<Object>();
                    for (String paramName : select.args()) {
                        ActionInputParameter parameterValue = actionDescriptor.getActionInputParameter(paramName);
                        if (parameterValue != null) {
                            from.add(parameterValue.getCallValue());
                        }
                    }

                    Object[] args = from.toArray();
                    possibleValues = instance.get(select.value(), args);
                } else {
                    possibleValues = new Object[0];
                }
            }
            return possibleValues;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public Object[] getPossibleValues(Property property, ActionDescriptor actionDescriptor) {
        // TODO remove code duplication of getPossibleValues
        try {
            Class<?> parameterType = property.getType();
            Object[] possibleValues;
            Class<?> nested;
            if (Enum[].class.isAssignableFrom(parameterType)) {
                possibleValues = parameterType.getComponentType().getEnumConstants();
            } else if (Enum.class.isAssignableFrom(parameterType)) {
                possibleValues = parameterType.getEnumConstants();
            } else if (Collection.class.isAssignableFrom(parameterType)
                    && Enum.class.isAssignableFrom(nested = TypeDescriptor.nested(property, 1).getType())) {
                possibleValues = nested.getEnumConstants();
            } else {
                Annotation[][] parameterAnnotations = property.getWriteMethod().getParameterAnnotations();
                // setter has exactly one param
                Select select = getSelectAnnotationFromFirstParam(parameterAnnotations[0]);
                if (select != null) {
                    Class<? extends Options> optionsClass = select.options();
                    Options options = optionsClass.newInstance();
                    // collect call values to pass to options.get
                    List<Object> from = new ArrayList<Object>();
                    for (String paramName : select.args()) {
                        ActionInputParameter parameterValue = actionDescriptor.getActionInputParameter(paramName);
                        if (parameterValue != null) {
                            from.add(parameterValue.getCallValue());
                        }
                    }

                    Object[] args = from.toArray();
                    possibleValues = options.get(select.value(), args);
                } else {
                    possibleValues = new Object[0];
                }
            }
            return possibleValues;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private Select getSelectAnnotationFromFirstParam(Annotation[] parameterAnnotation) {
        Select select = null;
        Annotation[] annotationsOnParameter = parameterAnnotation;
        for (Annotation annotation : annotationsOnParameter) {
            if (annotation.getClass() == Select.class) {
                select = (Select) annotation;
                break;
            }
        }
        return select;
    }

    public boolean isArrayOrCollection() {
        if (arrayOrCollection == null) {
            Class<?> parameterType = getParameterType();
            arrayOrCollection = DataType.isArrayOrCollection(parameterType);
        }
        return arrayOrCollection;
    }

    public boolean isBoolean() {
        return DataType.isBoolean(getParameterType());
    }

    public boolean isNumber() {
        return DataType.isNumber(getParameterType());
    }

    public boolean isRequired() {
        boolean ret;
        if (isRequestBody()) {
            ret = requestBody.required();
        } else if (isRequestParam()) {
            ret = ValueConstants.DEFAULT_NONE != requestParam.defaultValue() || requestParam.required();
        } else {
            ret = true;
        }
        return ret;
    }

    /**
     * Determines default value of request param, if available.
     *
     * @return value or null
     */
    public String getDefaultValue() {
        String ret;
        if (isRequestParam()) {
            ret = ValueConstants.DEFAULT_NONE != requestParam.defaultValue() ? requestParam.defaultValue() : null;
        } else {
            ret = null;
        }
        return ret;
    }

    public Object[] getCallValues() {
        Object[] callValues;
        if (!isArrayOrCollection()) {
            throw new UnsupportedOperationException("parameter is not an array or collection");
        }
        Object callValue = getCallValue();
        if (callValue == null) {
            callValues = new Object[0];
        } else {
            Class<?> parameterType = getParameterType();
            if (parameterType.isArray()) {
                callValues = (Object[]) callValue;
            } else {
                callValues = ((Collection<?>) callValue).toArray();
            }
        }
        return callValues;
    }

    public boolean hasCallValue() {
        return value != null;
    }

    public String getParameterName() {
        return methodParameter.getParameterName();
    }

    public Class<?> getParameterType() {
        return methodParameter.getParameterType();
    }

    public java.lang.reflect.Type getGenericParameterType() {
        return methodParameter.getGenericParameterType();
    }

    public Class<?> getNestedParameterType() {
        return methodParameter.getNestedParameterType();
    }

    public Map<String, Object> getInputConstraints() {
        return inputConstraints;
    }

}