EvaluatedExpressionWriter.java
/*
* Copyright 2017-2022 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.expressions;
import io.micronaut.context.expressions.AbstractEvaluatedExpression;
import io.micronaut.core.annotation.Generated;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.expressions.ExpressionEvaluationContext;
import io.micronaut.core.reflect.ReflectionUtils;
import io.micronaut.expressions.context.ExpressionWithContext;
import io.micronaut.expressions.parser.CompoundEvaluatedExpressionParser;
import io.micronaut.expressions.parser.compilation.ExpressionCompilationContext;
import io.micronaut.expressions.parser.compilation.ExpressionVisitorContext;
import io.micronaut.expressions.parser.exception.ExpressionCompilationException;
import io.micronaut.expressions.parser.exception.ExpressionParsingException;
import io.micronaut.inject.ast.Element;
import io.micronaut.inject.processing.ProcessingException;
import io.micronaut.inject.visitor.VisitorContext;
import io.micronaut.inject.writer.ClassOutputWriter;
import io.micronaut.inject.writer.ClassWriterOutputVisitor;
import io.micronaut.sourcegen.bytecode.ByteCodeWriter;
import io.micronaut.sourcegen.model.ClassDef;
import io.micronaut.sourcegen.model.ClassTypeDef;
import io.micronaut.sourcegen.model.MethodDef;
import io.micronaut.sourcegen.model.StatementDef;
import javax.lang.model.element.Modifier;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Writer for compile-time expressions.
*
* @author Sergey Gavrilov
* @since 4.0.0
*/
@Internal
public final class EvaluatedExpressionWriter implements ClassOutputWriter {
private static final ByteCodeWriter BYTE_CODE_WRITER = new ByteCodeWriter();
private static final Method DO_EVALUATE_METHOD
= ReflectionUtils.getRequiredMethod(AbstractEvaluatedExpression.class, "doEvaluate", ExpressionEvaluationContext.class);
private static final Set<String> WRITTEN_CLASSES = new HashSet<>();
private final ExpressionWithContext expressionMetadata;
private final VisitorContext visitorContext;
private final Element originatingElement;
public EvaluatedExpressionWriter(ExpressionWithContext expressionMetadata,
VisitorContext visitorContext,
Element originatingElement) {
this.visitorContext = visitorContext;
this.expressionMetadata = expressionMetadata;
this.originatingElement = originatingElement;
}
@Override
public void accept(ClassWriterOutputVisitor outputVisitor) throws IOException {
String expressionClassName = expressionMetadata.expressionClassName();
if (WRITTEN_CLASSES.contains(expressionClassName)) {
return;
}
try (OutputStream outputStream = outputVisitor.visitClass(expressionClassName, originatingElement)) {
ClassDef objectDef = generateClassDef(expressionClassName);
outputStream.write(
BYTE_CODE_WRITER.write(
objectDef
)
);
WRITTEN_CLASSES.add(expressionClassName);
}
}
private ClassDef generateClassDef(String expressionClassName) {
return ClassDef.builder(expressionClassName)
.synthetic()
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addAnnotation(Generated.class)
.superclass(ClassTypeDef.of(AbstractEvaluatedExpression.class))
.addMethod(MethodDef.constructor()
.addModifiers(Modifier.PUBLIC)
.addParameter(Object.class)
.build((aThis, methodParameters) ->
aThis.superRef().invokeConstructor(methodParameters.get(0)))
)
.addMethod(MethodDef.override(DO_EVALUATE_METHOD)
.build((aThis, methodParameters) -> {
List<StatementDef> statements = new ArrayList<>();
ExpressionCompilationContext ctx = new ExpressionCompilationContext(
new ExpressionVisitorContext(expressionMetadata.evaluationContext(), visitorContext),
methodParameters.get(0),
statements
);
Object annotationValue = expressionMetadata.annotationValue();
try {
statements.add(
new CompoundEvaluatedExpressionParser(annotationValue)
.parse()
.compile(ctx)
.returning()
);
} catch (ExpressionParsingException | ExpressionCompilationException ex) {
throw failCompilation(ex, annotationValue);
}
return StatementDef.multi(statements);
}))
.build();
}
private ProcessingException failCompilation(Throwable ex, Object initialAnnotationValue) {
String strRepresentation = null;
if (initialAnnotationValue instanceof String str) {
strRepresentation = str;
} else if (initialAnnotationValue instanceof String[] strArray) {
strRepresentation = Arrays.toString(strArray);
}
String message = null;
if (ex instanceof ExpressionParsingException parsingException) {
message = "Failed to parse evaluated expression [" + strRepresentation + "]. " +
"Cause: " + parsingException.getMessage();
} else if (ex instanceof ExpressionCompilationException compilationException) {
message = "Failed to compile evaluated expression [" + strRepresentation + "]. " +
"Cause: " + compilationException.getMessage();
}
return new ProcessingException(originatingElement, message, ex);
}
}