Generator.java

package ru.volnenko.plugin.openapidoc;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import lombok.SneakyThrows;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.FileUtils;
import ru.volnenko.plugin.openapidoc.exception.UnsupportedFormatException;
import ru.volnenko.plugin.openapidoc.model.*;
import ru.volnenko.plugin.openapidoc.parser.RootParser;
import ru.volnenko.plugin.openapidoc.util.ContentUtil;
import ru.volnenko.plugin.openapidoc.util.MapperUtil;
import ru.volnenko.plugin.openapidoc.util.ParameterUtil;
import ru.volnenko.plugin.openapidoc.util.StringUtil;

import java.io.File;
import java.util.*;

@Mojo(name = "generate", defaultPhase = LifecyclePhase.COMPILE)
public final class Generator extends AbstractMojo {

    @Getter
    @Setter
    @Parameter(property = "serviceName")
    public String serviceName = "Сервис";

    @Getter
    @Setter
    @Parameter(property = "headerFirstEnabled")
    public boolean headerFirstEnabled = true;

    @Getter
    @Setter
    @Parameter(property = "headerSecondEnabled")
    public boolean headerSecondEnabled = true;

    @Getter
    @Setter
    @Parameter(property = "tableOfContentsEnabled")
    public boolean tableOfContentsEnabled = true;

    @Getter
    @Setter
    @Parameter(property = "outputPath")
    public String outputPath = "./doc";

    @Getter
    @Setter
    @Parameter(property = "outputFile")
    public String outputFile = "index.adoc";

    @Getter
    @Setter
    @Parameter(property = "outputJsonFile")
    public String outputJsonFile = "scheme.json";

    @Getter
    @Setter
    @Parameter(property = "outputJsonFileEnabled")
    public Boolean outputJsonFileEnabled = false;

    @Getter
    @Setter
    @Parameter(property = "outputJavaScriptFile")
    public String outputJavaScriptFile = "scheme.js";

    @Getter
    @Setter
    @Parameter(property = "outputJavaScriptFileEnabled")
    public Boolean outputJavaScriptFileEnabled = false;

    @Getter
    @Setter
    @Parameter(property = "outputYamlFile")
    public String outputYamlFile = "scheme.yaml";

    @Getter
    @Setter
    @Parameter(property = "outputYamlFileEnabled")
    public Boolean outputYamlFileEnabled = false;

    @Parameter(defaultValue = "${project}", required = true, readonly = true)
    private MavenProject project;

    @Getter
    @Setter
    @Parameter(property = "files")
    private List<String> files = new ArrayList<>();

    @Getter
    @Setter
    @NonNull
    private final RootParser rootParser = new RootParser();

    @NonNull
    private final StringBuilder stringBuilder = new StringBuilder();

    @NonNull
    private ObjectMapper objectMapper(@NonNull final String file) {
        @NonNull final String name = file.toLowerCase(Locale.ROOT);
        if (name.endsWith(".json")) return MapperUtil.json();
        if (name.endsWith(".yaml")) return MapperUtil.yaml();
        if (name.endsWith(".yml")) return MapperUtil.yaml();
        throw new UnsupportedFormatException();
    }

    @SneakyThrows
    public void execute() throws MojoExecutionException, MojoFailureException {
        header();
        @NonNull final List<Root> roots = rootParser.files(files).parse();
        for (final String file : files) {
            if (file == null || file.isEmpty()) {
                continue;
            }
            parse(file);
        }

        save();
    }

    @NonNull
    @SneakyThrows
    private Generator saveDatabaseYAML(@NonNull final File path) {
        if (!outputYamlFileEnabled) return this;
        if (outputYamlFile.isEmpty()) return this;
        @NonNull final File file = new File(path.getAbsolutePath() + "/" + outputYamlFile);
        System.out.println(file);
        FileUtils.fileWrite(file, rootParser.yaml());
        return this;
    }

    @NonNull
    @SneakyThrows
    private Generator saveDatabaseJSON(@NonNull final File path) {
        if (!outputJsonFileEnabled) return this;
        if (outputJsonFile.isEmpty()) return this;
        @NonNull final File file = new File(path.getAbsolutePath() + "/" + outputJsonFile);
        System.out.println(file);
        FileUtils.fileWrite(file, rootParser.json());
        return this;
    }

    @NonNull
    @SneakyThrows
    private Generator saveDatabaseJavaScript(@NonNull final File path) {
        if (!outputJavaScriptFileEnabled) return this;
        if (outputJavaScriptFile.isEmpty()) return this;
        @NonNull final File file = new File(path.getAbsolutePath() + "/" + outputJavaScriptFile);
        System.out.println(file);
        FileUtils.fileWrite(file, "var scheme = " + rootParser.json());
        return this;
    }

    @SneakyThrows
    public void save() {
        if (outputPath == null || outputPath.isEmpty()) return;
        if (outputFile == null || outputFile.isEmpty()) return;
        @NonNull final File path = new File(outputPath);
        initOutputPath(path)
                .saveDatabaseYAML(path)
                .saveDatabaseJSON(path)
                .saveDatabaseJavaScript(path);

        @NonNull final File file = new File(path.getAbsolutePath() + "/" + outputFile);
        FileUtils.fileWrite(file, stringBuilder.toString());
    }

    @NonNull
    private Generator initOutputPath(@NonNull final File path) {
        path.mkdirs();
        return this;
    }


    @SneakyThrows
    public void parse(@NonNull final String file) {
        @NonNull final ObjectMapper objectMapper = objectMapper(file);
        @NonNull final Root root = objectMapper.readValue(new File(file), Root.class);
        generate(root);
    }

    @NonNull
    public String generate(final Root root) {
        if (root == null) return "";
        generate(root.getComponents());
        generate(root.getPaths());
        return stringBuilder.toString();
    }

    private void generate(final Components components) {
        if (components == null) return;
        if (components.getSchemas() == null) return;
        if (components.getSchemas().isEmpty()) return;
        int index = 1;
        for (final String model: components.getSchemas().keySet()) {
            generate(model, components.getSchemas().get(model), index);
            index++;
        }
    }

    public void generate(final String model, final Schema schema, final int indexm) {
        stringBuilder.append("=== Модель данных \""+ model + "\"" + " [[" + model + "]]" + "\n");
        stringBuilder.append("\n");

        stringBuilder.append("==== Общие сведения\n");
        stringBuilder.append("\n");
        stringBuilder.append("[cols=\"20,80\"]\n");
        stringBuilder.append("|===\n");
        stringBuilder.append("\n");
        stringBuilder.append("|*Физ. название*:\n");
        stringBuilder.append("|" + StringUtil.format(model) + "\n");
        stringBuilder.append("\n");
        stringBuilder.append("|*Лог. название*:\n");
        stringBuilder.append("|" + StringUtil.format(schema.getDescription()) + "\n");
        stringBuilder.append("\n");
        stringBuilder.append("|*Тип данных*:\n");
        stringBuilder.append("|" + StringUtil.format(schema.getType()) + "\n");
        stringBuilder.append("\n");
        stringBuilder.append("|*Сервис*:\n");
        stringBuilder.append("|" + StringUtil.format(serviceName) + "\n");
        stringBuilder.append("\n");
        stringBuilder.append("|===\n");
        stringBuilder.append("\n");

        stringBuilder.append("==== Описание полей \n");

        stringBuilder.append("\n");
        stringBuilder.append("[cols=\"0,30,30,20,10,10\"]\n");
        stringBuilder.append("|===\n");

        stringBuilder.append("\n");
        stringBuilder.append("^|*№*\n");
        stringBuilder.append("|*Физ. название*\n");
        stringBuilder.append("|*Лог. название*\n");
        stringBuilder.append("^|*Тип данных*\n");
        stringBuilder.append("^|*Формат*\n");
        stringBuilder.append("^|*Обязательный*\n");
        stringBuilder.append("\n");

        boolean exists = true;
        Map<String, Schema> properties = schema.getProperties();
        if ("array".equalsIgnoreCase(schema.getType())) {
            if (schema.getItems() != null) {
                properties = schema.getItems().getProperties();
            }
        }
        if (properties == null) exists = false;
        if (properties != null && properties.isEmpty()) exists = false;

        if (!exists) {
            stringBuilder.append("\n");
            stringBuilder.append("6+^| Отсутствует \n");
            stringBuilder.append("\n");
        }

        if (exists) {
            int index = 1;
            for (final String field: properties.keySet()) {
                final Schema property = properties.get(field);

                stringBuilder.append("\n");
                stringBuilder.append("^|"+StringUtil.format(index)+". \n");
                stringBuilder.append("|" + StringUtil.format(field) + "\n");
                stringBuilder.append("|" + StringUtil.format(property.getDescription()) + "\n");
                stringBuilder.append("^| " + ContentUtil.scheme(property) + "\n");
                stringBuilder.append("^|"+ ContentUtil.format(property)+"\n");

                if (schema.getRequired() == null) {
                    stringBuilder.append("^|--\n");
                } else {
                   if (schema.getRequired().contains(field)) {
                       stringBuilder.append("^|✓\n");
                   } else {
                       stringBuilder.append("^|--\n");
                   }
                }
                index++;
            }
        }

        stringBuilder.append("\n");
        stringBuilder.append("|===\n");
        stringBuilder.append("\n");
    }

    private void generate(final Map<String, Map<String, Operation>> paths) {
        if (paths == null || paths.isEmpty()) return;
        for (final String path: paths.keySet()) {
            if (path == null || path.isEmpty()) continue;
            generate(path, paths.get(path));
        }
    }

    private void generate(final String path, final Map<String, Operation> operations) {
        if (path == null) return;
        for (final String method: operations.keySet()) {
            if (method == null || method.isEmpty()) continue;
            generate(path, method, operations.get(method));
        }
    }

    private void generate(String path, String method, Operation operation) {
        if (path == null || path.isEmpty()) return;
        if (method == null || method.isEmpty()) return;
        if (operation == null) return;
        stringBuilder.append("=== Ресурс " + operation.tags() + " " + method.toUpperCase() + " \"" + path + "\" \n");
        stringBuilder.append("==== Общие сведения\n");
        stringBuilder.append("\n");
        stringBuilder.append("[cols=\"20,80\"]\n");
        stringBuilder.append("|===\n");
        stringBuilder.append("\n");
        stringBuilder.append("|*Физ. название*:\n");
        stringBuilder.append("|" + StringUtil.format(operation.getOperationId()) + "\n");
        stringBuilder.append("\n");
        stringBuilder.append("|*Лог. название*:\n");
        stringBuilder.append("|" + StringUtil.format(operation.getSummary()) + "\n");
        stringBuilder.append("\n");
        stringBuilder.append("|*Сервис*:\n");
        stringBuilder.append("|" + StringUtil.format(serviceName) + "\n");
        stringBuilder.append("\n");
        stringBuilder.append("|*HTTP-метод*:\n");
        stringBuilder.append("|" + StringUtil.format(method.toUpperCase()) + "\n");
        stringBuilder.append("\n");
        stringBuilder.append("|*HTTP-адрес*:\n");
        stringBuilder.append("|" + StringUtil.format(path) + "\n");
        stringBuilder.append("\n");
        stringBuilder.append("|===\n");
        stringBuilder.append("\n");

        if (operation.getParameters() == null) operation.setParameters(Collections.emptyList());
        generate(operation.getParameters().toArray(new ru.volnenko.plugin.openapidoc.model.Parameter[0]));

        {
            stringBuilder.append("==== Описание запроса \n");

            stringBuilder.append("\n");
            stringBuilder.append("[cols=\"0,20,50,20,10\"]\n");
            stringBuilder.append("|===\n");

            stringBuilder.append("\n");
            stringBuilder.append("^|*№*\n");
            stringBuilder.append("^|*Медиа тип*\n");
            stringBuilder.append("^|*Тип данных*\n");
            stringBuilder.append("^|*Формат*\n");
            stringBuilder.append("^|*Обязательный*\n");
            stringBuilder.append("\n");

            boolean exists = true;
            if (operation.getRequestBody() == null) exists = false;

            if (operation.getRequestBody() != null) {
                if (operation.getRequestBody().getContent() == null) {
                    exists = false;
                } else {
                    if (operation.getRequestBody().getContent().isEmpty()) {
                        exists = false;
                    }
                }
            }

            if (!exists) {
                stringBuilder.append("\n");
                stringBuilder.append("5+^| Отсутствует \n");
                stringBuilder.append("\n");
            }

            if (operation.getRequestBody() != null) {
                if (operation.getRequestBody().getContent() != null) {
                    int index = 1;
                    for (final String mediaType: operation.getRequestBody().getContent().keySet()) {
                        final Content content = operation.getRequestBody().getContent().get(mediaType);
                        if (content == null) continue;

                        stringBuilder.append("\n");
                        stringBuilder.append("^|"+StringUtil.format(index)+". \n");
                        stringBuilder.append("^|" + StringUtil.format(mediaType) + "\n");
                        stringBuilder.append("^| " + ContentUtil.scheme(content) + "\n");
                        stringBuilder.append("^|"+ ContentUtil.format(content)+"\n");
                        stringBuilder.append("^|"+StringUtil.format(operation.getRequestBody().getRequired()) +"\n");
                        stringBuilder.append("\n");
                    }
                }
            }

            stringBuilder.append("\n");
            stringBuilder.append("|===\n");
            stringBuilder.append("\n");
        }

        generate(operation);
    }

