All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.facebook.drift.idl.generator.ThriftIdlRenderer Maven / Gradle / Ivy

/*
 * Copyright (C) 2017 Facebook, Inc.
 *
 * 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 com.facebook.drift.idl.generator;

import com.facebook.drift.annotations.ThriftField.Requiredness;
import com.facebook.drift.codec.ThriftProtocolType;
import com.facebook.drift.codec.metadata.ThriftEnumMetadata;
import com.facebook.drift.codec.metadata.ThriftFieldMetadata;
import com.facebook.drift.codec.metadata.ThriftMethodMetadata;
import com.facebook.drift.codec.metadata.ThriftServiceMetadata;
import com.facebook.drift.codec.metadata.ThriftStructMetadata;
import com.facebook.drift.codec.metadata.ThriftType;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import static com.google.common.base.Strings.emptyToNull;
import static com.google.common.collect.Streams.mapWithIndex;
import static java.lang.String.format;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;

@SuppressFBWarnings("VA_FORMAT_STRING_USES_NEWLINE")
public final class ThriftIdlRenderer
{
    private final ThriftTypeRenderer typeRenderer;

    private ThriftIdlRenderer(ThriftTypeRenderer typeRenderer)
    {
        this.typeRenderer = requireNonNull(typeRenderer, "typeRenderer is null");
    }

    public static String renderThriftIdl(
            Map namespaces,
            Set includes,
            List thriftTypes,
            List services,
            ThriftTypeRenderer typeRenderer)
    {
        List> enums = thriftTypes.stream()
                .filter(type -> type.getProtocolType() == ThriftProtocolType.ENUM)
                .map(ThriftType::getEnumMetadata)
                .collect(toList());

        List structs = thriftTypes.stream()
                .filter(type -> type.getProtocolType() == ThriftProtocolType.STRUCT)
                .map(ThriftType::getStructMetadata)
                .collect(toList());

        return new ThriftIdlRenderer(typeRenderer)
                .render(namespaces, includes, enums, structs, services);
    }

    private String render(
            Map namespaces,
            Set includes,
            List> enums,
            List structs,
            List services)
    {
        return Joiner.on("\n").skipNulls().join(
                renderNamespaces(namespaces),
                renderIncludes(includes),
                renderEnums(enums),
                renderStructs(structs),
                renderServices(services));
    }

    private static String renderNamespaces(Map namespaces)
    {
        if (namespaces.isEmpty()) {
            return null;
        }
        return namespaces.entrySet().stream()
                .map(entry -> format("namespace %s %s", entry.getKey(), entry.getValue()))
                .collect(joining("\n")) + "\n";
    }

    private static String renderIncludes(Set includes)
    {
        if (includes.isEmpty()) {
            return null;
        }
        return includes.stream()
                .map(include -> format("include \"%s\"", include))
                .collect(joining("\n")) + "\n";
    }

    private static String renderEnums(List> enums)
    {
        return emptyToNull(enums.stream()
                .map(ThriftIdlRenderer::renderEnum)
                .collect(joining("\n")));
    }

    private static String renderEnum(ThriftEnumMetadata metadata)
    {
        return documentation(metadata.getDocumentation(), "") +
                format("enum %s {\n%s}\n", metadata.getEnumName(), renderEnumElements(metadata));
    }

    private static > String renderEnumElements(ThriftEnumMetadata metadata)
    {
        if (metadata.getElementsDocumentation().values().stream().allMatch(Collection::isEmpty) &&
                (metadata.getByEnumConstant() == null)) {
            return "  " + metadata.getElementsDocumentation().keySet().stream()
                    .map(Enum::name)
                    .collect(joining(", ")) + "\n";
        }

        LineSeparator separator = new LineSeparator();
        StringBuilder builder = new StringBuilder();
        for (Entry> entry : metadata.getElementsDocumentation().entrySet()) {
            builder.append(separator.getAndUpdate(!entry.getValue().isEmpty()))
                    .append(documentation(entry.getValue(), "  "))
                    .append("  ").append(entry.getKey().name());
            if (metadata.getByEnumConstant() != null) {
                builder.append(" = ").append(metadata.getByEnumConstant().get(entry.getKey()));
            }
            builder.append(";\n");
        }
        return builder.toString();
    }

    private String renderStructs(List structs)
    {
        return emptyToNull(structs.stream()
                .map(this::renderStruct)
                .collect(joining("\n")));
    }

    private String renderStruct(ThriftStructMetadata struct)
    {
        StringBuilder builder = new StringBuilder()
                .append(documentation(struct.getDocumentation(), ""))
                .append(format("%s %s {\n", structKind(struct), struct.getStructName()));

        LineSeparator separator = new LineSeparator();
        for (ThriftFieldMetadata field : struct.getFields()) {
            if (field.isInternal()) {
                continue;
            }
            builder.append(separator.getAndUpdate(!field.getDocumentation().isEmpty()))
                    .append(documentation(field.getDocumentation(), "  "))
                    .append(format("  %s: %s%s %s;\n",
                            field.getId(),
                            requiredness(field.getRequiredness()),
                            typeName(field.getThriftType()),
                            field.getName()));
        }

        return builder.append("}\n").toString();
    }

    private static String structKind(ThriftStructMetadata struct)
    {
        if (struct.isStruct()) {
            return "struct";
        }
        if (struct.isException()) {
            return "exception";
        }
        if (struct.isUnion()) {
            return "union";
        }
        throw new IllegalArgumentException("Unknown type: " + struct);
    }

    private static String requiredness(Requiredness requiredness)
    {
        switch (requiredness) {
            case REQUIRED:
                return "required ";
            case OPTIONAL:
                return "optional ";
        }
        return "";
    }

    private String renderServices(List services)
    {
        return emptyToNull(services.stream()
                .map(this::renderService)
                .collect(joining("\n")));
    }

    private String renderService(ThriftServiceMetadata service)
    {
        return documentation(service.getDocumentation(), "") +
                format("service %s {\n", service.getIdlName()) +
                service.getMethods().stream()
                        .map(this::renderMethod)
                        .collect(joining("\n")) +
                "}\n";
    }

    private String renderMethod(ThriftMethodMetadata method)
    {
        StringBuilder builder = new StringBuilder()
                .append(documentation(method.getDocumentation(), "  "));

        String methodStart = format("  %s%s %s(",
                method.getOneway() ? "oneway " : "",
                typeName(method.getReturnType()),
                method.getName());

        List parameters = method.getParameters().stream()
                .map(parameter -> format("%s: %s %s",
                        parameter.getId(),
                        typeName(parameter.getThriftType()),
                        parameter.getName()))
                .collect(toList());

        builder.append(renderParameters(methodStart, parameters));

        if (!method.getExceptions().isEmpty()) {
            List exceptions = mapWithIndex(method.getExceptions().entrySet().stream(),
                    (entry, index) -> format("%s: %s ex%s",
                            entry.getKey(),
                            typeName(entry.getValue()),
                            index + 1))
                    .collect(toList());
            builder.append("\n")
                    .append(renderParameters("    throws (", exceptions));
        }

        return builder.append(";\n").toString();
    }

    private static String renderParameters(String start, List parameters)
    {
        String joined = Joiner.on(", ").join(parameters) + ")";
        if ((start.length() + joined.length()) <= 80) {
            return start + joined;
        }
        return start + "\n      " + Joiner.on(",\n      ").join(parameters) + ")";
    }

    private String typeName(ThriftType type)
    {
        return typeRenderer.toString(type);
    }

    private static String documentation(List documentation, String indent)
    {
        if (documentation.isEmpty()) {
            return "";
        }
        StringBuilder builder = new StringBuilder()
                .append(indent).append("/**\n");
        for (String line : documentation) {
            builder.append(indent).append(" *");
            if (!line.isEmpty()) {
                builder.append(" ").append(line);
            }
            builder.append("\n");
        }
        return builder.append(indent).append(" */\n").toString();
    }

    private static class LineSeparator
    {
        private boolean first = true;
        private boolean wasDocumented;

        public String getAndUpdate(boolean documented)
        {
            boolean needed = !first && (wasDocumented || documented);
            first = false;
            wasDocumented = documented;
            return needed ? "\n" : "";
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy