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

com.swirlds.config.processor.antlr.AntlrUtils Maven / Gradle / Ivy

Go to download

Swirlds is a software platform designed to build fully-distributed applications that harness the power of the cloud without servers. Now you can develop applications with fairness in decision making, speed, trust and reliability, at a fraction of the cost of traditional server-based platforms.

There is a newer version: 0.56.5
Show newest version
/*
 * Copyright (C) 2023-2024 Hedera Hashgraph, LLC
 *
 * 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.swirlds.config.processor.antlr;

import com.swirlds.config.processor.ConfigDataRecordDefinition;
import com.swirlds.config.processor.antlr.generated.JavaLexer;
import com.swirlds.config.processor.antlr.generated.JavaParser;
import com.swirlds.config.processor.antlr.generated.JavaParser.AnnotationContext;
import com.swirlds.config.processor.antlr.generated.JavaParser.ClassOrInterfaceModifierContext;
import com.swirlds.config.processor.antlr.generated.JavaParser.CompilationUnitContext;
import com.swirlds.config.processor.antlr.generated.JavaParser.ElementValueContext;
import com.swirlds.config.processor.antlr.generated.JavaParser.ImportDeclarationContext;
import com.swirlds.config.processor.antlr.generated.JavaParser.RecordComponentContext;
import com.swirlds.config.processor.antlr.generated.JavaParser.RecordDeclarationContext;
import com.swirlds.config.processor.antlr.generated.JavaParser.TypeDeclarationContext;
import com.swirlds.config.processor.antlr.generated.JavadocLexer;
import com.swirlds.config.processor.antlr.generated.JavadocParser;
import com.swirlds.config.processor.antlr.generated.JavadocParser.BlockTagContext;
import com.swirlds.config.processor.antlr.generated.JavadocParser.DocumentationContentContext;
import com.swirlds.config.processor.antlr.generated.JavadocParser.DocumentationContext;
import com.swirlds.config.processor.antlr.generated.JavadocParser.TagSectionContext;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.Lexer;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.RuleContext;
import org.antlr.v4.runtime.TokenStream;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.TerminalNode;

/**
 * Utils for antlr4 parsing of Java source code
 */
public final class AntlrUtils {

    public static final String JAVADOC_PARAM = "param";
    public static final String ANNOTATION_VALUE_PROPERTY_NAME = "value";

    private AntlrUtils() {}

    /**
     * Get all annotations for a given {@code record} declaration context
     *
     * @param ctx the antlr context of the {@code record}
     * @return all annotations as antlr context instances
     */
    @NonNull
    public static List getAllAnnotations(@NonNull final RecordDeclarationContext ctx) {
        Objects.requireNonNull(ctx, "ctx must not be null");
        return ctx.getParent().children.stream()
                .filter(child -> child instanceof ClassOrInterfaceModifierContext)
                .map(child -> (ClassOrInterfaceModifierContext) child)
                .flatMap(modifierContext -> modifierContext.children.stream())
                .filter(child -> child instanceof AnnotationContext)
                .map(child -> (AnnotationContext) child)
                .collect(Collectors.toList());
    }

    /**
     * Get all annotations for a given {@code record} component context
     *
     * @param ctx the antlr context of the {@code record} component
     * @return all annotations as antlr context instances
     */
    @NonNull
    public static List getAllAnnotations(@NonNull RecordComponentContext ctx) {
        return Collections.unmodifiableList(ctx.typeType().annotation());
    }

    /**
     * Search in the given annotation context for an optional annotation context for a given {@code Annotation}.
     *
     * @param          the annotation type
     * @param annotation  the annotation class
     * @param annotations all possible annotations
     * @param packageName the package name of the context in that all contexts life in
     * @param imports     the imports of the context in that all contexts life in
     * @return the optional annotation
     */
    @NonNull
    public static  Optional findAnnotationOfType(
            @NonNull final Class annotation,
            @NonNull final List annotations,
            @NonNull final String packageName,
            @NonNull final List imports) {
        Objects.requireNonNull(annotation, "annotation must not be null");
        Objects.requireNonNull(annotations, "annotations must not be null");
        return annotations.stream()
                .filter(c -> c.qualifiedName().getText().endsWith(annotation.getSimpleName()))
                .filter(c -> isValid(c, annotation, packageName, imports))
                .findAny();
    }

