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

io.helidon.common.processor.classmodel.Executable Maven / Gradle / Ivy

/*
 * Copyright (c) 2023 Oracle and/or its affiliates.
 *
 * 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 io.helidon.common.processor.classmodel;

import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Supplier;

import io.helidon.common.types.AccessModifier;
import io.helidon.common.types.TypeName;

/**
 * Executable base, used by method and constructor.
 */
public abstract class Executable extends AnnotatedComponent {

    private final Content content;
    private final List parameters;
    private final List exceptions;

    Executable(Builder builder) {
        super(builder);
        this.content = builder.contentBuilder.build();
        this.parameters = List.copyOf(builder.parameters.values());
        this.exceptions = List.copyOf(builder.exceptions);
    }

    @Override
    void addImports(ImportOrganizer.Builder imports) {
        super.addImports(imports);
        parameters.forEach(parameter -> parameter.addImports(imports));
        content.addImports(imports);
        exceptions.forEach(exc -> exc.addImports(imports));
    }

    void writeThrows(ModelWriter writer, Set declaredTokens, ImportOrganizer imports, ClassType classType)
            throws IOException {
        if (!exceptions().isEmpty()) {
            writer.write(" throws ");
            boolean first = true;
            for (Type exception : exceptions()) {
                if (first) {
                    first = false;
                } else {
                    writer.write(", ");
                }
                exception.writeComponent(writer, declaredTokens, imports, classType);
            }
        }
    }

    void writeBody(ModelWriter writer, ImportOrganizer imports) throws IOException {
        writer.increasePaddingLevel();
        writer.write("\n");
        content.writeBody(writer, imports);
        writer.decreasePaddingLevel();
        writer.write("\n");
    }

    List parameters() {
        return parameters;
    }

    List exceptions() {
        return exceptions;
    }

    boolean hasBody() {
        return content.hasBody();
    }

    /**
     * Base builder from executable components (method an constructor).
     *
     * @param  type of the builder
     * @param  type of the built instance
     */
    public abstract static class Builder, T extends Executable>
            extends AnnotatedComponent.Builder {

        private final Map parameters = new LinkedHashMap<>();
        private final Set exceptions = new LinkedHashSet<>();
        private final Content.Builder contentBuilder = Content.builder();

        Builder() {
        }

        @Override
        public B javadoc(Javadoc javadoc) {
            return super.javadoc(javadoc);
        }

        @Override
        public B addJavadocTag(String tag, String description) {
            return super.addJavadocTag(tag, description);
        }

        @Override
        public B accessModifier(AccessModifier accessModifier) {
            return super.accessModifier(accessModifier);
        }

        /**
         * Add text line to the content.
         * New line character is added after this line.
         *
         * @param line line to add
         * @return updated builder instance
         */
        public B addLine(String line) {
            contentBuilder.addLine(line);
            return identity();
        }

        /**
         * Add text line to the content.
         * New line character is not added after this line, so all newly added text will be appended to the same line.
         *
         * @param line line to add
         * @return updated builder instance
         */
        public B add(String line) {
            contentBuilder.add(line);
            return identity();
        }

        /**
         * Clears created content.
         *
         * @return updated builder instance
         */
        public B clearContent() {
            contentBuilder.clearContent();
            return identity();
        }

        /**
         * Obtained fully qualified type name is enclosed between {@link ClassModel#TYPE_TOKEN} tokens.
         * Class names in such a format are later recognized as class names for import handling.
         *
         * @param fqClassName fully qualified class name to import
         * @return updated builder instance
         */
        public B typeName(String fqClassName) {
            contentBuilder.typeName(fqClassName);
            return identity();
        }

        /**
         * Obtained type is enclosed between {@link ClassModel#TYPE_TOKEN} tokens.
         * Class names in such a format are later recognized as class names for import handling.
         *
         * @param type type to import
         * @return updated builder instance
         */
        public B typeName(Class type) {
            return typeName(type.getCanonicalName());
        }

        /**
         * Obtained type is enclosed between {@link ClassModel#TYPE_TOKEN} tokens.
         * Class names in such a format are later recognized as class names for import handling.
         *
         * @param typeName type name to import
         * @return updated builder instance
         */
        public B typeName(TypeName typeName) {
            return typeName(typeName.resolvedName());
        }

        /**
         * Adds single padding.
         * This extra padding is added only once. If more permanent padding increment is needed use {{@link #increasePadding()}}.
         *
         * @return updated builder instance
         */
        public B padding() {
            contentBuilder.padding();
            return identity();
        }

        /**
         * Adds padding with number of repetitions.
         * This extra padding is added only once. If more permanent padding increment is needed use {{@link #increasePadding()}}.
         *
         * @param repetition number of padding repetitions
         * @return updated builder instance
         */
        public B padding(int repetition) {
            contentBuilder.padding(repetition);
            return identity();
        }

        /**
         * Method for manual padding increment.
         * This method will affect padding of the later added content.
         *
         * @return updated builder instance
         */
        public B increasePadding() {
            contentBuilder.increasePadding();
            return identity();
        }

        /**
         * Method for manual padding decrement.
         * This method will affect padding of the later added content.
         *
         * @return updated builder instance
         */
        public B decreasePadding() {
            contentBuilder.decreasePadding();
            return identity();
        }

        /**
         * Set new content.
         * This method replaces previously created content in this builder.
         *
         * @param content content to be set
         * @return updated builder instance
         */
        public B content(String content) {
            return content(List.of(content));
        }

        /**
         * Set new content.
         * This method replaces previously created content in this builder.
         *
         * @param content content to be set
         * @return updated builder instance
         */
        public B content(List content) {
            contentBuilder.content(content);
            return identity();
        }

        /**
         * Add new method parameter.
         *
         * @param consumer method builder consumer
         * @return updated builder instance
         */
        public B addParameter(Consumer consumer) {
            Parameter.Builder builder = Parameter.builder();
            consumer.accept(builder);
            return addParameter(builder.build());
        }

        /**
         * Add new method parameter.
         *
         * @param parameter method parameter
         * @return updated builder instance
         */
        public B addParameter(Parameter parameter) {
            this.parameters.put(parameter.name(), parameter);
            return this.addJavadocParameter(parameter.name(), parameter.description());
        }

        /**
         * Add new method parameter.
         *
         * @param supplier method parameter supplier
         * @return updated builder instance
         */
        public B addParameter(Supplier supplier) {
            Parameter parameter = supplier.get();
            this.parameters.put(parameter.name(), parameter);
            return this.addJavadocParameter(parameter.name(), parameter.description());
        }

        /**
         * Add a declared throws definition.
         *
         * @param exception exception declaration
         * @param description description to add to javadoc
         * @return updated builder instance
         */
        public B addThrows(TypeName exception, String description) {
            Objects.requireNonNull(exception);
            Objects.requireNonNull(description);
            return addThrows(ex -> ex.type(exception)
                    .description(description));
        }

        /**
         * Add a declared throws definition.
         *
         * @param consumer exception declaration builder consumer
         * @return updated builder instance
         */
        public B addThrows(Consumer consumer) {
            Objects.requireNonNull(consumer);
            Throws.Builder builder = Throws.builder();
            consumer.accept(builder);
            return addThrows(builder);
        }

        /**
         * Add a declared throws definition.
         *
         * @param supplier exception declaration supplier
         * @return updated builder instance
         */
        public B addThrows(Supplier supplier) {
            Objects.requireNonNull(supplier);
            return addThrows(supplier.get());
        }

        /**
         * Add a declared throws definition.
         *
         * @param exception exception declaration
         * @return updated builder instance
         */
        public B addThrows(Throws exception) {
            Objects.requireNonNull(exception);
            this.exceptions.add(exception.type());
            return addJavadocThrows(exception.type().fqTypeName(), exception.description());
        }

        @Override
        public B generateJavadoc(boolean generateJavadoc) {
            return super.generateJavadoc(generateJavadoc);
        }

        Map parameters() {
            return parameters;
        }
    }

}