JsonGenerator.java

/*
 * Copyright 2015 Providence Authors
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you 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 net.morimekta.providence.generator.format.json;

import net.morimekta.providence.generator.Generator;
import net.morimekta.providence.generator.GeneratorException;
import net.morimekta.providence.generator.util.FileManager;
import net.morimekta.providence.model.ConstType;
import net.morimekta.providence.model.EnumType;
import net.morimekta.providence.model.EnumValue;
import net.morimekta.providence.model.FieldRequirement;
import net.morimekta.providence.model.FieldType;
import net.morimekta.providence.model.FunctionType;
import net.morimekta.providence.model.MessageType;
import net.morimekta.providence.model.MessageVariant;
import net.morimekta.providence.model.ProgramType;
import net.morimekta.providence.model.ServiceType;
import net.morimekta.providence.model.TypedefType;
import net.morimekta.providence.reflect.ProgramRegistry;
import net.morimekta.providence.reflect.contained.CProgram;
import net.morimekta.providence.reflect.model.AnnotationDeclaration;
import net.morimekta.providence.reflect.model.ConstDeclaration;
import net.morimekta.providence.reflect.model.Declaration;
import net.morimekta.providence.reflect.model.EnumDeclaration;
import net.morimekta.providence.reflect.model.EnumValueDeclaration;
import net.morimekta.providence.reflect.model.FieldDeclaration;
import net.morimekta.providence.reflect.model.IncludeDeclaration;
import net.morimekta.providence.reflect.model.MessageDeclaration;
import net.morimekta.providence.reflect.model.MethodDeclaration;
import net.morimekta.providence.reflect.model.NamespaceDeclaration;
import net.morimekta.providence.reflect.model.ProgramDeclaration;
import net.morimekta.providence.reflect.model.ServiceDeclaration;
import net.morimekta.providence.reflect.model.TypedefDeclaration;
import net.morimekta.providence.reflect.util.ReflectionUtils;
import net.morimekta.providence.serializer.JsonSerializer;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static net.morimekta.providence.model.Declaration.withDeclConst;
import static net.morimekta.providence.model.Declaration.withDeclEnum;
import static net.morimekta.providence.model.Declaration.withDeclMessage;
import static net.morimekta.providence.model.Declaration.withDeclService;
import static net.morimekta.providence.model.Declaration.withDeclTypedef;

/**
 * Generate a simple JSON model of the program structure.
 */
public class JsonGenerator extends Generator {
    private final JsonSerializer serializer;

    public JsonGenerator(FileManager fileManager) {
        super(fileManager);
        serializer = new JsonSerializer().pretty();
    }

    @Override
    public void generate(ProgramRegistry registry) throws IOException, GeneratorException {
        ProgramType doc         = convertProgram(registry.getProgramType());
        CProgram    program     = registry.getProgram();
        Path        programFile = Paths.get(program.getProgramFilePath());
        String      programName = ReflectionUtils.programNameFromPath(programFile);
        getFileManager().createIfMissingOrOutdated(
                programFile, null, programName + ".json",
                out -> {
                    serializer.serialize(out, doc);
                    out.write('\n');
                });
    }

    private ProgramType convertProgram(ProgramDeclaration declaration) {
        ProgramType._Builder program = ProgramType.builder();
        program.setProgramName(declaration.getProgramName());
        for (IncludeDeclaration include : declaration.getIncludes()) {
            String includedProgram = ReflectionUtils.programNameFromPath(Paths.get(include.getFilePath()));
            program.putInIncludes(include.getProgramName(), includedProgram + ".json");
        }
        for (NamespaceDeclaration namespace : declaration.getNamespaces()) {
            program.putInNamespaces(namespace.getLanguage(), namespace.getNamespace());
        }
        for (Declaration decl : declaration.getDeclarationList()) {
            if (decl instanceof TypedefDeclaration) {
                program.addToDecl(convertTypedef((TypedefDeclaration) decl));
            } else if (decl instanceof EnumDeclaration) {
                program.addToDecl(convertEnum((EnumDeclaration) decl));
            } else if (decl instanceof MessageDeclaration) {
                program.addToDecl(convertMessage((MessageDeclaration) decl));
            } else if (decl instanceof ConstDeclaration) {
                program.addToDecl(convertConst((ConstDeclaration) decl));
            } else if (decl instanceof ServiceDeclaration) {
                program.addToDecl(convertService((ServiceDeclaration) decl));
            }
        }
        return program.build();
    }