    /**
     * Checks if the annotationContext is a valid usage of the annotation.
     *
     * @param annotationContext the annotation context
     * @param annotation        the annotation class
     * @param packageName       the package name of the context in that all contexts life in
     * @param imports           the imports of the context in that all contexts life in
     * @param                the annotation type
     * @return true if the annotation is valid
     */
    private static  boolean isValid(
            @NonNull final AnnotationContext annotationContext,
            @NonNull final Class annotation,
            @NonNull final String packageName,
            @NonNull final List imports) {
        Objects.requireNonNull(annotationContext, "annotationContext must not be null");
        Objects.requireNonNull(annotation, "annotation must not be null");
        Objects.requireNonNull(packageName, "packageName must not be null");
        Objects.requireNonNull(imports, "imports must not be null");
        return imports.contains(annotation.getName())
                || annotationContext.qualifiedName().getText().equals(annotation.getName())
                || annotationContext.qualifiedName().getText().equals(packageName + "." + annotation.getSimpleName());
    }

    /**
     * Returns the compilation unit context for a given antlr context by doing a (recursive) search in the parent
     * contexts.
     *
     * @param ctx the antlr context
     * @return the compilation unit context
     */
    @NonNull
    public static CompilationUnitContext getCompilationUnit(@NonNull final ParserRuleContext ctx) {
        Objects.requireNonNull(ctx, "ctx must not be null");
        if (ctx instanceof CompilationUnitContext compilationUnitContext) {
            return compilationUnitContext;
        } else {
            return getCompilationUnit(ctx.getParent());
        }
    }

    /**
     * Returns all imports of a given declaration context (by going up to the compilation unit context)
     *
     * @param ctx the antlr contexts
     * @return all imports as strings
     */
    @NonNull
    public static List getImports(@NonNull final ParserRuleContext ctx) {
        final CompilationUnitContext compilationUnitContext = getCompilationUnit(ctx);
        return compilationUnitContext.importDeclaration().stream()
                .map(ImportDeclarationContext::qualifiedName)
                .map(RuleContext::getText)
                .collect(Collectors.toList());
    }

    /**
     * Returns the package name of a given declaration context (by going up to the compilation unit context)
     *
     * @param ctx the antlr context
     * @return the package name
     */
    @NonNull
    public static String getPackage(@NonNull final ParserRuleContext ctx) {
        final CompilationUnitContext compilationUnitContext = getCompilationUnit(ctx);
        return compilationUnitContext.packageDeclaration().qualifiedName().getText();
    }

    /**
     * Returns the value of an annotation attribute
     *
     * @param annotationContext the annotation context
     * @param identifier        the identifier of the attribute
     * @return the value of the attribute
     */
    @NonNull
    public static Optional getAnnotationValue(
            @NonNull final AnnotationContext annotationContext, @NonNull final String identifier) {
        if (ANNOTATION_VALUE_PROPERTY_NAME.equals(identifier)) {
            final ElementValueContext elementValueContext = annotationContext.elementValue();
            if (elementValueContext != null) {
                return Optional.of(elementValueContext.getText()).map(text -> {
                    if (text.startsWith("\"") && text.endsWith("\"")) {
                        return text.substring(1, text.length() - 1);
                    }
                    return text;
                });
            }
        }
        if (annotationContext.elementValuePairs() == null) {
            return Optional.empty();
        }
        return annotationContext.elementValuePairs().elementValuePair().stream()
                .filter(p -> Objects.equals(p.identifier().getText(), identifier))
                .map(p -> p.elementValue().getText())
                .map(text -> {
                    if (text.startsWith("\"") && text.endsWith("\"")) {
                        return text.substring(1, text.length() - 1);
                    }
                    return text;
                })
                .findAny();
    }

    public static boolean isJavaDocNode(@NonNull final ParseTree node) {
        if (node instanceof TerminalNode terminalNode) {
            return terminalNode.getSymbol().getType() == JavaParser.JAVADOC_COMMENT;
        }
        return false;
    }

    public static List getRecordDeclarationContext(
            @NonNull final CompilationUnitContext compilationUnitContext) {
        return compilationUnitContext.children.stream()
                .filter(child -> child instanceof TypeDeclarationContext)
                .map(child -> (TypeDeclarationContext) child)
                .flatMap(typeDeclarationContext -> typeDeclarationContext.children.stream())
                .filter(child -> child instanceof RecordDeclarationContext)
                .map(child -> (RecordDeclarationContext) child)
                .collect(Collectors.toList());
    }

    /**
     * Returns all {@code @param} tags of a given java doc. The key of the map is the name of the parameter and the
     * value is the description of the parameter.
     *
     * @param rawDocContent the javadoc
     * @return the params
     */
    @NonNull
    public static Map getJavaDocParams(@NonNull String rawDocContent) {
        Objects.requireNonNull(rawDocContent, "rawDocContent must not be null");
        final Map params = new HashMap<>();
        final Lexer lexer = new JavadocLexer(CharStreams.fromString(rawDocContent));
        final TokenStream tokens = new CommonTokenStream(lexer);
        final JavadocParser parser = new JavadocParser(tokens);
        final DocumentationContext documentationContext = parser.documentation();
        Optional.ofNullable(documentationContext.exception).ifPresent(e -> {
            throw new IllegalStateException("Error in ANTLR parsing", e);
        });
        documentationContext.children.stream()
                .filter(c -> c instanceof DocumentationContentContext)
                .map(c -> (DocumentationContentContext) c)
                .flatMap(context -> context.children.stream())
                .filter(c -> c instanceof TagSectionContext)
                .map(c -> (TagSectionContext) c)
                .flatMap(context -> context.children.stream())
                .filter(c -> c instanceof BlockTagContext)
                .map(c -> (BlockTagContext) c)
                .filter(c -> Objects.equals(c.blockTagName().NAME().getText(), JAVADOC_PARAM))
                .map(AntlrUtils::extractFullText)
                .filter(fullText -> !fullText.isBlank())
                .filter(fullText -> fullText.contains(" "))
                .forEach(fullText -> {
                    final String paramName = fullText.split(" ")[0].trim();
                    final String description =
                            fullText.substring(paramName.length()).trim();
                    params.put(paramName.trim(), description.trim());
                });
        return params;
    }

    private static String extractFullText(@NonNull final BlockTagContext c) {
        final String[] split = c.getText().trim().split("\n \\*");
        final String result = Arrays.stream(split)
                .map(String::trim)
                .filter(s -> !s.isBlank())
                .reduce((a, b) -> a + " " + b)
                .orElse("");
        if (result.startsWith("@param")) {
            return result.substring("@param".length()).trim();
        }
        return result.trim();
    }

    /**
     * Parse the given file content and return a {@link ConfigDataRecordDefinition} object. The file must be a valid
     * Java file.
     *
     * @param fileContent the file content to parse
     * @return the {@link ConfigDataRecordDefinition} object
     */
    public static CompilationUnitContext parse(@NonNull final String fileContent) {
        final Lexer lexer = new JavaLexer(CharStreams.fromString(fileContent));
        final TokenStream tokens = new CommonTokenStream(lexer);
        final JavaParser parser = new JavaParser(tokens);
        final CompilationUnitContext context = parser.compilationUnit();
        Optional.ofNullable(context.exception).ifPresent(e -> {
            throw new IllegalStateException("Error in ANTLR parsing", e);
        });
        return context;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy