org.lastaflute.doc.generator.ActionDocumentGenerator.java Source code

Java tutorial

Introduction

Here is the source code for org.lastaflute.doc.generator.ActionDocumentGenerator.java

Source

/*
 * Copyright 2014-2017 the original author or authors.
 *
 * 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 org.lastaflute.doc.generator;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import org.dbflute.jdbc.Classification;
import org.dbflute.optional.OptionalThing;
import org.dbflute.util.DfCollectionUtil;
import org.dbflute.util.DfReflectionUtil;
import org.dbflute.util.DfReflectionUtil.ReflectionFailureException;
import org.dbflute.util.DfStringUtil;
import org.lastaflute.core.json.JsonMappingOption;
import org.lastaflute.core.json.JsonMappingOption.JsonFieldNaming;
import org.lastaflute.core.util.ContainerUtil;
import org.lastaflute.di.core.ComponentDef;
import org.lastaflute.di.core.LaContainer;
import org.lastaflute.di.core.factory.SingletonLaContainerFactory;
import org.lastaflute.doc.meta.ActionDocMeta;
import org.lastaflute.doc.meta.TypeDocMeta;
import org.lastaflute.doc.reflector.SourceParserReflector;
import org.lastaflute.doc.util.LaDocReflectionUtil;
import org.lastaflute.web.Execute;
import org.lastaflute.web.UrlChain;
import org.lastaflute.web.path.ActionPathResolver;
import org.lastaflute.web.ruts.config.ActionExecute;
import org.lastaflute.web.ruts.config.ModuleConfig;
import org.lastaflute.web.ruts.multipart.MultipartFormFile;
import org.lastaflute.web.util.LaModuleConfigUtil;

import com.google.gson.FieldNamingPolicy;

// package of this class should be under lastaflute but no fix for compatible
/**
 * @author p1us2er0
 * @author jflute
 * @since 0.5.0-sp9 of UTFlute (2015/09/18 Friday)
 */
public class ActionDocumentGenerator extends BaseDocumentGenerator {

    // ===================================================================================
    //                                                                          Definition
    //                                                                          ==========
    /** list of suppressed fields, e.g. enhanced fields by JaCoCo. */
    protected static final Set<String> SUPPRESSED_FIELD_SET;
    static {
        SUPPRESSED_FIELD_SET = DfCollectionUtil.newHashSet("$jacocoData");
    }

    protected static final List<String> TARGET_SUFFIX_LIST;
    static {
        TARGET_SUFFIX_LIST = Arrays.asList("Form", "Body", "Bean", "Result");
    }

    protected static final List<Class<?>> NATIVE_TYPE_LIST = Arrays.asList(void.class, boolean.class, byte.class,
            int.class, long.class, float.class, double.class, Void.class, Byte.class, Boolean.class, Integer.class,
            Byte.class, Long.class, Float.class, Double.class, String.class, Map.class, byte[].class, Byte[].class,
            Date.class, LocalDate.class, LocalDateTime.class, LocalTime.class, MultipartFormFile.class);

    // ===================================================================================
    //                                                                           Attribute
    //                                                                           =========
    /** The list of source directory. (NotNull) */
    protected final List<String> srcDirList;

    /** depth of analyzed target, to avoid cyclic analyzing. */
    protected int depth; // #question depth count down? by jflute

    /** The optional reflector of source parser, e.g. java parser. (NotNull, EmptyAllowed) */
    protected final OptionalThing<SourceParserReflector> sourceParserReflector;

    // ===================================================================================
    //                                                                         Constructor
    //                                                                         ===========
    public ActionDocumentGenerator(List<String> srcDirList, int depth,
            OptionalThing<SourceParserReflector> sourceParserReflector) {
        this.srcDirList = srcDirList;
        this.depth = depth;
        this.sourceParserReflector = sourceParserReflector;
    }

    // ===================================================================================
    //                                                                            Generate
    //                                                                            ========
    public List<ActionDocMeta> generateActionDocMetaList() { // the list is per execute method
        final List<String> actionComponentNameList = findActionComponentNameList();
        final List<ActionDocMeta> metaList = DfCollectionUtil.newArrayList();
        final ModuleConfig moduleConfig = LaModuleConfigUtil.getModuleConfig();
        actionComponentNameList.forEach(componentName -> { // per action class
            moduleConfig.findActionMapping(componentName).alwaysPresent(actionMapping -> {
                final Class<?> actionClass = actionMapping.getActionDef().getComponentClass();
                final List<Method> methodList = DfCollectionUtil.newArrayList();
                sourceParserReflector.ifPresent(sourceParserReflector -> {
                    methodList.addAll(sourceParserReflector.getMethodListOrderByDefinition(actionClass));
                });
                if (methodList.isEmpty()) { // no java parser, use normal reflection
                    methodList
                            .addAll(Arrays.stream(actionClass.getMethods()).sorted(Comparator.comparing(method -> {
                                return method.getName();
                            })).collect(Collectors.toList()));
                }
                methodList.forEach(method -> { // contains all methods
                    if (method.getAnnotation(Execute.class) != null) { // only execute method here
                        final ActionExecute actionExecute = actionMapping.getActionExecute(method);
                        if (actionExecute != null && !exceptsActionExecute(actionExecute)) {
                            final ActionDocMeta actionDocMeta = createActionDocMeta(actionExecute);
                            metaList.add(actionDocMeta);
                        }
                    }
                });
            });
        });
        return metaList;
    }

    protected boolean exceptsActionExecute(ActionExecute actionExecute) { // may be overridden
        if (suppressActionExecute(actionExecute)) { // for compatible
            return true;
        }
        return false;
    }

    @Deprecated
    protected boolean suppressActionExecute(ActionExecute actionExecute) {
        return false;
    }

    // ===================================================================================
    //                                                                      Action DocMeta
    //                                                                      ==============
    protected ActionDocMeta createActionDocMeta(ActionExecute execute) {
        final ActionDocMeta actionDocMeta = new ActionDocMeta();
        final Class<?> actionClass = execute.getActionMapping().getActionDef().getComponentClass();
        final UrlChain urlChain = new UrlChain(actionClass);
        final String urlPattern = execute.getPreparedUrlPattern().getResolvedUrlPattern();
        if (!"index".equals(urlPattern)) {
            urlChain.moreUrl(urlPattern);
        }

        // action item
        actionDocMeta.setUrl(getActionPathResolver().toActionUrl(actionClass, urlChain));

        // class item
        final Method executeMethod = execute.getExecuteMethod();
        final Class<?> methodDeclaringClass = executeMethod.getDeclaringClass(); // basically same as componentClass
        actionDocMeta.setType(methodDeclaringClass);
        actionDocMeta.setTypeName(adjustTypeName(methodDeclaringClass));
        actionDocMeta.setSimpleTypeName(adjustSimpleTypeName(methodDeclaringClass));

        // field item
        actionDocMeta.setFieldTypeDocMetaList(Arrays.stream(methodDeclaringClass.getDeclaredFields()).map(field -> {
            final TypeDocMeta typeDocMeta = new TypeDocMeta();
            typeDocMeta.setName(field.getName());
            typeDocMeta.setType(field.getType());
            typeDocMeta.setTypeName(adjustTypeName(field.getGenericType()));
            typeDocMeta.setSimpleTypeName(adjustSimpleTypeName((field.getGenericType())));
            typeDocMeta.setAnnotationTypeList(Arrays.asList(field.getAnnotations()));
            typeDocMeta.setAnnotationList(analyzeAnnotationList(typeDocMeta.getAnnotationTypeList()));

            sourceParserReflector.ifPresent(sourceParserReflector -> {
                sourceParserReflector.reflect(typeDocMeta, field.getType());
            });
            return typeDocMeta;
        }).collect(Collectors.toList()));

        // method item
        actionDocMeta.setMethodName(executeMethod.getName());

        // annotation item
        final List<Annotation> annotationList = DfCollectionUtil.newArrayList();
        annotationList.addAll(Arrays.asList(methodDeclaringClass.getAnnotations()));
        annotationList.addAll(Arrays.asList(executeMethod.getAnnotations()));
        actionDocMeta.setAnnotationTypeList(annotationList); // contains both action and execute method
        actionDocMeta.setAnnotationList(analyzeAnnotationList(annotationList));

        // in/out item (parameter, form, return)
        final List<TypeDocMeta> parameterTypeDocMetaList = DfCollectionUtil.newArrayList();
        Arrays.stream(executeMethod.getParameters()).filter(parameter -> {
            return !(execute.getFormMeta().isPresent()
                    && execute.getFormMeta().get().getFormType().equals(parameter.getType()));
        }).forEach(parameter -> { // except form parameter here
            final StringBuilder builder = new StringBuilder();
            builder.append("{").append(parameter.getName()).append("}");
            actionDocMeta.setUrl(actionDocMeta.getUrl().replaceFirst("\\{\\}", builder.toString()));
            parameterTypeDocMetaList.add(analyzeMethodParameter(parameter));
        });
        actionDocMeta.setParameterTypeDocMetaList(parameterTypeDocMetaList);
        analyzeFormClass(execute).ifPresent(formTypeDocMeta -> {
            actionDocMeta.setFormTypeDocMeta(formTypeDocMeta);
        });
        actionDocMeta.setReturnTypeDocMeta(analyzeReturnClass(executeMethod));

        // extension item (url, return, comment...)
        sourceParserReflector.ifPresent(sourceParserReflector -> {
            sourceParserReflector.reflect(actionDocMeta, executeMethod);
        });

        return actionDocMeta;
    }

    // ===================================================================================
    //                                                                             Analyze
    //                                                                             =======
    // -----------------------------------------------------
    //                                     Analyze Parameter
    //                                     -----------------
    protected TypeDocMeta analyzeMethodParameter(Parameter parameter) {
        final TypeDocMeta parameterDocMeta = new TypeDocMeta();
        parameterDocMeta.setName(parameter.getName());
        parameterDocMeta.setType(parameter.getType());
        parameterDocMeta.setTypeName(adjustTypeName(parameter.getParameterizedType()));
        parameterDocMeta.setSimpleTypeName(adjustSimpleTypeName(parameter.getParameterizedType()));
        if (OptionalThing.class.isAssignableFrom(parameter.getType())) {
            parameterDocMeta
                    .setGenericType(DfReflectionUtil.getGenericFirstClass(parameter.getParameterizedType()));
        }
        parameterDocMeta.setAnnotationTypeList(Arrays.asList(parameter.getAnnotatedType().getAnnotations()));
        parameterDocMeta.setAnnotationList(analyzeAnnotationList(parameterDocMeta.getAnnotationTypeList()));
        parameterDocMeta.setNestTypeDocMetaList(Collections.emptyList());
        sourceParserReflector.ifPresent(sourceParserReflector -> {
            sourceParserReflector.reflect(parameterDocMeta, parameter.getType());
        });
        return parameterDocMeta;
    }

    // -----------------------------------------------------
    //                                          Analyze Form
    //                                          ------------
    protected OptionalThing<TypeDocMeta> analyzeFormClass(ActionExecute execute) {
        return execute.getFormMeta().map(lastafluteFormMeta -> {
            final TypeDocMeta formDocMeta = new TypeDocMeta();
            lastafluteFormMeta.getListFormParameterParameterizedType().ifPresent(type -> {
                formDocMeta.setType(lastafluteFormMeta.getFormType());
                formDocMeta.setTypeName(adjustTypeName(type));
                formDocMeta.setSimpleTypeName(adjustSimpleTypeName(type));
            }).orElse(() -> {
                formDocMeta.setType(lastafluteFormMeta.getFormType());
                formDocMeta.setTypeName(adjustTypeName(lastafluteFormMeta.getFormType()));
                formDocMeta.setSimpleTypeName(adjustSimpleTypeName(lastafluteFormMeta.getFormType()));
            });
            final Class<?> formType = lastafluteFormMeta.getListFormParameterGenericType()
                    .orElse(lastafluteFormMeta.getFormType());
            final Map<String, Type> genericParameterTypesMap = DfCollectionUtil.newLinkedHashMap(); // #question can be emptyList()? by jflute
            final List<TypeDocMeta> propertyDocMetaList = analyzeProperties(formType, genericParameterTypesMap,
                    depth);
            formDocMeta.setNestTypeDocMetaList(propertyDocMetaList);
            sourceParserReflector.ifPresent(sourceParserReflector -> {
                sourceParserReflector.reflect(formDocMeta, formType);
            });
            return formDocMeta;
        });
    }