    private net.morimekta.providence.model.Declaration convertService(ServiceDeclaration decl) {
        return withDeclService(ServiceType.builder()
                                          .setDocumentation(decl.getDocumentation())
                                          .setName(decl.getName())
                                          .setExtend(decl.getExtending())
                                          .setMethods(convertMethods(decl.getMethods()))
                                          .setAnnotations(makeAnnotations(decl.getAnnotations()))
                                          .build());
    }

    private List<FunctionType> convertMethods(List<MethodDeclaration> methods) {
        List<FunctionType> functions = new ArrayList<>();
        for (MethodDeclaration method : methods) {
            functions.add(FunctionType
                                  .builder()
                                  .setDocumentation(method.getDocumentation())
                                  .setOneWay(method.isOneWay() ? Boolean.TRUE : null)
                                  .setReturnType(method.getReturnType())
                                  .setName(method.getName())
                                  .setParams(convertFields(method.getParams()))
                                  .setExceptions(method.getThrowing() != null && !method.getThrowing().isEmpty() ?
                                                 convertFields(method.getThrowing()) : null)
                                  .setAnnotations(makeAnnotations(method.getAnnotations()))
                                  .build());
        }
        return functions;
    }

    private net.morimekta.providence.model.Declaration convertConst(ConstDeclaration decl) {
        return withDeclConst(ConstType.builder()
                                      .setDocumentation(decl.getDocumentation())
                                      .setName(decl.getName())
                                      .setType(decl.getType())
                                      .setValue(decl.getValue())
                                      .setAnnotations(makeAnnotations(decl.getAnnotations()))
                                      .build());
    }

    private net.morimekta.providence.model.Declaration convertMessage(MessageDeclaration decl) {
        return withDeclMessage(MessageType.builder()
                                          .setDocumentation(decl.getDocumentation())
                                          .setVariant(MessageVariant.findByName(decl.getVariant().toString()))
                                          .setName(decl.getName())
                                          .setImplementing(decl.getImplementing() == null ? null : decl.getImplementing().toString())
                                          .setFields(convertFields(decl.getFields()))
                                          .setAnnotations(makeAnnotations(decl.getAnnotations()))
                                          .build());
    }

    private net.morimekta.providence.model.Declaration convertEnum(EnumDeclaration decl) {
        List<EnumValue> values = new ArrayList<>();
        for (EnumValueDeclaration value : decl.getValues()) {
            values.add(EnumValue.builder()
                                .setDocumentation(value.getDocumentation())
                                .setName(value.getName())
                                .setId(value.getIdToken() == null ? null : value.getId())
                                .setAnnotations(makeAnnotations(value.getAnnotations()))
                                .build());
        }
        return withDeclEnum(EnumType.builder()
                                    .setDocumentation(decl.getDocumentation())
                                    .setName(decl.getName())
                                    .setValues(values)
                                    .setAnnotations(makeAnnotations(decl.getAnnotations()))
                                    .build());
    }

    private List<FieldType> convertFields(List<FieldDeclaration> fields) {
        if (fields == null) return null;
        List<FieldType> out = new ArrayList<>();
        for (FieldDeclaration field : fields) {
            out.add(convertField(field));
        }
        return out;
    }

    private FieldType convertField(FieldDeclaration decl) {
        return FieldType.builder()
                        .setDocumentation(decl.getDocumentation())
                        .setId(decl.getIdToken() == null ? null : decl.getId())
                        .setRequirement(decl.getRequirementToken() == null
                                        ? null
                                        : FieldRequirement.findByName(decl.getRequirement().name()))
                        .setType(decl.getType())
                        .setName(decl.getName())
                        .setDefaultValue(decl.getDefaultValue())
                        .setAnnotations(makeAnnotations(decl.getAnnotations()))
                        .build();
    }

    private Map<String, String> makeAnnotations(List<AnnotationDeclaration> annotations) {
        if (annotations == null) return null;
        Map<String,String> map = new HashMap<>();
        for (AnnotationDeclaration annotation : annotations) {
            map.put(annotation.getTag(), annotation.getValue());
        }
        if (map.isEmpty()) return null;
        return map;
    }

    private net.morimekta.providence.model.Declaration convertTypedef(TypedefDeclaration declaration) {
        return withDeclTypedef(TypedefType.builder()
                                          .setDocumentation(declaration.getDocumentation())
                                          .setName(declaration.getName())
                                          .setType(declaration.getType())
                                          .build());
    }
}