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

io.micronaut.annotation.processing.test.JavaParser Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2017-2020 original authors
 *
 * 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
 *
 * https://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.micronaut.annotation.processing.test;

import com.sun.source.util.JavacTask;
import io.micronaut.annotation.processing.*;

import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.util.CollectionUtils;
import spock.util.environment.Jvm;

import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.*;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.util.*;

import static java.nio.charset.StandardCharsets.UTF_8;

/**
 * Utility for parsing Java code.
 * NOTE: Forked from Google Compile Testing Project
 *
 * @author graemerocher
 * @since 1.1
 */
public class JavaParser implements Closeable {

    private final JavaCompiler compiler;
    private final InMemoryJavaFileManager fileManager;
    private final DiagnosticCollector diagnosticCollector;
    private JavacTask lastTask;

    /**
     * Default constructor.
     */
    public JavaParser() {
        this.compiler = ToolProvider.getSystemJavaCompiler();
        this.diagnosticCollector = new DiagnosticCollector<>();
        this.fileManager =
                new InMemoryJavaFileManager(
                        compiler.getStandardFileManager(diagnosticCollector, Locale.getDefault(), UTF_8));
    }

    /**
     * @return The compiler used
     */
    public JavaCompiler getCompiler() {
        return compiler;
    }

    /**
     * @return The file manager
     */
    public JavaFileManager getFileManager() {
        return fileManager;
    }

    /**
     * @return The filer
     */
    public Filer getFiler() {
        return fileManager;
    }

    /**
     * @return Dummy processing environment
     */
    public ProcessingEnvironment getProcessingEnv() {
        return new ProcessingEnvironment() {
            @Override
            public Map getOptions() {
                return Collections.emptyMap();
            }

            @Override
            public Messager getMessager() {
                return new Messager() {
                    @Override
                    public void printMessage(Diagnostic.Kind kind, CharSequence msg) {

                    }

                    @Override
                    public void printMessage(Diagnostic.Kind kind, CharSequence msg, Element e) {

                    }

                    @Override
                    public void printMessage(Diagnostic.Kind kind, CharSequence msg, Element e, AnnotationMirror a) {

                    }

                    @Override
                    public void printMessage(Diagnostic.Kind kind, CharSequence msg, Element e, AnnotationMirror a, AnnotationValue v) {

                    }
                };
            }

            @Override
            public Filer getFiler() {
                return JavaParser.this.fileManager;
            }

            @Override
            public Elements getElementUtils() {
                if (lastTask == null) {
                    throw new IllegalStateException("Call parse first");
                }
                return lastTask.getElements();
            }

            @Override
            public Types getTypeUtils() {
                if (lastTask == null) {
                    throw new IllegalStateException("Call parse first");
                }
                return lastTask.getTypes();
            }

            @Override
            public SourceVersion getSourceVersion() {
                return SourceVersion.RELEASE_17;
            }

            @Override
            public Locale getLocale() {
                return Locale.getDefault();
            }
        };
    }

    /**
     * Parses {@code sources} into {@linkplain com.sun.source.tree.CompilationUnitTree compilation units}. This method
     * does not compile the sources.
     *
     * @param className The class name
     * @param lines The lines to parse
     * @return The elements
     */
    public Iterable parseLines(String className, String... lines) {
        return parse(JavaFileObjects.forSourceLines(className.replace('.', File.separatorChar) + ".java", lines));
    }

    /**
     * Parses {@code sources} into {@code com.sun.source.tree.CompilationUnitTree} units. This method
     * does not compile the sources.
     *
     * @param sources The sources
     * @return The elements
     */
    public Iterable parse(JavaFileObject... sources) {
        this.lastTask = getJavacTask(sources);
        try {
            lastTask.parse();
            return lastTask.analyze();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            List> diagnostics = diagnosticCollector.getDiagnostics();
            for (Diagnostic diagnostic : diagnostics) {
                System.out.println(diagnostic);
                if (diagnostic.getKind() == Diagnostic.Kind.ERROR) {
                    throw new RuntimeException(diagnostic.toString());
                }
            }
        }
    }

    /**
     * @return The last task that was used
     */
    public Optional getLastTask() {
        return Optional.ofNullable(lastTask);
    }

    /**
     * gets the javac task.
     * @param sources The sources
     * @return the task
     */
    public JavacTask getJavacTask(JavaFileObject... sources) {
        Set options = getCompilerOptions();
        this.lastTask = (JavacTask) compiler.getTask(
                null, // explicitly use the default because old javac logs some output on stderr
                fileManager,
                diagnosticCollector,
                options,
                Collections.emptySet(),
                Arrays.asList(sources)
        );
        return lastTask;
    }

    /**
     * Parses {@code sources} into {@code com.sun.source.tree.CompilationUnitTree} units. This method
     * does not compile the sources.
     *
     * @param className The class name
     * @param code the raw code
     * @return The generated file objects
     */
    public Iterable generate(String className, String code) {
        return generate(JavaFileObjects.forSourceString(className, code));
    }

    /**
     * Reads the contents of a generated file as a reader.
     * @param filePath The file path
     * @param className The class name that produces the file
     * @param code The code of the class
     * @return The generated file
     * @throws IOException when an error occurs reading the file
     */
    public @Nullable Reader readGenerated(@NonNull String filePath, String className, String code) throws IOException {
        final String computedPath = fileManager.getMetaInfPath(filePath);
        final Iterable generatedFiles = generate(JavaFileObjects.forSourceString(className, code));
        for (JavaFileObject generatedFile : generatedFiles) {
            if (generatedFile.getName().equals(computedPath)) {
                return generatedFile.openReader(true);
            }
        }
        return null;
    }

    /**
     * Parses {@code sources} into {@code com.sun.source.tree.CompilationUnitTree} units. This method
     * does not compile the sources.
     *
     * @param sources The sources
     * @return The java file objects
     */
    public Iterable generate(JavaFileObject... sources) {
        JavacTask task = getJavacTask(sources);
        try {

            List processors = getAnnotationProcessors();
            task.setProcessors(processors);
            task.generate();
            return fileManager.getOutputFiles();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            List> diagnostics = diagnosticCollector.getDiagnostics();
            StringBuilder error = new StringBuilder();
            for (Diagnostic diagnostic : diagnostics) {
                if (diagnostic.getKind() == Diagnostic.Kind.ERROR) {
                    error.append(diagnostic);
                }
            }
            if (!error.isEmpty()) {
                throw new RuntimeException(error.toString());
            }
        }
    }

    private Set getCompilerOptions() {
        Set options;
        final Jvm jvm = Jvm.getCurrent();
        if (jvm.isJava15Compatible() && !jvm.isJava17Compatible()) {
            options = CollectionUtils.setOf(
                    "--enable-preview",
                    "-source",
                    jvm.getJavaSpecificationVersion()
            );
        } else {
            options = Collections.emptySet();
        }
        return options;
    }

    /**
     * The list of processors to use.
     * @return The processor list
     */
    protected @NonNull List getAnnotationProcessors() {
        List processors = new ArrayList<>();
        processors.add(getTypeElementVisitorProcessor());
        processors.add(getAggregatingTypeElementVisitorProcessor());
        processors.add(new PackageConfigurationInjectProcessor());
        processors.add(getBeanDefinitionInjectProcessor());
        return processors;
    }

    /**
     * The {@link BeanDefinitionInjectProcessor} to use.
     * @return The {@link BeanDefinitionInjectProcessor}
     */
    protected @NonNull BeanDefinitionInjectProcessor getBeanDefinitionInjectProcessor() {
        return new BeanDefinitionInjectProcessor();
    }

    /**
     * The type element visitor processor to use.
     *
     * @return The type element visitor processor
     */
    protected @NonNull TypeElementVisitorProcessor getTypeElementVisitorProcessor() {
        return new TypeElementVisitorProcessor();
    }

    /**
     * The type element visitor processor to use.
     *
     * @return The type element visitor processor
     */
    protected @NonNull AggregatingTypeElementVisitorProcessor getAggregatingTypeElementVisitorProcessor() {
        return new AggregatingTypeElementVisitorProcessor();
    }

    @Override
    public void close() {
        if (fileManager != null) {
            try {
                fileManager.close();
            } catch (Exception e) {
                // ignore
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy