Add Eclipse implementation for Locked annotations

This commit is contained in:
Pim van der Loos committed 2023-04-18 18:07:53 +02:00
1 parent 21375449db
commit 5e31bb253c
4 files changed
+437

No files matched your search

@@ -0,0 +1,55 @@
/*
* 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 lombok.core.AnnotationValues;
import lombok.core.HandlerPriority;
import lombok.eclipse.DeferUntilPostDiet;
import lombok.eclipse.EclipseAnnotationHandler;
import lombok.eclipse.EclipseNode;
import lombok.experimental.Locked;
import lombok.spi.Provides;
import org.eclipse.jdt.internal.compiler.ast.Annotation;
import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
/**
* Handles the {@code lombok.Locked} 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 HandleLocked extends EclipseAnnotationHandler<Locked> {
private static final String ANNOTATION_NAME = "@Locked";
private static final char[][] LOCK_CLASS = new char[][] { TypeConstants.JAVA, TypeConstants.UTIL,
"concurrent".toCharArray(), "locks".toCharArray(),
"ReentrantLock".toCharArray() };
@Override public void handle(AnnotationValues<Locked> annotation, Annotation source, EclipseNode annotationNode) {
String annotationValue = annotation.getInstance().value();
HandleLockedUtil.handle(annotationValue, source, annotationNode, ANNOTATION_NAME, LOCK_CLASS);
}
@Override public void preHandle(AnnotationValues<Locked> annotation, Annotation source, EclipseNode annotationNode) {
String annotationValue = annotation.getInstance().value();
HandleLockedUtil.preHandle(annotationValue, LOCK_CLASS, annotationNode);
}
}
@@ -0,0 +1,56 @@
/*
* 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 lombok.core.AnnotationValues;
import lombok.core.HandlerPriority;
import lombok.eclipse.DeferUntilPostDiet;
import lombok.eclipse.EclipseAnnotationHandler;
import lombok.eclipse.EclipseNode;
import lombok.experimental.Locked;
import lombok.spi.Provides;
import org.eclipse.jdt.internal.compiler.ast.Annotation;
import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
/**
* Handles the {@code lombok.Locked.Read} 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 HandleLockedRead extends EclipseAnnotationHandler<Locked.Read> {
private static final char[] LOCK_METHOD = "readLock".toCharArray();
private static final String ANNOTATION_NAME = "@Locked.Read";
private static final char[][] LOCK_CLASS = new char[][] { TypeConstants.JAVA, TypeConstants.UTIL,
"concurrent".toCharArray(), "locks".toCharArray(),
"ReentrantReadWriteLock".toCharArray() };
@Override public void handle(AnnotationValues<Locked.Read> annotation, Annotation source, EclipseNode annotationNode) {
String annotationValue = annotation.getInstance().value();
HandleLockedUtil.handle(annotationValue, source, annotationNode, ANNOTATION_NAME, LOCK_CLASS, LOCK_METHOD);
}
@Override public void preHandle(AnnotationValues<Locked.Read> annotation, Annotation source, EclipseNode annotationNode) {
String annotationValue = annotation.getInstance().value();
HandleLockedUtil.preHandle(annotationValue, LOCK_CLASS, annotationNode);
}
}
@@ -0,0 +1,270 @@
/* 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 lombok.ConfigurationKeys;
import lombok.core.AST;
import lombok.eclipse.EclipseNode;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.AllocationExpression;
import org.eclipse.jdt.internal.compiler.ast.Annotation;
import org.eclipse.jdt.internal.compiler.ast.Block;
import org.eclipse.jdt.internal.compiler.ast.Expression;
import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
import org.eclipse.jdt.internal.compiler.ast.FieldReference;
import org.eclipse.jdt.internal.compiler.ast.MessageSend;
import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference;
import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference;
import org.eclipse.jdt.internal.compiler.ast.Reference;
import org.eclipse.jdt.internal.compiler.ast.Statement;
import org.eclipse.jdt.internal.compiler.ast.ThisReference;
import org.eclipse.jdt.internal.compiler.ast.TryStatement;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.ast.TypeReference;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import static lombok.core.handlers.HandlerUtil.handleFlagUsage;
import static lombok.eclipse.handlers.EclipseHandlerUtil.upToTypeNode;
import static lombok.eclipse.handlers.EclipseHandlerUtil.*;
/**
* Container for static utility methods used by the Locked[.Read/Write] annotations for eclipse.
*/
public final class HandleLockedUtil {
private static final char[] INSTANCE_LOCK_NAME = "lock".toCharArray();
private static final char[] STATIC_LOCK_NAME = "LOCK".toCharArray();
private static final char[] LOCK_METHOD = "lock".toCharArray();
private static final char[] UNLOCK_METHOD = "unlock".toCharArray();
private HandleLockedUtil() {
//Prevent instantiation
}
private static boolean typeReferenceNameEquals(TypeReference a, TypeReference b) {
char[][] typeNameA = a.getTypeName();
char[][] typeNameB = b.getTypeName();
if (typeNameA.length != typeNameB.length) return false;
for (int idx = 0; idx < typeNameA.length; ++idx) {
if (!Arrays.equals(typeNameA[idx], typeNameB[idx])) return false;
}
return true;
}
public static void preHandle(String annotationValue, char[][] lockClass, EclipseNode annotationNode) {
EclipseNode methodNode = annotationNode.up();
if (methodNode == null || methodNode.getKind() != AST.Kind.METHOD || !(methodNode.get() instanceof MethodDeclaration)) return;
MethodDeclaration method = (MethodDeclaration) methodNode.get();
if (method.isAbstract()) return;
createLockField(annotationValue, annotationNode, lockClass, new boolean[] { method.isStatic() }, false);
}
private static char[] createLockField(String name, EclipseNode annotationNode, char[][] lockClass,
boolean[] isStatic, boolean reportErrors) {
char[] lockName = name.toCharArray();
Annotation source = (Annotation) annotationNode.get();
if (lockName.length == 0) {
lockName = isStatic[0] ? STATIC_LOCK_NAME : INSTANCE_LOCK_NAME;
}
EclipseNode typeNode = upToTypeNode(annotationNode);
MemberExistsResult exists = MemberExistsResult.NOT_EXISTS;
QualifiedTypeReference lockType = new QualifiedTypeReference(lockClass, new long[] { 0, 0, 0, 0, 0 });
if (typeNode != null && typeNode.get() instanceof TypeDeclaration) {
TypeDeclaration typeDecl = (TypeDeclaration) typeNode.get();
if (typeDecl.fields != null) for (FieldDeclaration def : typeDecl.fields) {
char[] fName = def.name;
if (fName == null) continue;
if (Arrays.equals(fName, lockName)) {
exists = getGeneratedBy(def) == null ? MemberExistsResult.EXISTS_BY_USER : MemberExistsResult.EXISTS_BY_LOMBOK;
boolean st = def.isStatic();
if (st != isStatic[0] && exists == MemberExistsResult.EXISTS_BY_LOMBOK) {
if (reportErrors) annotationNode.addError(String.format("The generated field %s does not match the static status of this method", new String(lockName)));
return null;
}
isStatic[0] = st;
if (exists == MemberExistsResult.EXISTS_BY_LOMBOK && !typeReferenceNameEquals(lockType, def.type)) {
annotationNode.addError("Expected field " + charArrToString(lockName) + " to be of type " +
lockType + " but got type " + def.type + "!" +
" Did you mix @Locked with @Locked.Read/Write on the same generated field?");
return null;
}
break;
}
}
}
if (exists == MemberExistsResult.NOT_EXISTS) {
FieldDeclaration fieldDecl = setGeneratedBy(new FieldDeclaration(lockName, 0, -1), source);
fieldDecl.declarationSourceEnd = -1;
fieldDecl.modifiers = (isStatic[0] ? Modifier.STATIC : 0) | Modifier.FINAL | Modifier.PRIVATE;
AllocationExpression lockAlloc = setGeneratedBy(new AllocationExpression(), source);
lockAlloc.type = setGeneratedBy(new QualifiedTypeReference(lockClass, new long[] { 0, 0, 0, 0, 0 }), source);
fieldDecl.type = setGeneratedBy(new QualifiedTypeReference(lockClass, new long[] { 0, 0, 0, 0, 0 }), source);
fieldDecl.initialization = lockAlloc;
injectField(annotationNode.up().up(), fieldDecl);
}
return lockName;
}
/**
* See {@link #handle(String, Annotation, EclipseNode, String, char[][], char[])} for
* {@code lockableMethodName = null}.
*/
public static void handle(String annotationValue, Annotation ast, EclipseNode annotationNode,
String annotationName, char[][] lockClass) {
handle(annotationValue, ast, annotationNode, annotationName, lockClass, null);
}
/**
* Called when an annotation is found that is likely to match the annotation you're interested in.
*
* Be aware that you'll be called for ANY annotation node in the source that looks like a match. There is,
* for example, no guarantee that the annotation node belongs to a method, even if you set your
* TargetType in the annotation to methods only.
*
* @param annotationValue The value of the annotation. This will be the name of the object used for locking.
* @param source The Eclipse AST node representing the annotation.
* @param annotationNode The Lombok AST wrapper around the 'ast' parameter. You can use this object
* to travel back up the chain (something javac AST can't do) to the parent of the annotation, as well
* as access useful methods such as generating warnings or errors focused on the annotation.
* @param annotationName The name of the annotation to use when referencing it in errors.
* @param lockClass The fully qualified name of the type of lock to use (e.g. Reentrant[Read/Write]Lock).
* @param lockableMethodName The name of the method in the {@code lockClass} that returns a
* {@link java.util.concurrent.locks.Lock} object. When this is {@code null}, it is assumed that {@code lockClass}
* itself can be locked/unlocked.
*/
public static void handle(String annotationValue, Annotation source, EclipseNode annotationNode,
String annotationName, char[][] lockClass, char[] lockableMethodName) {
handleFlagUsage(annotationNode, ConfigurationKeys.LOCKED_FLAG_USAGE, annotationName);
int p1 = source.sourceStart -1;
int p2 = source.sourceStart -2;
long pos = (((long) p1) << 32) | p2;
EclipseNode methodNode = annotationNode.up();
if (methodNode == null || methodNode.getKind() != AST.Kind.METHOD ||
!(methodNode.get() instanceof MethodDeclaration)) {
annotationNode.addError(annotationName + " is legal only on methods.");
return;
}
MethodDeclaration method = (MethodDeclaration) methodNode.get();
if (method.isAbstract()) {
annotationNode.addError(annotationName + " is legal only on concrete methods.");
return;
}
EclipseNode typeNode = upToTypeNode(annotationNode);
if (!isClassOrEnum(typeNode)) {
annotationNode.addError(annotationName + " is legal only on methods in classes and enums.");
return;
}
boolean[] isStatic = { method.isStatic() };
char[] lockName = createLockField(annotationValue, annotationNode, lockClass, isStatic, true);
if (lockName == null) return;
if (method.statements == null) return;
Block block = new Block(0);
block.statements = method.statements;
setGeneratedBy(block, source);
// Positions for in-method generated nodes are special
block.sourceEnd = method.bodyEnd;
block.sourceStart = method.bodyStart;
Statement acquireLock = getLockingStatement(source, typeNode, LOCK_METHOD, lockName,
lockableMethodName, isStatic[0], p1, p2, pos);
Statement unLock = getLockingStatement(source, typeNode, UNLOCK_METHOD, lockName,
lockableMethodName, isStatic[0], p1, p2, pos);
TryStatement tryStatement = new TryStatement();
tryStatement.tryBlock = block;
tryStatement.finallyBlock = new Block(0);
tryStatement.finallyBlock.statements = new Statement[] { unLock };
method.statements = new Statement[] { acquireLock, tryStatement };
// Positions for in-method generated nodes are special
method.statements[0].sourceEnd = method.bodyEnd;
method.statements[0].sourceStart = method.bodyStart;
methodNode.rebuild();
}
private static Statement getLockingStatement(ASTNode source, EclipseNode typeNode, char[] lockMethod,
char[] lockableObjectName, char[] lockableMethodName, boolean isStatic,
int p1, int p2, long pos) {
MessageSend lockStat = setGeneratedBy(new MessageSend(), source);
lockStat.receiver = getLockable(source, typeNode, lockableObjectName, lockableMethodName, isStatic, p1, p2, pos);
lockStat.selector = lockMethod;
lockStat.nameSourcePosition = pos;
lockStat.sourceStart = p1;
lockStat.sourceEnd = lockStat.statementEnd = p2;
return lockStat;
}
private static Expression getLockable(ASTNode source, EclipseNode typeNode, char[] lockName,
char[] lockableMethodName, boolean isStatic, int p1, int p2, long pos) {
Reference lockVariable;
if (isStatic) {
char[][] n = getQualifiedInnerName(typeNode, lockName);
long[] ps = new long[n.length];
Arrays.fill(ps, pos);
lockVariable = new QualifiedNameReference(n, ps, p1, p2);
} else {
lockVariable = new FieldReference(lockName, pos);
ThisReference thisReference = new ThisReference(p1, p2);
setGeneratedBy(thisReference, source);
((FieldReference) lockVariable).receiver = thisReference;
}
setGeneratedBy(lockVariable, source);
Expression lockable;
if (lockableMethodName == null) lockable = lockVariable;
else {
lockable = new MessageSend();
((MessageSend) lockable).receiver = lockVariable;
((MessageSend) lockable).selector = lockableMethodName;
((MessageSend) lockable).nameSourcePosition = pos;
lockable.sourceStart = p1;
lockable.sourceEnd = lockable.statementEnd = p2;
}
return setGeneratedBy(lockable, source);
}
private static String charArrToString(char[] arr) {
String str = "";
for (int idx = 0; idx < arr.length; ++idx) str += arr[idx];
return str;
}
}
@@ -0,0 +1,56 @@
/*
* 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 lombok.core.AnnotationValues;
import lombok.core.HandlerPriority;
import lombok.eclipse.DeferUntilPostDiet;
import lombok.eclipse.EclipseAnnotationHandler;
import lombok.eclipse.EclipseNode;
import lombok.experimental.Locked;
import lombok.spi.Provides;
import org.eclipse.jdt.internal.compiler.ast.Annotation;
import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
/**
* Handles the {@code lombok.Locked.Write} 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 HandleLockedWrite extends EclipseAnnotationHandler<Locked.Write> {
private static final char[] LOCK_METHOD = "writeLock".toCharArray();
private static final String ANNOTATION_NAME = "@Locked.Write";
private static final char[][] LOCK_CLASS = new char[][] { TypeConstants.JAVA, TypeConstants.UTIL,
"concurrent".toCharArray(), "locks".toCharArray(),
"ReentrantReadWriteLock".toCharArray() };
@Override public void handle(AnnotationValues<Locked.Write> annotation, Annotation source, EclipseNode annotationNode) {
String annotationValue = annotation.getInstance().value();
HandleLockedUtil.handle(annotationValue, source, annotationNode, ANNOTATION_NAME, LOCK_CLASS, LOCK_METHOD);
}
@Override public void preHandle(AnnotationValues<Locked.Write> annotation, Annotation source, EclipseNode annotationNode) {
String annotationValue = annotation.getInstance().value();
HandleLockedUtil.preHandle(annotationValue, LOCK_CLASS, annotationNode);
}
}