    private void generate(@NonNull final Operation operation) {
        stringBuilder.append("==== Описание ответов \n");

        stringBuilder.append("\n");
        stringBuilder.append("[cols=\"0,15,20,50,30,20\"]\n");
        stringBuilder.append("|===\n");

        stringBuilder.append("\n");
        stringBuilder.append("^|*№*\n");
        stringBuilder.append("^|*HTTP-код*\n");
        stringBuilder.append("^|*Медиа тип*\n");
        stringBuilder.append("|*Описание*\n");
        stringBuilder.append("^|*Тип данных*\n");
        stringBuilder.append("^|*Формат*\n");
        stringBuilder.append("\n");

        if (operation.getResponses() == null) operation.setResponses(Collections.emptyMap());

        int index = 1;
        for (final String httpCode: operation.getResponses().keySet()) {
            Response response = operation.getResponses().get(httpCode);
            if (response.getContent() == null || response.getContent().isEmpty()) {
                continue;
            }
            generate(httpCode, response, index);
            index++;
        }

        if (index == 1) {
            stringBuilder.append("\n");
            stringBuilder.append("6+^| Отсутствует \n");
            stringBuilder.append("\n");
        }

        stringBuilder.append("\n");
        stringBuilder.append("|===\n");
        stringBuilder.append("\n");
    }

    private void generate(@NonNull final String httpCode, final Response response, int index) {
        if (response.getContent() == null || response.getContent().isEmpty()) {
            return;
        }
        for (final String mediaType: response.getContent().keySet()) {
            final Content content = response.getContent().get(mediaType);
            stringBuilder.append("\n");
            stringBuilder.append("^|" + StringUtil.format(index) + ". \n");
            stringBuilder.append("^|" + StringUtil.format(httpCode) + "\n");
            stringBuilder.append("^| \"" + StringUtil.format(mediaType) + "\" \n");
            stringBuilder.append("|" + String.format(response.getDescription())+"\n");
            stringBuilder.append("^| " + ContentUtil.scheme(content) + "\n");
            stringBuilder.append("^|"+ ContentUtil.format(content)+"\n");
            stringBuilder.append("\n");
        }
    }

    private void generate(@NonNull final ru.volnenko.plugin.openapidoc.model.Parameter[] parameters) {
        stringBuilder.append("==== Описание параметров \n");
        int index = 1;
        stringBuilder.append("\n");
        stringBuilder.append("[cols=\"0,20,20,10,10,10,10\"]\n");
        stringBuilder.append("|===\n");

        stringBuilder.append("\n");
        stringBuilder.append("^|*№*\n");
        stringBuilder.append("|*Физ. название*\n");
        stringBuilder.append("|*Лог. название*\n");
        stringBuilder.append("^|*Тип*\n");
        stringBuilder.append("^|*Формат*\n");
        stringBuilder.append("^|*Вид*\n");
        stringBuilder.append("^|*Обязательный*\n");
        stringBuilder.append("\n");

        if (parameters.length == 0) {
            stringBuilder.append("\n");
            stringBuilder.append("7+^| Отсутствует \n");
            stringBuilder.append("\n");
        }

        for (ru.volnenko.plugin.openapidoc.model.Parameter parameter: parameters) {
            generate(parameter, index);
            index++;
        }

        stringBuilder.append("\n");
        stringBuilder.append("|===\n");
        stringBuilder.append("\n");
    }

    private void generate(ru.volnenko.plugin.openapidoc.model.Parameter parameter, int index) {
        stringBuilder.append("\n");
        stringBuilder.append("^|"+StringUtil.format(index) + ". \n");
        stringBuilder.append("|"+StringUtil.format(parameter.getName())+"\n");
        stringBuilder.append("|"+StringUtil.format(parameter.getDescription())+"\n");
        stringBuilder.append("^|"+parameter.getSchema().toString() + "\n");
        stringBuilder.append("^|"+ ParameterUtil.format(parameter)+"\n");
        stringBuilder.append("^|"+StringUtil.format(parameter.getIn())+"\n");
        stringBuilder.append("^|"+StringUtil.format(parameter.getRequired()) +"\n");
        stringBuilder.append("\n");
    }

    private void header() {
        if (headerFirstEnabled) {
            stringBuilder.append("= " + StringUtil.format(serviceName) + "\n");
            if (tableOfContentsEnabled) {
                stringBuilder.append(":toc-title: Оглавление\n");
                stringBuilder.append(":toc:\n");
            }
            stringBuilder.append("\n");
        }
        if (headerSecondEnabled) {
            stringBuilder.append("== Представление веб-сервисов \n");
            stringBuilder.append("\n");
        }
    }

}