Skip to content

Commit

Permalink
Merge pull request #87 from jamezp/LOGTOOL-101-2021
Browse files Browse the repository at this point in the history
[LOGTOOL-101] Create a @TransformException annotation which acts as a…
  • Loading branch information
jamezp authored Sep 13, 2021
2 parents 918f05d + cdcd44e commit 23de264
Show file tree
Hide file tree
Showing 8 changed files with 280 additions and 35 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* JBoss, Home of Professional Open Source.
*
* Copyright 2021 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* 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 org.jboss.logging.annotations;

import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.CLASS;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

/**
* Transforms the parameter into a new exception appending the {@linkplain Throwable#getLocalizedMessage() message} from
* the parameter to the {@linkplain Message message} from the method. This annotation is only allowed on parameters that
* are a super type of the return type.
* <p>
* Note that the {@linkplain Message message} must include a {@code %s} for the message from the parameter.
* </p>
*
* <p>
* <pre>{@code
* @Message("Binding to %s failed: %s")
* IOException bindFailed(SocketAddress address, @TransformException({BindException.class, SocketException.class}) IOException toCopy);
* }</pre>
*
* In the above example an exception is created based on the {@code toCopy} parameter. If the {@code toCopy} parameter
* is a {@link java.net.BindException} then a {@link java.net.BindException} is created and the stack trace from the
* {@code toCopy} parameter is copied to the newly created exception. This will happen for each type listed as a value,
* finally falling back to an {@link java.io.IOException} if the parameter is not an instance of the suggested types.
* </p>
* <p>
* The message for the newly created exception will be; &quot;Binding to {@code address.toString()} failed:
* {@code toCopy.getLocalizedMessage()}&quot;.
* </p>
*
* @author <a href="mailto:jperkins@redhat.com">James R. Perkins</a>
*/
@Retention(CLASS)
@Target(PARAMETER)
@Documented
public @interface TransformException {

/**
* Indicates if the stack trace from the parameter should be copied to the exception returned.
* <p>
* If {@code true}, the default, the parameters stack trace will be set as the stack trace on the newly created
* exception that is returned.
* </p>
*
* @return {@code true} if the stack trace should be copied to the newly created exception
*/
boolean copyStackTrace() default true;

/**
* An array of suggested types to create. Each type must be a super type of the parameter.
*
* @return the suggested types to create
*/
Class<? extends Throwable>[] value() default {};
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import org.jboss.logging.annotations.Pos;
import org.jboss.logging.annotations.Property;
import org.jboss.logging.annotations.Transform;
import org.jboss.logging.annotations.TransformException;
import org.jboss.logging.processor.model.MessageMethod;
import org.jboss.logging.processor.model.Parameter;
import org.jboss.logging.processor.util.Comparison;
Expand Down Expand Up @@ -119,7 +120,8 @@ private static class AptParameter extends AbstractClassType implements Parameter
isFormatArg = param.getAnnotationMirrors().isEmpty() ||
ElementHelper.isAnnotatedWith(param, FormatWith.class) ||
ElementHelper.isAnnotatedWith(param, Transform.class) ||
ElementHelper.isAnnotatedWith(param, Pos.class);
ElementHelper.isAnnotatedWith(param, Pos.class) ||
ElementHelper.isAnnotatedWith(param, TransformException.class);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@
import static org.jboss.logging.processor.util.Objects.areEqual;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
Expand All @@ -42,6 +44,7 @@

import org.jboss.logging.annotations.Param;
import org.jboss.logging.annotations.Signature;
import org.jboss.logging.annotations.TransformException;
import org.jboss.logging.processor.model.MessageMethod;
import org.jboss.logging.processor.model.Parameter;
import org.jboss.logging.processor.model.ThrowableType;
Expand Down Expand Up @@ -259,6 +262,7 @@ private static class AptReturnThrowableType extends AptThrowableType {
private final MessageMethod messageMethod;

private final Set<Parameter> constructionParameters;
private final Set<ThrowableType> suggestions;

private boolean useConstructionParameters = false;
private boolean causeSet = false;
Expand All @@ -274,6 +278,7 @@ private AptReturnThrowableType(final ProcessingEnvironment processingEnv, final
super(processingEnv, type, (messageMethod.hasCause() ? messageMethod.cause().asType() : null));
this.messageMethod = messageMethod;
constructionParameters = new LinkedHashSet<>();
suggestions = new LinkedHashSet<>();
}

@Override
Expand Down Expand Up @@ -317,6 +322,17 @@ protected void init() {
} else {
super.init();
}
final Optional<Parameter> parameter = messageMethod.parametersAnnotatedWith(TransformException.class)
.stream()
.findFirst();
if (parameter.isPresent()) {
final List<TypeMirror> suggestions = ElementHelper.getClassArrayAnnotationValue(parameter.get(), TransformException.class, "value");
for (TypeMirror suggestion : suggestions) {
final AptThrowableType result = new AptThrowableType(processingEnv, suggestion, null);
result.init();
this.suggestions.add(result);
}
}
}

@Override
Expand Down Expand Up @@ -382,5 +398,10 @@ public Set<Parameter> constructionParameters() {
public boolean causeSetInConstructor() {
return causeSet;
}

@Override
public Collection<ThrowableType> suggestions() {
return suggestions;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
import org.jboss.logging.annotations.Suppressed;
import org.jboss.logging.annotations.Transform;
import org.jboss.logging.annotations.Transform.TransformType;
import org.jboss.logging.annotations.TransformException;
import org.jboss.logging.processor.apt.ProcessingException;
import org.jboss.logging.processor.model.MessageInterface;
import org.jboss.logging.processor.model.MessageMethod;
Expand Down Expand Up @@ -216,6 +217,10 @@ void createBundleMethod(final JClassDef classDef, final JCall localeGetter, fina
}
added = true;
}
if (param.isAnnotatedWith(TransformException.class)) {
args.add($v(var).call("getLocalizedMessage"));
added = true;
}
if (!added) {
if (formatterCall == null) {
// This should never happen, but let's safe guard against it
Expand Down Expand Up @@ -381,47 +386,39 @@ private void addMethodTypeParameters(final JMethodDef method, final TypeMirror t
}

private JExpr createReturnType(final JClassDef classDef, final MessageMethod messageMethod, final JBlock body, final JCall format, final Map<String, JParamDeclaration> fields, final Map<String, JParamDeclaration> properties) {
boolean callInitCause = false;
final Set<Parameter> producers = messageMethod.parametersAnnotatedWith(Producer.class);
final JType type;
final JVarDeclaration resultField;
// There should only be one parameter annotated with TransformException
final Parameter teParameter = messageMethod.parametersAnnotatedWith(TransformException.class)
.stream()
.findFirst()
.orElse(null);

// If there are no producers we need to construct a new return type
if (producers.isEmpty()) {
final ThrowableType returnType = messageMethod.returnType().throwableReturnType();
type = JTypes.typeOf(returnType.asType());
// Import once more as the throwable return type may be different than the actual return type
// Import once more as the throwable return type may be different from the actual return type
sourceFile._import(type);
final JCall result = type._new();
resultField = body.var(FINAL, type, "result", result);
if (returnType.useConstructionParameters()) {
for (Parameter param : returnType.constructionParameters()) {
if (param.isMessageMethod()) {
result.arg(format);
final Collection<ThrowableType> suggestions = returnType.suggestions();
if (teParameter == null || suggestions.isEmpty()) {
resultField = constructReturnType(messageMethod, returnType, format, body, null);
} else {
resultField = body.var(FINAL, type, "result");
JIf ifBody = null;
for (ThrowableType suggested : suggestions) {
if (ifBody == null) {
ifBody = body._if($v(teParameter.name())._instanceof(JTypes.typeOf(suggested.asType())));
} else {
result.arg($v(param.name()));
ifBody = ifBody.elseIf($v(teParameter.name())._instanceof(JTypes.typeOf(suggested.asType())));
}
constructReturnType(messageMethod, suggested, format, ifBody, resultField);
}
callInitCause = messageMethod.hasCause() && !returnType.causeSetInConstructor();
} else if (returnType.hasStringAndThrowableConstructor() && messageMethod.hasCause()) {
result.arg(format).arg($v(messageMethod.cause().name()));
} else if (returnType.hasThrowableAndStringConstructor() && messageMethod.hasCause()) {
result.arg($v(messageMethod.cause().name())).arg(format);
} else if (returnType.hasStringConstructor()) {
result.arg(format);
if (messageMethod.hasCause()) {
callInitCause = true;
}
} else if (returnType.hasThrowableConstructor() && messageMethod.hasCause()) {
result.arg($v(messageMethod.cause().name()));
} else if (returnType.hasStringAndThrowableConstructor() && !messageMethod.hasCause()) {
result.arg(format).arg(NULL);
} else if (returnType.hasThrowableAndStringConstructor() && !messageMethod.hasCause()) {
result.arg(NULL).arg(format);
} else if (messageMethod.hasCause()) {
callInitCause = true;
constructReturnType(messageMethod, returnType, format, ifBody._else(), resultField);
}
} else {
boolean callInitCause = false;
final TypeMirror returnType = messageMethod.returnType().resolvedType();
// Should only be one producer
final Parameter producer = producers.iterator().next();
Expand Down Expand Up @@ -461,18 +458,24 @@ private JExpr createReturnType(final JClassDef classDef, final MessageMethod mes
callInitCause = messageMethod.hasCause();
}
resultField = body.var(FINAL, type, "result", result);
}
// Assign the result field the result value
if (callInitCause) {
body.add($v(resultField).call("initCause").arg($v(messageMethod.cause().name())));
// Assign the result field the result value
if (callInitCause) {
body.add($v(resultField).call("initCause").arg($v(messageMethod.cause().name())));
}
}

// Get the @Property or @Properties annotation values
addDefultProperties(messageMethod, ElementHelper.getAnnotations(messageMethod, Properties.class, Property.class), body, $v(resultField), false);
addDefultProperties(messageMethod, ElementHelper.getAnnotations(messageMethod, Fields.class, Field.class), body, $v(resultField), true);

// Remove this caller from the stack trace
body.add(getCopyStackMethod(classDef).arg($v(resultField)));
// Determine how the stack trace should be copied. If annotated with TransformException and copyStackTrace() is
// true, then we copy the parameters stack trace.
if (teParameter != null && teParameter.getAnnotation(TransformException.class).copyStackTrace()) {
body.add($v(resultField).call("setStackTrace").arg($v(teParameter.name()).call("getStackTrace")));
} else {
// Remove this caller from the stack trace
body.add(getCopyStackMethod(classDef).arg($v(resultField)));
}

// Add any suppressed messages
final Set<Parameter> suppressed = messageMethod.parametersAnnotatedWith(Suppressed.class);
Expand All @@ -497,6 +500,53 @@ private JExpr createReturnType(final JClassDef classDef, final MessageMethod mes
return resultExpr;
}

private JVarDeclaration constructReturnType(final MessageMethod messageMethod, final ThrowableType returnType, final JCall format,
final JBlock body, final JVarDeclaration resultField) {
final JType type = JTypes.typeOf(returnType.asType());
// Import once more as the throwable return type may be different from the actual return type
sourceFile._import(type);
boolean callInitCause = false;
final JCall result = type._new();
if (returnType.useConstructionParameters()) {
for (Parameter param : returnType.constructionParameters()) {
if (param.isMessageMethod()) {
result.arg(format);
} else {
result.arg($v(param.name()));
}
}
callInitCause = messageMethod.hasCause() && !returnType.causeSetInConstructor();
} else if (returnType.hasStringAndThrowableConstructor() && messageMethod.hasCause()) {
result.arg(format).arg($v(messageMethod.cause().name()));
} else if (returnType.hasThrowableAndStringConstructor() && messageMethod.hasCause()) {
result.arg($v(messageMethod.cause().name())).arg(format);
} else if (returnType.hasStringConstructor()) {
result.arg(format);
if (messageMethod.hasCause()) {
callInitCause = true;
}
} else if (returnType.hasThrowableConstructor() && messageMethod.hasCause()) {
result.arg($v(messageMethod.cause().name()));
} else if (returnType.hasStringAndThrowableConstructor() && !messageMethod.hasCause()) {
result.arg(format).arg(NULL);
} else if (returnType.hasThrowableAndStringConstructor() && !messageMethod.hasCause()) {
result.arg(NULL).arg(format);
} else if (messageMethod.hasCause()) {
callInitCause = true;
}
final JVarDeclaration returnField;
if (resultField == null) {
returnField = body.var(FINAL, type, "result", result);
} else {
returnField = resultField;
body.assign($v(returnField), result);
}
if (callInitCause) {
body.add($v(returnField).call("initCause").arg($v(messageMethod.cause().name())));
}
return returnField;
}

protected final void addThrownTypes(final MessageMethod messageMethod, final JMethodDef jMethod) {
for (ThrowableType thrownType : messageMethod.thrownTypes()) {
jMethod._throws(thrownType.name());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@

package org.jboss.logging.processor.model;

import java.util.Collection;
import java.util.Collections;
import java.util.Set;

/**
Expand Down Expand Up @@ -111,4 +113,13 @@ default boolean causeSetInConstructor() {
* @return the qualified class name fo the return type.
*/
String name();

/**
* The suggested types to create for the throwable type created.
*
* @return the suggested types to be created, or an empty set if this type itself should be created
*/
default Collection<ThrowableType> suggestions() {
return Collections.emptySet();
}
}
Loading

0 comments on commit 23de264

Please sign in to comment.