VisitorContext.java
/*
* 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.inject.visitor;
import io.micronaut.core.annotation.Experimental;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.convert.value.MutableConvertibleValues;
import io.micronaut.expressions.context.ExpressionCompilationContextFactory;
import io.micronaut.inject.annotation.AbstractAnnotationMetadataBuilder;
import io.micronaut.inject.ast.ClassElement;
import io.micronaut.inject.ast.Element;
import io.micronaut.inject.ast.ElementFactory;
import io.micronaut.inject.ast.annotation.ElementAnnotationMetadataFactory;
import io.micronaut.inject.writer.ClassWriterOutputVisitor;
import io.micronaut.inject.writer.GeneratedFile;
import java.net.URI;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
/**
* Provides a way for {@link TypeElementVisitor} classes to log messages during compilation and fail compilation.
*
* @author James Kleeh
* @author Graeme Rocher
* @since 1.0
*/
public interface VisitorContext extends MutableConvertibleValues<Object>, ClassWriterOutputVisitor {
String MICRONAUT_BASE_OPTION_NAME = "micronaut";
String MICRONAUT_PROCESSING_PROJECT_DIR = "micronaut.processing.project.dir";
String MICRONAUT_PROCESSING_GROUP = "micronaut.processing.group";
String MICRONAUT_PROCESSING_MODULE = "micronaut.processing.module";
/**
* @return The visitor context's language.
* @since 4.2.0
*/
Language getLanguage();
/**
* Gets the element factory for this visitor context.
*
* @return The element factory
* @since 2.3.0
*/
@NonNull
ElementFactory<?, ?, ?, ?> getElementFactory();
/**
* Gets the element annotation metadata factory.
*
* @return The element annotation metadata factory
* @since 4.0.0
*/
@NonNull
ElementAnnotationMetadataFactory getElementAnnotationMetadataFactory();
/**
* @return The expression compilation context factory.
* @since 4.0.0
*/
@Experimental
@NonNull
ExpressionCompilationContextFactory getExpressionCompilationContextFactory();
/**
* Gets the annotation metadata builder.
*
* @return The annotation metadata builder
*
* @since 4.0.0
*/
@Internal
@NonNull
AbstractAnnotationMetadataBuilder<?, ?> getAnnotationMetadataBuilder();
/**
* Allows printing informational messages.
*
* @param message The message
* @param element The element
*/
void info(String message, @Nullable Element element);
/**
* Allows printing informational messages.
*
* @param message The message
*/
void info(String message);
/**
* Allows failing compilation for a given element with the given message.
*
* @param message The message
* @param element The element
*/
void fail(String message, @Nullable Element element);
/**
* Allows printing a warning for the given message and element.
*
* @param message The message
* @param element The element
*/
void warn(String message, @Nullable Element element);
/**
* @return The visitor configuration
*/
default @NonNull VisitorConfiguration getConfiguration() {
return VisitorConfiguration.DEFAULT;
}
/**
* Visit a file within the META-INF directory.
*
* @param path The path to the file
* @return An optional file it was possible to create it
*/
@Override
@Experimental
Optional<GeneratedFile> visitMetaInfFile(String path, Element... originatingElements);
/**
* Visit a file that will be located within the generated source directory.
*
* @param path The path to the file
* @return An optional file it was possible to create it
*/
@Override
@Experimental
Optional<GeneratedFile> visitGeneratedFile(String path);
/**
* Obtain a set of resources from the user classpath.
*
* @param path The path
* @return An iterable of resources
*/
@Experimental
default @NonNull Iterable<URL> getClasspathResources(@NonNull String path) {
return Collections.emptyList();
}
/**
* Obtain the project directory.
*
* @return An optional wrapping the project directory
*/
default Optional<Path> getProjectDir() {
Optional<Path> projectDir = get(MICRONAUT_PROCESSING_PROJECT_DIR, Path.class);
if (projectDir.isPresent()) {
return projectDir;
}
// let's find the projectDir
Optional<GeneratedFile> dummyFile = visitGeneratedFile("dummy" + System.nanoTime());
if (dummyFile.isPresent()) {
URI uri = dummyFile.get().toURI();
// happens in tests 'mem:///CLASS_OUTPUT/dummy'
if (uri.getScheme() != null && !uri.getScheme().equals("mem")) {
// assume files are generated in 'build' or 'target' directories
Path dummy = Paths.get(uri).normalize();
while (dummy != null) {
Path dummyFileName = dummy.getFileName();
if (dummyFileName != null && ("build".equals(dummyFileName.toString()) || "target".equals(dummyFileName.toString()))) {
projectDir = Optional.ofNullable(dummy.getParent());
put(MICRONAUT_PROCESSING_PROJECT_DIR, dummy.getParent());
break;
}
dummy = dummy.getParent();
}
}
}
return projectDir;
}
/**
* Provide the Path to the annotation processing classes output directory, i.e. the parent of META-INF.
*
* <p>This might, for example, be used as a convenience for {@link TypeElementVisitor} classes to provide
* relative path strings to {@link VisitorContext#addGeneratedResource(String)}</p>
* <pre>
* Path resource = ... // absolute path to the resource
* visitorContext.getClassesOutputPath().ifPresent(path ->
* visitorContext.addGeneratedResource(path.relativize(resource).toString()));
* </pre>
*
* @return Path pointing to the classes output directory
*/
@Experimental
default Optional<Path> getClassesOutputPath() {
Optional<GeneratedFile> dummy = visitMetaInfFile("dummy", Element.EMPTY_ELEMENT_ARRAY);
if (dummy.isPresent()) {
// we want the parent directory of META-INF/dummy
Path classesOutputDir = Paths.get(dummy.get().toURI()).getParent().getParent();
return Optional.of(classesOutputDir);
}
return Optional.empty();
}
/**
* This method will look up another class element by name. If it cannot be found an empty optional will be returned.
*
* @param name The name
* @return The class element
*/
default Optional<ClassElement> getClassElement(String name) {
return Optional.empty();
}
/**
* This method will look up another class element by name. If it cannot be found an empty optional will be returned.
*
* @param name The name
* @param annotationMetadataFactory The element annotation metadata factory
* @return The class element
* @since 4.0.0
*/
default Optional<ClassElement> getClassElement(String name, ElementAnnotationMetadataFactory annotationMetadataFactory) {
return Optional.empty();
}
/**
* This method will look up another class element by name. If it cannot be found an exception thrown.
*
* @param name The name
* @param annotationMetadataFactory The element annotation metadata factory
* @return The class element
* @since 4.0.0
*/
default ClassElement getRequiredClassElement(String name, ElementAnnotationMetadataFactory annotationMetadataFactory) {
return getClassElement(name, annotationMetadataFactory).orElseThrow(() -> new IllegalStateException("Unknown type: " + name));
}
/**
* This method will look up another class element by name. If it cannot be found an empty optional will be returned.
*
* @param type The name
* @return The class element
*/
default Optional<ClassElement> getClassElement(Class<?> type) {
if (type != null) {
return getClassElement(type.getName());
}
return Optional.empty();
}
/**
* Find all the classes within the given package and having the given annotation.
*
* @param aPackage The package
* @param stereotypes The stereotypes
* @return The class elements
*/
default @NonNull ClassElement[] getClassElements(@NonNull String aPackage, @NonNull String... stereotypes) {
return ClassElement.ZERO_CLASS_ELEMENTS;
}
/**
* The annotation processor environment custom options.
* <p><b>All options names MUST start with {@link VisitorContext#MICRONAUT_BASE_OPTION_NAME}</b></p>
*
* @return A Map with annotation processor runtime options
* @see javax.annotation.processing.ProcessingEnvironment#getOptions()
*/
@Experimental
default Map<String, String> getOptions() {
return Collections.emptyMap();
}
/**
* Provide a collection of generated classpath resources that other TypeElement visitors might want to consume.
* The generated resources are intended to be strings paths relative to the classpath root.
*
* @return a possibly empty collection of resource paths
*/
@Experimental
default Collection<String> getGeneratedResources() {
info("EXPERIMENTAL: Compile time resource contribution to the context is experimental", null);
return Collections.emptyList();
}
/**
* Some TypeElementVisitors generate classpath resources that other visitors might be interested in.
* The generated resources are intended to be strings paths relative to the classpath root
*
* @param resource the relative path to add
*/
@Experimental
default void addGeneratedResource(String resource) {
info("EXPERIMENTAL: Compile time resource contribution to the context is experimental", null);
}
/**
* The languages that are supported in source code generation.
* Not all visitors may support all languages.
*
* @since 4.2.0
*/
enum Language {
JAVA("Java"),
GROOVY("Groovy"),
KOTLIN("Kotlin");
private final String displayName;
Language(String displayName) {
this.displayName = displayName;
}
@Override
public String toString() {
return displayName;
}
}
}