/* * Copyright (C) 2009-2021 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package lombok.eclipse.handlers; import static lombok.core.handlers.HandlerUtil.*; import static lombok.eclipse.handlers.EclipseHandlerUtil.*; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import lombok.ConfigurationKeys; import lombok.SneakyThrows; import lombok.core.AnnotationValues; import lombok.core.HandlerPriority; import lombok.eclipse.DeferUntilPostDiet; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; import lombok.spi.Provides; import org.eclipse.jdt.internal.compiler.ast.ASTNode; import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; import org.eclipse.jdt.internal.compiler.ast.Annotation; import org.eclipse.jdt.internal.compiler.ast.Argument; import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer; import org.eclipse.jdt.internal.compiler.ast.Block; import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration; import org.eclipse.jdt.internal.compiler.ast.ExplicitConstructorCall; import org.eclipse.jdt.internal.compiler.ast.Expression; import org.eclipse.jdt.internal.compiler.ast.MemberValuePair; import org.eclipse.jdt.internal.compiler.ast.MessageSend; import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference; import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference; import org.eclipse.jdt.internal.compiler.ast.Statement; import org.eclipse.jdt.internal.compiler.ast.ThrowStatement; import org.eclipse.jdt.internal.compiler.ast.TryStatement; import org.eclipse.jdt.internal.compiler.ast.TypeReference; /** * Handles the {@code lombok.HandleSneakyThrows} annotation for eclipse. */ @Provides @DeferUntilPostDiet @HandlerPriority(value = 1024) // 2^10; @NonNull must have run first, so that we wrap around the statements generated by it. public class HandleSneakyThrows extends EclipseAnnotationHandler { private static class DeclaredException { final String exceptionName; final ASTNode node; DeclaredException(String exceptionName, ASTNode node) { this.exceptionName = exceptionName; this.node = node; } } @Override public void handle(AnnotationValues annotation, Annotation source, EclipseNode annotationNode) { handleFlagUsage(annotationNode, ConfigurationKeys.SNEAKY_THROWS_FLAG_USAGE, "@SneakyThrows"); List exceptionNames = annotation.getRawExpressions("value"); List exceptions = new ArrayList(); MemberValuePair[] memberValuePairs = source.memberValuePairs(); if (memberValuePairs == null || memberValuePairs.length == 0) { exceptions.add(new DeclaredException("java.lang.Throwable", source)); } else { Expression arrayOrSingle = memberValuePairs[0].value; final Expression[] exceptionNameNodes; if (arrayOrSingle instanceof ArrayInitializer) { exceptionNameNodes = ((ArrayInitializer)arrayOrSingle).expressions; } else exceptionNameNodes = new Expression[] { arrayOrSingle }; if (exceptionNames.size() != exceptionNameNodes.length) { annotationNode.addError( "LOMBOK BUG: The number of exception classes in the annotation isn't the same pre- and post- guessing."); } int idx = 0; for (String exceptionName : exceptionNames) { if (exceptionName.endsWith(".class")) exceptionName = exceptionName.substring(0, exceptionName.length() - 6); exceptions.add(new DeclaredException(exceptionName, exceptionNameNodes[idx++])); } } EclipseNode owner = annotationNode.up(); switch (owner.getKind()) { // case FIELD: // return handleField(annotationNode, (FieldDeclaration)owner.get(), exceptions); case METHOD: handleMethod(annotationNode, (AbstractMethodDeclaration)owner.get(), exceptions); break; default: annotationNode.addError("@SneakyThrows is legal only on methods and constructors."); } } // private boolean handleField(Node annotation, FieldDeclaration field, List exceptions) { // if (field.initialization == null) { // annotation.addError("@SneakyThrows can only be used on fields with an initialization statement."); // return true; // } // // Expression expression = field.initialization; // Statement[] content = new Statement[] {new Assignment( // new SingleNameReference(field.name, 0), expression, 0)}; // field.initialization = null; // // for (DeclaredException exception : exceptions) { // content = new Statement[] { buildTryCatchBlock(content, exception) }; // } // // Block block = new Block(0); // block.statements = content; // // Node typeNode = annotation.up().up(); // // Initializer initializer = new Initializer(block, field.modifiers & Modifier.STATIC); // initializer.sourceStart = expression.sourceStart; // initializer.sourceEnd = expression.sourceEnd; // initializer.declarationSourceStart = expression.sourceStart; // initializer.declarationSourceEnd = expression.sourceEnd; // injectField(typeNode, initializer); // // typeNode.rebuild(); // // return true; // } public void handleMethod(EclipseNode annotation, AbstractMethodDeclaration method, List exceptions) { if (method.isAbstract()) { annotation.addError("@SneakyThrows can only be used on concrete methods."); return; } if (method.statements == null || method.statements.length == 0) { boolean hasConstructorCall = false; if (method instanceof ConstructorDeclaration) { ExplicitConstructorCall constructorCall = ((ConstructorDeclaration) method).constructorCall; hasConstructorCall = constructorCall != null && !constructorCall.isImplicitSuper() && !constructorCall.isImplicitThis(); } if (hasConstructorCall) { annotation.addWarning("Calls to sibling / super constructors are always excluded from @SneakyThrows; @SneakyThrows has been ignored because there is no other code in this constructor."); } else { annotation.addWarning("This method or constructor is empty; @SneakyThrows has been ignored."); } return; } Statement[] contents = method.statements; for (DeclaredException exception : exceptions) { contents = new Statement[] { buildTryCatchBlock(contents, exception, exception.node, method) }; } method.statements = contents; annotation.up().rebuild(); } public Statement buildTryCatchBlock(Statement[] contents, DeclaredException exception, ASTNode source, AbstractMethodDeclaration method) { int methodStart = method.bodyStart; int methodEnd = method.bodyEnd; long methodPosEnd = ((long) methodEnd) << 32 | (methodEnd & 0xFFFFFFFFL); TryStatement tryStatement = new TryStatement(); setGeneratedBy(tryStatement, source); tryStatement.tryBlock = new Block(0); // Positions for in-method generated nodes are special tryStatement.tryBlock.sourceStart = methodStart; tryStatement.tryBlock.sourceEnd = methodEnd; setGeneratedBy(tryStatement.tryBlock, source); tryStatement.tryBlock.statements = contents; TypeReference typeReference; if (exception.exceptionName.indexOf('.') == -1) { typeReference = new SingleTypeReference(exception.exceptionName.toCharArray(), methodPosEnd); typeReference.statementEnd = methodEnd; } else { String[] x = exception.exceptionName.split("\\."); char[][] elems = new char[x.length][]; long[] poss = new long[x.length]; Arrays.fill(poss, methodPosEnd); for (int i = 0; i < x.length; i++) { elems[i] = x[i].trim().toCharArray(); } typeReference = new QualifiedTypeReference(elems, poss); } setGeneratedBy(typeReference, source); Argument catchArg = new Argument("$ex".toCharArray(), methodPosEnd, typeReference, Modifier.FINAL); setGeneratedBy(catchArg, source); catchArg.declarationSourceEnd = catchArg.declarationEnd = catchArg.sourceEnd = methodEnd; catchArg.declarationSourceStart = catchArg.modifiersSourceStart = catchArg.sourceStart = methodEnd; tryStatement.catchArguments = new Argument[] { catchArg }; MessageSend sneakyThrowStatement = new MessageSend(); setGeneratedBy(sneakyThrowStatement, source); sneakyThrowStatement.receiver = new QualifiedNameReference(new char[][] { "lombok".toCharArray(), "Lombok".toCharArray() }, new long[2], methodEnd, methodEnd); setGeneratedBy(sneakyThrowStatement.receiver, source); sneakyThrowStatement.receiver.statementEnd = methodEnd; sneakyThrowStatement.selector = "sneakyThrow".toCharArray(); SingleNameReference exRef = new SingleNameReference("$ex".toCharArray(), methodPosEnd); setGeneratedBy(exRef, source); exRef.statementEnd = methodEnd; sneakyThrowStatement.arguments = new Expression[] { exRef }; // This is the magic fix for rendering issues // In org.eclipse.jdt.core.dom.ASTConverter#convert(org.eclipse.jdt.internal.compiler.ast.MessageSend) // a new SimpleName is created and the setSourceRange should receive -1, 0. That's why we provide -2L :-) sneakyThrowStatement.nameSourcePosition = -2L; sneakyThrowStatement.sourceStart = methodEnd; sneakyThrowStatement.sourceEnd = sneakyThrowStatement.statementEnd = methodEnd; Statement rethrowStatement = new ThrowStatement(sneakyThrowStatement, methodEnd, methodEnd); setGeneratedBy(rethrowStatement, source); Block block = new Block(0); block.sourceStart = methodEnd; block.sourceEnd = methodEnd; setGeneratedBy(block, source); block.statements = new Statement[] { rethrowStatement }; tryStatement.catchBlocks = new Block[] { block }; // Positions for in-method generated nodes are special tryStatement.sourceStart = method.bodyStart; tryStatement.sourceEnd = method.bodyEnd; return tryStatement; } }