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

io.micronaut.ast.groovy.visitor.GroovyVisitorContext Maven / Gradle / Ivy

There is a newer version: 4.6.5
Show 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.ast.groovy.visitor;

import groovy.lang.GroovyClassLoader;
import io.micronaut.ast.groovy.GroovyNativeElementHelper;
import io.micronaut.ast.groovy.annotation.GroovyAnnotationMetadataBuilder;
import io.micronaut.ast.groovy.annotation.GroovyElementAnnotationMetadataFactory;
import io.micronaut.ast.groovy.scan.ClassPathAnnotationScanner;
import io.micronaut.ast.groovy.utils.AstMessageUtils;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.convert.ArgumentConversionContext;
import io.micronaut.core.convert.value.MutableConvertibleValues;
import io.micronaut.core.convert.value.MutableConvertibleValuesMap;
import io.micronaut.core.reflect.ClassUtils;
import io.micronaut.core.util.ArgumentUtils;
import io.micronaut.core.util.CollectionUtils;
import io.micronaut.expressions.context.DefaultExpressionCompilationContextFactory;
import io.micronaut.expressions.context.ExpressionCompilationContextFactory;
import io.micronaut.inject.ast.ClassElement;
import io.micronaut.inject.ast.Element;
import io.micronaut.inject.ast.annotation.ElementAnnotationMetadataFactory;
import io.micronaut.inject.visitor.VisitorContext;
import io.micronaut.inject.visitor.util.VisitorContextUtils;
import io.micronaut.inject.writer.AbstractBeanDefinitionBuilder;
import io.micronaut.inject.writer.ClassWriterOutputVisitor;
import io.micronaut.inject.writer.GeneratedFile;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.control.ClassNodeResolver;
import org.codehaus.groovy.control.CompilationUnit;
import org.codehaus.groovy.control.Janitor;
import org.codehaus.groovy.control.SourceUnit;

import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

/**
 * The visitor context when visiting Groovy code.
 *
 * @author James Kleeh
 * @author Graeme Rocher
 * @since 1.0
 */
@Internal
public class GroovyVisitorContext implements VisitorContext {
    private static final MutableConvertibleValues VISITOR_ATTRIBUTES = new MutableConvertibleValuesMap<>();
    private final CompilationUnit compilationUnit;
    private final ClassWriterOutputVisitor outputVisitor;
    private final SourceUnit sourceUnit;
    private final MutableConvertibleValues attributes;
    private final List generatedResources = new ArrayList<>();
    private final GroovyElementFactory groovyElementFactory;
    private final List beanDefinitionBuilders = new ArrayList<>();
    private final GroovyElementAnnotationMetadataFactory elementAnnotationMetadataFactory;
    private final ExpressionCompilationContextFactory expressionCompilationContextFactory;
    private final GroovyNativeElementHelper nativeElementHelper;
    private final GroovyAnnotationMetadataBuilder annotationMetadataBuilder;

    /**
     * @param sourceUnit      The source unit
     * @param compilationUnit The compilation unit
     */
    public GroovyVisitorContext(SourceUnit sourceUnit, @Nullable CompilationUnit compilationUnit) {
        this(sourceUnit, compilationUnit, new GroovyClassWriterOutputVisitor(compilationUnit));
    }

    /**
     * @param sourceUnit      The source unit
     * @param compilationUnit The compilation unit
     * @param outputVisitor   The class writer output visitor
     */
    public GroovyVisitorContext(SourceUnit sourceUnit, @Nullable CompilationUnit compilationUnit, ClassWriterOutputVisitor outputVisitor) {
        this.sourceUnit = sourceUnit;
        this.compilationUnit = compilationUnit;
        this.outputVisitor = outputVisitor;
        this.attributes = VISITOR_ATTRIBUTES;
        this.nativeElementHelper = new GroovyNativeElementHelper();
        this.groovyElementFactory = new GroovyElementFactory(this);
        this.annotationMetadataBuilder = new GroovyAnnotationMetadataBuilder(sourceUnit, compilationUnit, nativeElementHelper, this);
        this.elementAnnotationMetadataFactory = new GroovyElementAnnotationMetadataFactory(false, annotationMetadataBuilder);
        this.expressionCompilationContextFactory = new DefaultExpressionCompilationContextFactory(this);
    }

    @Override
    public Language getLanguage() {
        return Language.GROOVY;
    }

    @NonNull
    @Override
    public Iterable getClasspathResources(@NonNull String path) {
        try {
            final Enumeration resources = compilationUnit.getClassLoader().getResources(path);
            return CollectionUtils.enumerationToIterable(resources);
        } catch (IOException e) {
            return Collections.emptyList();
        }
    }

    @Override
    public Optional getClassElement(String name) {
        return getClassElement(name, getElementAnnotationMetadataFactory());
    }

    @Override
    public Optional getClassElement(String name, ElementAnnotationMetadataFactory annotationMetadataFactory) {
        if (name == null) {
            return Optional.empty();
        } else if (compilationUnit == null) {
            return Optional.ofNullable(classNodeFromClassLoader(name)).map(cn ->
                groovyElementFactory.newClassElement(cn, annotationMetadataFactory)
            );
        }

        ClassNodeResolver.LookupResult lookupResult = compilationUnit.getClassNodeResolver().resolveName(name, compilationUnit);
        Optional classNode;
        if (lookupResult != null) {
            classNode = Optional.ofNullable(lookupResult.getClassNode());
        } else {
            classNode = Optional.ofNullable(compilationUnit.getClassNode(name));
        }

        ClassNode finalClassNode = classNode.orElseGet(() -> classNodeFromClassLoader(name));

        return Optional.ofNullable(finalClassNode).map(cn -> groovyElementFactory.newClassElement(cn, annotationMetadataFactory));
    }

    private ClassNode classNodeFromClassLoader(String name) {
        ClassNode cn = null;
        if (sourceUnit != null) {
            GroovyClassLoader classLoader = sourceUnit.getClassLoader();
            if (classLoader != null) {
                cn = ClassUtils.forName(name, classLoader).map(ClassHelper::make).orElse(null);
            }
        }
        return cn;
    }

    @Override
    public Optional getClassElement(Class type) {
        final ClassNode classNode = ClassHelper.makeCached(type);
        return Optional.of(groovyElementFactory.newClassElement(classNode, getElementAnnotationMetadataFactory()));
    }

    @NonNull
    @Override
    public ClassElement[] getClassElements(@NonNull String aPackage, @NonNull String... stereotypes) {
        ArgumentUtils.requireNonNull("aPackage", aPackage);
        ArgumentUtils.requireNonNull("stereotypes", stereotypes);

        if (compilationUnit == null) {
            return ClassElement.ZERO_CLASS_ELEMENTS;
        }

        ClassPathAnnotationScanner scanner = new ClassPathAnnotationScanner(compilationUnit.getClassLoader());
        List classElements = new ArrayList<>();
        for (String s : stereotypes) {
            scanner.scan(s, aPackage).forEach(aClass -> {
                final ClassNode classNode = ClassHelper.make(aClass);
                classElements.add(groovyElementFactory.newClassElement(classNode, getElementAnnotationMetadataFactory()));
            });
        }
        return classElements.toArray(ClassElement.ZERO_CLASS_ELEMENTS);
    }

    @NonNull
    @Override
    public GroovyElementFactory getElementFactory() {
        return groovyElementFactory;
    }

    @NonNull
    @Override
    public GroovyElementAnnotationMetadataFactory getElementAnnotationMetadataFactory() {
        return elementAnnotationMetadataFactory;
    }

    @Override
    public ExpressionCompilationContextFactory getExpressionCompilationContextFactory() {
        return this.expressionCompilationContextFactory;
    }

    @Override
    public GroovyAnnotationMetadataBuilder getAnnotationMetadataBuilder() {
        return annotationMetadataBuilder;
    }

    @Override
    public void info(String message, @Nullable Element element) {
        StringBuilder msg = new StringBuilder("Note: ").append(message);
        if (element instanceof AbstractGroovyElement abstractGroovyElement) {
            ASTNode expr = abstractGroovyElement.getNativeType().annotatedNode();
            final String sample = sourceUnit.getSample(expr.getLineNumber(), expr.getColumnNumber(), new Janitor());
            msg.append("\n\n").append(sample);
        }
        System.out.println(msg);
    }

    @Override
    public void info(String message) {
        System.out.println("Note: " + message);
    }

    @Override
    public void fail(String message, @Nullable Element element) {
        if (element instanceof AbstractGroovyElement abstractGroovyElement) {
            AstMessageUtils.error(sourceUnit, abstractGroovyElement.getNativeType().annotatedNode(), message);
        } else {
            AstMessageUtils.error(sourceUnit, null, message);
        }
    }

    public final void fail(String message, ASTNode expr) {
        AstMessageUtils.error(sourceUnit, expr, message);
    }

    @Override
    public void warn(String message, @Nullable Element element) {
        if (element instanceof AbstractGroovyElement abstractGroovyElement) {
            AstMessageUtils.warning(sourceUnit, abstractGroovyElement.getNativeType().annotatedNode(), message);
        } else {
            AstMessageUtils.warning(sourceUnit, null, message);
        }
    }

    @Override
    public OutputStream visitClass(String classname, @Nullable Element originatingElement) throws IOException {
        return outputVisitor.visitClass(classname, originatingElement);
    }

    @Override
    public OutputStream visitClass(String classname, Element... originatingElements) throws IOException {
        return outputVisitor.visitClass(classname, originatingElements);
    }

    @Override
    public void visitServiceDescriptor(String type, String classname) {
        outputVisitor.visitServiceDescriptor(type, classname);
    }

    @Override
    public void visitServiceDescriptor(String type, String classname, Element originatingElement) {
        outputVisitor.visitServiceDescriptor(type, classname, originatingElement);
    }

    @Override
    public Optional visitMetaInfFile(String path, Element... originatingElements) {
        return outputVisitor.visitMetaInfFile(path, originatingElements);
    }

    @Override
    public Optional visitGeneratedFile(String path) {
        return outputVisitor.visitGeneratedFile(path);
    }

    @Override
    public Optional visitGeneratedFile(String path, Element... originatingElements) {
        return outputVisitor.visitGeneratedFile(path, originatingElements);
    }

    @Override
    public Optional visitGeneratedSourceFile(String packageName, String fileNameWithoutExtension, Element... originatingElements) {
        return outputVisitor.visitGeneratedSourceFile(packageName, fileNameWithoutExtension, originatingElements);
    }

    @Override
    public void finish() {
        outputVisitor.finish();
    }

    /**
     * @return The source unit
     */
    SourceUnit getSourceUnit() {
        return sourceUnit;
    }

    /**
     * @return The compilation unit
     */
    @Internal
    public CompilationUnit getCompilationUnit() {
        return compilationUnit;
    }

    /**
     * @return The native element helper
     */
    @Internal
    public GroovyNativeElementHelper getNativeElementHelper() {
        return nativeElementHelper;
    }

    /**
     * Groovy options source are {@link System#getProperties()} based.
     * 

All properties MUST start with {@link GroovyVisitorContext#MICRONAUT_BASE_OPTION_NAME}

* @return options {@link Map} */ @Override public Map getOptions() { return VisitorContextUtils.getSystemOptions(); } @Override public MutableConvertibleValues put(CharSequence key, @Nullable Object value) { return attributes.put(key, value); } @Override public MutableConvertibleValues remove(CharSequence key) { return attributes.remove(key); } @Override public MutableConvertibleValues clear() { return attributes.clear(); } @Override public Set names() { return attributes.names(); } @Override public Collection values() { return attributes.values(); } @Override public Optional get(CharSequence name, ArgumentConversionContext conversionContext) { return attributes.get(name, conversionContext); } @Override public Collection getGeneratedResources() { return Collections.unmodifiableCollection(generatedResources); } @Override public void addGeneratedResource(@NonNull String resource) { generatedResources.add(resource); } /** * @return Gets the produced bean definition builders. */ @Internal public List getBeanElementBuilders() { final ArrayList current = new ArrayList<>(beanDefinitionBuilders); beanDefinitionBuilders.clear(); return current; } /** * Adds a java bean definition builder. * @param groovyBeanDefinitionBuilder The groovy bean definition builder */ @Internal void addBeanDefinitionBuilder(GroovyBeanDefinitionBuilder groovyBeanDefinitionBuilder) { this.beanDefinitionBuilders.add(groovyBeanDefinitionBuilder); } }