    // -----------------------------------------------------
    //                                        Analyze Return
    //                                        --------------
    protected TypeDocMeta analyzeReturnClass(Method method) {
        final TypeDocMeta returnDocMeta = new TypeDocMeta();
        returnDocMeta.setType(method.getReturnType());
        returnDocMeta.setTypeName(adjustTypeName(method.getGenericReturnType()));
        returnDocMeta.setSimpleTypeName(adjustSimpleTypeName(method.getGenericReturnType()));
        returnDocMeta.setGenericType(DfReflectionUtil.getGenericFirstClass(method.getGenericReturnType()));
        returnDocMeta.setAnnotationTypeList(Arrays.asList(method.getAnnotatedReturnType().getAnnotations()));
        returnDocMeta.setAnnotationList(analyzeAnnotationList(returnDocMeta.getAnnotationTypeList()));
        derivedManualReturnClass(method, returnDocMeta);

        Class<?> returnClass = returnDocMeta.getGenericType();
        if (returnClass != null) { // e.g. List<String>, Sea<Land>
            // TODO p1us2er0 optimisation, generic handling in analyzeReturnClass() (2015/09/30)
            final Map<String, Type> genericParameterTypesMap = DfCollectionUtil.newLinkedHashMap();
            final Type[] parameterTypes = DfReflectionUtil.getGenericParameterTypes(method.getGenericReturnType());
            final TypeVariable<?>[] typeVariables = returnClass.getTypeParameters();
            IntStream.range(0, parameterTypes.length).forEach(parameterTypesIndex -> {
                final Type[] genericParameterTypes = DfReflectionUtil
                        .getGenericParameterTypes(parameterTypes[parameterTypesIndex]);
                IntStream.range(0, typeVariables.length).forEach(typeVariablesIndex -> {
                    Type type = genericParameterTypes[typeVariablesIndex];
                    genericParameterTypesMap.put(typeVariables[typeVariablesIndex].getTypeName(), type);
                });
            });

            if (Iterable.class.isAssignableFrom(returnClass)) { // e.g. List<String>, List<Sea<Land>>
                returnClass = LaDocReflectionUtil.extractElementType(method.getGenericReturnType(), 1);
            }
            final List<Class<? extends Object>> nativeClassList = getNativeClassList();
            if (returnClass != null && !nativeClassList.contains(returnClass)) {
                final List<TypeDocMeta> propertyDocMetaList = analyzeProperties(returnClass,
                        genericParameterTypesMap, depth);
                returnDocMeta.setNestTypeDocMetaList(propertyDocMetaList);
            }

            if (sourceParserReflector.isPresent()) {
                sourceParserReflector.get().reflect(returnDocMeta, returnClass);
            }
        }

        return returnDocMeta;
    }

    protected void derivedManualReturnClass(Method method, TypeDocMeta returnDocMeta) {
    }

    protected List<Class<?>> getNativeClassList() {
        return NATIVE_TYPE_LIST;
    }

    // -----------------------------------------------------
    //                                    Analyze Properties
    //                                    ------------------
    // for e.g. form type, return type, nested property type
    protected List<TypeDocMeta> analyzeProperties(Class<?> propertyOwner,
            Map<String, Type> genericParameterTypesMap, int depth) {
        if (depth < 0) {
            return DfCollectionUtil.newArrayList();
        }
        final Set<Field> fieldSet = extractWholeFieldSet(propertyOwner);
        return fieldSet.stream().filter(field -> { // also contains private fields and super's fields
            return !exceptsField(field);
        }).map(field -> { // #question contains private fields, right? by jflute
            return analyzePropertyField(propertyOwner, genericParameterTypesMap, depth, field);
        }).collect(Collectors.toList());
    }

    protected Set<Field> extractWholeFieldSet(Class<?> propertyOwner) {
        final Set<Field> fieldSet = DfCollectionUtil.newLinkedHashSet();
        for (Class<?> targetClazz = propertyOwner; targetClazz != Object.class; targetClazz = targetClazz
                .getSuperclass()) {
            if (targetClazz == null) { // e.g. interface: MultipartFormFile
                break;
            }
            fieldSet.addAll(Arrays.asList(targetClazz.getDeclaredFields()));
        }
        return fieldSet;
    }

    protected boolean exceptsField(Field field) { // e.g. special field and static field
        return SUPPRESSED_FIELD_SET.contains(field.getName()) || Modifier.isStatic(field.getModifiers());
    }

    // -----------------------------------------------------
    //                                Analyze Property Field
    //                                ----------------------
    protected TypeDocMeta analyzePropertyField(Class<?> propertyOwner, Map<String, Type> genericParameterTypesMap,
            int depth, Field field) {
        final TypeDocMeta meta = new TypeDocMeta();

        final Class<?> resolvedClass;
        {
            final Type resolvedType;
            {
                final Type genericClass = genericParameterTypesMap.get(field.getGenericType().getTypeName());
                resolvedType = genericClass != null ? genericClass : field.getType();
            }

            // basic item
            meta.setName(field.getName()); // also property name #question but overridden later, needed? by jflute
            meta.setType(field.getType()); // e.g. String, Integer, SeaPart #question not use resolvedType, right? by jflute
            meta.setTypeName(adjustTypeName(resolvedType));
            meta.setSimpleTypeName(adjustSimpleTypeName(resolvedType));

            // annotation item
            meta.setAnnotationTypeList(Arrays.asList(field.getAnnotations()));
            meta.setAnnotationList(analyzeAnnotationList(meta.getAnnotationTypeList()));

            // comment item (value expression)
            if (resolvedType instanceof Class) {
                resolvedClass = (Class<?>) resolvedType;
            } else {
                resolvedClass = (Class<?>) DfReflectionUtil.getGenericParameterTypes(resolvedType)[0];
            }
            if (resolvedClass.isEnum()) {
                meta.setValue(buildEnumValuesExp(resolvedClass)); // e.g. {FML = Formalized, PRV = Provisinal, ...}
            }
        }

        if (isTargetSuffixResolvedClass(resolvedClass)) { // nested bean of direct type as top or inner class
            // _/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
            // e.g.
            //  public SeaResult sea; // current field, the outer type should have suffix
            //
            //   or
            //
            //  public class SeaResult { // the declaring class should have suffix
            //      public HangarPart hangar; // current field
            //      public static class HangarPart {
            //          ...
            //      }
            //  }
            // _/_/_/_/_/_/_/_/_/_/
            meta.setNestTypeDocMetaList(analyzeProperties(resolvedClass, genericParameterTypesMap, depth - 1));
        } else if (isTargetSuffixFieldGeneric(field)) { // nested bean of generic type as top or inner class
            // _/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
            // e.g.
            //  public List<SeaResult> seaList; // current field, the outer type should have suffix
            //
            //   or
            //
            //  public class SeaResult { // the declaring class should have suffix
            //      public List<HangarPart> hangarList; // current field
            //      public static class HangarPart {
            //          ...
            //      }
            //  }
            // _/_/_/_/_/_/_/_/_/_/
            final Class<?> typeArgumentClass = (Class<?>) ((ParameterizedType) field.getGenericType())
                    .getActualTypeArguments()[0];
            meta.setNestTypeDocMetaList(analyzeProperties(typeArgumentClass, genericParameterTypesMap, depth - 1));

            // overriding type names that are already set before
            final String currentTypeName = meta.getTypeName();
            meta.setTypeName(adjustTypeName(currentTypeName) + "<" + adjustTypeName(typeArgumentClass) + ">");
            meta.setSimpleTypeName(
                    adjustSimpleTypeName(currentTypeName) + "<" + adjustSimpleTypeName(typeArgumentClass) + ">");
        } else { // e.g. String, Integer, LocalDate, Sea<Mystic>
            // TODO p1us2er0 optimisation, generic handling in analyzePropertyField() (2017/09/26)
            if (field.getGenericType().getTypeName().matches(".*<(.*)>")) { // e.g. Sea<Mystic>
                final String genericTypeName = field.getGenericType().getTypeName().replaceAll(".*<(.*)>", "$1");

                // generic item
                try {
                    meta.setGenericType(DfReflectionUtil.forName(genericTypeName));
                } catch (ReflectionFailureException ignored) { // e.g. BEAN (generic parameter name)
                    meta.setGenericType(Object.class); // unknown
                }

                final Type genericClass = genericParameterTypesMap.get(genericTypeName);
                if (genericClass != null) { // the generic is defined at top definition (e.g. return)
                    meta.setNestTypeDocMetaList(
                            analyzeProperties((Class<?>) genericClass, genericParameterTypesMap, depth - 1));

                    // overriding type names that are already set before
                    final String typeName = meta.getTypeName();
                    meta.setTypeName(adjustTypeName(typeName) + "<" + adjustTypeName(genericClass) + ">");
                    meta.setSimpleTypeName(
                            adjustSimpleTypeName(typeName) + "<" + adjustSimpleTypeName(genericClass) + ">");
                } else {
                    // overriding type names that are already set before
                    final String typeName = meta.getTypeName();
                    meta.setTypeName(adjustTypeName(typeName) + "<" + adjustSimpleTypeName(genericTypeName) + ">"); // #question why simple? by jflute
                    meta.setSimpleTypeName(
                            adjustSimpleTypeName(typeName) + "<" + adjustSimpleTypeName(genericTypeName) + ">");
                }
            }
        }

        // e.g. comment item (description, comment)
        sourceParserReflector.ifPresent(sourceParserReflector -> {
            sourceParserReflector.reflect(meta, propertyOwner);
        });

        // necessary to set it after parsing javadoc
        meta.setName(adjustFieldName(propertyOwner, field));
        return meta;
    }

    protected String buildEnumValuesExp(Class<?> typeClass) {
        // cannot resolve type by maven compiler, explicitly cast it
        final String valuesExp;
        if (Classification.class.isAssignableFrom(typeClass)) {
            @SuppressWarnings("unchecked")
            final Class<Classification> clsType = ((Class<Classification>) typeClass);
            valuesExp = Arrays.stream(clsType.getEnumConstants()).collect(Collectors.toMap(keyMapper -> {
                return ((Classification) keyMapper).code();
            }, valueMapper -> {
                return ((Classification) valueMapper).alias();
            }, (u, v) -> v, LinkedHashMap::new)).toString(); // e.g. {FML = Formalized, PRV = Provisinal, ...}
        } else {
            final Enum<?>[] constants = (Enum<?>[]) typeClass.getEnumConstants();
            valuesExp = Arrays.stream(constants).collect(Collectors.toList()).toString(); // e.g. [SEA, LAND, PIARI]
        }
        return valuesExp;
    }

    protected boolean isTargetSuffixResolvedClass(Class<?> resolvedClass) {
        // #question suffix but contains? for AllSuffixResult$ResortParkPart? by jflute
        return getTargetTypeSuffixList().stream().anyMatch(suffix -> resolvedClass.getName().contains(suffix));
    }

    protected boolean isTargetSuffixFieldGeneric(Field field) {
        // #question suffix but contains? by jflute
        return getTargetTypeSuffixList().stream()
                .anyMatch(suffix -> field.getGenericType().getTypeName().contains(suffix));
    }

    protected List<String> getTargetTypeSuffixList() {
        return TARGET_SUFFIX_LIST;
    }

    protected String adjustFieldName(Class<?> clazz, Field field) {
        // TODO p1us2er0 judge accurately in adjustFieldName() (2017/04/20)
        if (clazz.getSimpleName().endsWith("Form")) {
            return field.getName();
        }
        return getApplicationJsonMappingOption().flatMap(jsonMappingOption -> {
            return jsonMappingOption.getFieldNaming().map(naming -> {
                if (naming == JsonFieldNaming.IDENTITY) {
                    return FieldNamingPolicy.IDENTITY.translateName(field);
                } else if (naming == JsonFieldNaming.CAMEL_TO_LOWER_SNAKE) {
                    return FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES.translateName(field);
                } else {
                    return field.getName();
                }
            });
        }).orElse(field.getName());
    }

    // ===================================================================================
    //                                                                     Action Property
    //                                                                     ===============
    // #question who calls? by jflute
    public Map<String, Map<String, String>> generateActionPropertyNameMap(List<ActionDocMeta> actionDocMetaList) {
        final Map<String, Map<String, String>> propertyNameMap = actionDocMetaList.stream()
                .collect(Collectors.toMap(key -> {
                    return key.getUrl().replaceAll("\\{.*", "").replaceAll("/$", "").replaceAll("/", "_");
                }, value -> {
                    return convertPropertyNameMap("", value.getFormTypeDocMeta());
                }, (u, v) -> v, LinkedHashMap::new));
        return propertyNameMap;
    }

    protected Map<String, String> convertPropertyNameMap(String parentName, TypeDocMeta typeDocMeta) {
        if (typeDocMeta == null) {
            return DfCollectionUtil.newLinkedHashMap();
        }

        final Map<String, String> propertyNameMap = DfCollectionUtil.newLinkedHashMap();

        final String name = calculateName(parentName, typeDocMeta.getName(), typeDocMeta.getTypeName());
        if (DfStringUtil.is_NotNull_and_NotEmpty(name)) {
            propertyNameMap.put(name, "");
        }

        if (typeDocMeta.getNestTypeDocMetaList() != null) {
            typeDocMeta.getNestTypeDocMetaList().forEach(nestDocMeta -> {
                propertyNameMap.putAll(convertPropertyNameMap(name, nestDocMeta));
            });
        }

        return propertyNameMap;
    }

    protected String calculateName(String parentName, String name, String type) {
        if (DfStringUtil.is_Null_or_Empty(name)) {
            return null;
        }

        final StringBuilder builder = new StringBuilder();
        if (DfStringUtil.is_NotNull_and_NotEmpty(parentName)) {
            builder.append(parentName + ".");
        }
        builder.append(name);
        if (name.endsWith("List")) {
            builder.append("[]");
        }

        return builder.toString();
    }

    // ===================================================================================
    //                                                                        DI Container
    //                                                                        ============
    protected List<String> findActionComponentNameList() {
        final List<String> componentNameList = DfCollectionUtil.newArrayList();
        final LaContainer container = getRootContainer();
        srcDirList.stream().filter(srcDir -> Paths.get(srcDir).toFile().exists()).forEach(srcDir -> {
            try (Stream<Path> stream = Files.find(Paths.get(srcDir), Integer.MAX_VALUE, (path, attr) -> {
                return path.toString().endsWith("Action.java");
            })) {
                stream.sorted().map(path -> {
                    final String className = extractActionClassName(path, srcDir);
                    return DfReflectionUtil.forName(className);
                }).filter(clazz -> !clazz.isInterface() && !Modifier.isAbstract(clazz.getModifiers()))
                        .forEach(clazz -> {
                            final String componentName = container.getComponentDef(clazz).getComponentName();
                            if (componentName != null && !componentNameList.contains(componentName)) {
                                componentNameList.add(componentName);
                            }
                        });
            } catch (IOException e) {
                throw new IllegalStateException("Failed to find the components: " + srcDir, e);
            }
        });
        IntStream.range(0, container.getComponentDefSize()).forEach(index -> {
            final ComponentDef componentDef = container.getComponentDef(index);
            final String componentName = componentDef.getComponentName();
            if (componentName.endsWith("Action") && !componentNameList.contains(componentName)) {
                componentNameList.add(componentDef.getComponentName());
            }
        });
        return componentNameList;
    }

    protected String extractActionClassName(Path path, String srcDir) { // for forName()
        String className = DfStringUtil.substringFirstRear(path.toFile().getAbsolutePath(),
                new File(srcDir).getAbsolutePath());
        if (className.startsWith(File.separator)) {
            className = className.substring(1);
        }
        className = DfStringUtil.substringLastFront(className, ".java").replace(File.separatorChar, '.');
        return className;
    }

    // ===================================================================================
    //                                                                        Small Helper
    //                                                                        ============
    protected DocumentGeneratorFactory createDocumentGeneratorFactory() {
        return new DocumentGeneratorFactory();
    }

    protected LaContainer getRootContainer() {
        return SingletonLaContainerFactory.getContainer().getRoot();
    }

    protected ActionPathResolver getActionPathResolver() {
        return ContainerUtil.getComponent(ActionPathResolver.class);
    }

    protected OptionalThing<JsonMappingOption> getApplicationJsonMappingOption() {
        return createDocumentGeneratorFactory().getApplicationJsonMappingOption();
    }
}