diff --git a/AUTHORS b/AUTHORS index 50f9e10e..bbbebb7c 100755 --- a/AUTHORS +++ b/AUTHORS @@ -37,6 +37,7 @@ Pascal Bihler Peter Grant Philipp Eichhorn Philippe Charles +Pim van der Loos Rabea Gransberger Raul Wißfeld Reinier Zwitserloot diff --git a/src/core/lombok/ConfigurationKeys.java b/src/core/lombok/ConfigurationKeys.java index e94dc65f..27fe78e0 100644 --- a/src/core/lombok/ConfigurationKeys.java +++ b/src/core/lombok/ConfigurationKeys.java @@ -632,8 +632,17 @@ public class ConfigurationKeys { */ public static final ConfigurationKey HELPER_FLAG_USAGE = new ConfigurationKey("lombok.helper.flagUsage", "Emit a warning or error if @Helper is used.") {}; + // ----- LOCKED ----- + + /** + * lombok configuration: {@code lombok.locked.flagUsage} = {@code WARNING} | {@code ERROR}. + * + * If set, any usage of {@code @Locked} results in a warning / error. + */ + public static final ConfigurationKey LOCKED_FLAG_USAGE = new ConfigurationKey("lombok.locked.flagUsage", "Emit a warning or error if @Locked is used.") {}; + // ----- onX ----- - + /** * lombok configuration: {@code lombok.onX.flagUsage} = {@code WARNING} | {@code ERROR}. * diff --git a/src/core/lombok/Locked.java b/src/core/lombok/Locked.java new file mode 100644 index 00000000..37adf553 --- /dev/null +++ b/src/core/lombok/Locked.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2021-2023 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; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Guards all statements in an annotation method with a {@link java.util.concurrent.locks.Lock}. + *

+ * For non-static methods, a field named {@code $lock} is used, and for static methods, + * {@code $LOCK} is used. These will be generated if needed and if they aren't already present. + *

+ * Because {@link Locked} uses a different type of lock from {@link Locked.Read} and {@link Locked.Write}, using both in + * the same class using the default names will result in a compile time error. + *

+ * Complete documentation is found at the project lombok features page for @Locked. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.SOURCE) +public @interface Locked { + /** + * Locks using a {@link java.util.concurrent.locks.ReadWriteLock#readLock()}. + */ + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.SOURCE) + public @interface Read { + /** + * Optional: specify the name of a different field to lock on. It is a compile time error if this field + * doesn't already exist (the fields are automatically generated only if you don't specify a specific name). + * + * @return Name of the field to lock on (blank = generate one). + */ + String value() default ""; + } + + /** + * Locks using a {@link java.util.concurrent.locks.ReadWriteLock#writeLock()}. + */ + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.SOURCE) + public @interface Write { + /** + * Optional: specify the name of a different field to lock on. It is a compile time error if this field + * doesn't already exist (the fields are automatically generated only if you don't specify a specific name). + * + * @return Name of the field to lock on (blank = generate one). + */ + String value() default ""; + } + + /** + * Optional: specify the name of a different field to lock on. It is a compile time error if this field + * doesn't already exist (the fields are automatically generated only if you don't specify a specific name). + * + * @return Name of the field to lock on (blank = generate one). + */ + String value() default ""; +} diff --git a/src/core/lombok/Synchronized.java b/src/core/lombok/Synchronized.java index 5dff0fb2..1089bea8 100644 --- a/src/core/lombok/Synchronized.java +++ b/src/core/lombok/Synchronized.java @@ -42,7 +42,7 @@ import java.lang.annotation.Target; public @interface Synchronized { /** * Optional: specify the name of a different field to lock on. It is a compile time error if this field - * doesn't already exist (the fields are automatically generated only if you don't specify a specific name. + * doesn't already exist (the fields are automatically generated only if you don't specify a specific name). * * @return Name of the field to lock on (blank = generate one). */ diff --git a/src/core/lombok/eclipse/handlers/HandleLocked.java b/src/core/lombok/eclipse/handlers/HandleLocked.java new file mode 100644 index 00000000..240a6498 --- /dev/null +++ b/src/core/lombok/eclipse/handlers/HandleLocked.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2021-2023 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.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 { + private static final String ANNOTATION_NAME = "@Locked"; + private static final char[][] LOCK_TYPE_CLASS = new char[][] { TypeConstants.JAVA, TypeConstants.UTIL, + "concurrent".toCharArray(), "locks".toCharArray(), "Lock".toCharArray() }; + private static final char[][] LOCK_IMPL_CLASS = new char[][] { TypeConstants.JAVA, TypeConstants.UTIL, + "concurrent".toCharArray(), "locks".toCharArray(), "ReentrantLock".toCharArray() }; + + @Override public void handle(AnnotationValues annotation, Annotation source, EclipseNode annotationNode) { + String annotationValue = annotation.getInstance().value(); + HandleLockedUtil.handle(annotationValue, source, annotationNode, ANNOTATION_NAME, LOCK_TYPE_CLASS, LOCK_IMPL_CLASS); + } + + @Override public void preHandle(AnnotationValues annotation, Annotation source, EclipseNode annotationNode) { + String annotationValue = annotation.getInstance().value(); + HandleLockedUtil.preHandle(annotationValue, LOCK_TYPE_CLASS, LOCK_IMPL_CLASS, annotationNode); + } +} diff --git a/src/core/lombok/eclipse/handlers/HandleLockedRead.java b/src/core/lombok/eclipse/handlers/HandleLockedRead.java new file mode 100644 index 00000000..a293467a --- /dev/null +++ b/src/core/lombok/eclipse/handlers/HandleLockedRead.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2021-2023 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.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 { + private static final char[] LOCK_METHOD = "readLock".toCharArray(); + private static final String ANNOTATION_NAME = "@Locked.Read"; + private static final char[][] LOCK_TYPE_CLASS = new char[][] { TypeConstants.JAVA, TypeConstants.UTIL, + "concurrent".toCharArray(), "locks".toCharArray(), "ReadWriteLock".toCharArray() }; + private static final char[][] LOCK_IMPL_CLASS = new char[][] { TypeConstants.JAVA, TypeConstants.UTIL, + "concurrent".toCharArray(), "locks".toCharArray(), "ReentrantReadWriteLock".toCharArray() }; + + @Override public void handle(AnnotationValues annotation, Annotation source, EclipseNode annotationNode) { + String annotationValue = annotation.getInstance().value(); + HandleLockedUtil.handle(annotationValue, source, annotationNode, ANNOTATION_NAME, LOCK_TYPE_CLASS, LOCK_IMPL_CLASS, LOCK_METHOD); + } + + @Override public void preHandle(AnnotationValues annotation, Annotation source, EclipseNode annotationNode) { + String annotationValue = annotation.getInstance().value(); + HandleLockedUtil.preHandle(annotationValue, LOCK_TYPE_CLASS, LOCK_IMPL_CLASS, annotationNode); + } +} diff --git a/src/core/lombok/eclipse/handlers/HandleLockedUtil.java b/src/core/lombok/eclipse/handlers/HandleLockedUtil.java new file mode 100644 index 00000000..64fe3591 --- /dev/null +++ b/src/core/lombok/eclipse/handlers/HandleLockedUtil.java @@ -0,0 +1,251 @@ +/* Copyright (C) 2021-2023 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 java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicBoolean; + +import static lombok.core.handlers.HandlerUtil.*; +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 + } + + public static void preHandle(String annotationValue, char[][] lockTypeClass, char[][] lockImplClass, 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, lockTypeClass, lockImplClass, new AtomicBoolean(method.isStatic()), false); + } + + private static char[] createLockField(String name, EclipseNode annotationNode, char[][] lockTypeClass, char[][] lockImplClass, AtomicBoolean isStatic, boolean reportErrors) { + char[] lockName = name.toCharArray(); + + Annotation source = (Annotation) annotationNode.get(); + if (lockName.length == 0) { + lockName = isStatic.get() ? STATIC_LOCK_NAME : INSTANCE_LOCK_NAME; + } + + EclipseNode typeNode = upToTypeNode(annotationNode); + MemberExistsResult exists = MemberExistsResult.NOT_EXISTS; + + QualifiedTypeReference lockType = new QualifiedTypeReference(lockTypeClass, 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.get() && 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.set(st); + + if (exists == MemberExistsResult.EXISTS_BY_LOMBOK && !Arrays.deepEquals(lockType.getTypeName(), def.type.getTypeName())) { + annotationNode.addError("Expected field " + new String(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.get() ? Modifier.STATIC : 0) | Modifier.FINAL | Modifier.PRIVATE; + + AllocationExpression lockAlloc = setGeneratedBy(new AllocationExpression(), source); + lockAlloc.type = setGeneratedBy(new QualifiedTypeReference(lockImplClass, new long[] { 0, 0, 0, 0, 0 }), source); + fieldDecl.type = setGeneratedBy(new QualifiedTypeReference(lockTypeClass, 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[][] lockTypeClass, char[][] lockImplClass) { + handle(annotationValue, ast, annotationNode, annotationName, lockTypeClass, lockImplClass, 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 lockTypeClass The fully qualified type of the variable when generating a lock to use. + * @param lockImplClass Call the constructor of this fully qualified classname to generate a lock to use. + * @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[][] lockTypeClass, char[][] lockImplClass, 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; + } + + AtomicBoolean isStatic = new AtomicBoolean(method.isStatic()); + char[] lockName = createLockField(annotationValue, annotationNode, lockTypeClass, lockImplClass, 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.get(), p1, p2, pos); + Statement unLock = getLockingStatement(source, typeNode, UNLOCK_METHOD, lockName, lockableMethodName, isStatic.get(), 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); + } +} diff --git a/src/core/lombok/eclipse/handlers/HandleLockedWrite.java b/src/core/lombok/eclipse/handlers/HandleLockedWrite.java new file mode 100644 index 00000000..6192c76b --- /dev/null +++ b/src/core/lombok/eclipse/handlers/HandleLockedWrite.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2021-2023 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.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 { + private static final char[] LOCK_METHOD = "writeLock".toCharArray(); + private static final String ANNOTATION_NAME = "@Locked.Write"; + private static final char[][] LOCK_TYPE_CLASS = new char[][] { TypeConstants.JAVA, TypeConstants.UTIL, + "concurrent".toCharArray(), "locks".toCharArray(), "ReadWriteLock".toCharArray() }; + private static final char[][] LOCK_IMPL_CLASS = new char[][] { TypeConstants.JAVA, TypeConstants.UTIL, + "concurrent".toCharArray(), "locks".toCharArray(), "ReentrantReadWriteLock".toCharArray() }; + + @Override public void handle(AnnotationValues annotation, Annotation source, EclipseNode annotationNode) { + String annotationValue = annotation.getInstance().value(); + HandleLockedUtil.handle(annotationValue, source, annotationNode, ANNOTATION_NAME, LOCK_TYPE_CLASS, LOCK_IMPL_CLASS, LOCK_METHOD); + } + + @Override public void preHandle(AnnotationValues annotation, Annotation source, EclipseNode annotationNode) { + String annotationValue = annotation.getInstance().value(); + HandleLockedUtil.preHandle(annotationValue, LOCK_TYPE_CLASS, LOCK_IMPL_CLASS, annotationNode); + } +} diff --git a/src/core/lombok/javac/handlers/HandleLocked.java b/src/core/lombok/javac/handlers/HandleLocked.java new file mode 100644 index 00000000..538ec29b --- /dev/null +++ b/src/core/lombok/javac/handlers/HandleLocked.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2021-2023 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.javac.handlers; + +import lombok.core.AnnotationValues; +import lombok.core.HandlerPriority; +import lombok.Locked; +import lombok.javac.JavacAnnotationHandler; +import lombok.javac.JavacNode; +import lombok.spi.Provides; + +import static com.sun.tools.javac.tree.JCTree.*; + +/** + * Handles the {@code lombok.Locked} annotation for javac. + */ +@Provides +@HandlerPriority(value = 1024) // 2^10; @NonNull must have run first, so that we wrap around the statements generated by it. +public class HandleLocked extends JavacAnnotationHandler { + private static final String ANNOTATION_NAME = "@Locked"; + private static final String[] LOCK_TYPE_CLASS = new String[]{"java", "util", "concurrent", "locks", "Lock"}; + private static final String[] LOCK_IMPL_CLASS = new String[]{"java", "util", "concurrent", "locks", "ReentrantLock"}; + + @Override public void handle(AnnotationValues annotation, JCAnnotation ast, JavacNode annotationNode) { + String annotationValue = annotation.getInstance().value(); + HandleLockedUtil.handle(annotationValue, ast, annotationNode, Locked.class, ANNOTATION_NAME, LOCK_TYPE_CLASS, LOCK_IMPL_CLASS); + } +} diff --git a/src/core/lombok/javac/handlers/HandleLockedRead.java b/src/core/lombok/javac/handlers/HandleLockedRead.java new file mode 100644 index 00000000..be3a14a6 --- /dev/null +++ b/src/core/lombok/javac/handlers/HandleLockedRead.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2021-2023 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.javac.handlers; + +import lombok.core.AnnotationValues; +import lombok.core.HandlerPriority; +import lombok.Locked; +import lombok.javac.JavacAnnotationHandler; +import lombok.javac.JavacNode; +import lombok.spi.Provides; + +import static com.sun.tools.javac.tree.JCTree.*; + +/** + * Handles the {@code lombok.Locked.Read} annotation for javac. + */ +@Provides +@HandlerPriority(value = 1024) // 2^10; @NonNull must have run first, so that we wrap around the statements generated by it. +public class HandleLockedRead extends JavacAnnotationHandler { + private static final String LOCK_METHOD = "readLock"; + private static final String ANNOTATION_NAME = "@Locked.Read"; + private static final String[] LOCK_TYPE_CLASS = new String[]{"java", "util", "concurrent", "locks", "ReadWriteLock"}; + private static final String[] LOCK_IMPL_CLASS = new String[]{"java", "util", "concurrent", "locks", "ReentrantReadWriteLock"}; + + @Override public void handle(AnnotationValues annotation, JCAnnotation ast, JavacNode annotationNode) { + String annotationValue = annotation.getInstance().value(); + HandleLockedUtil.handle(annotationValue, ast, annotationNode, Locked.Read.class, ANNOTATION_NAME, LOCK_TYPE_CLASS, LOCK_IMPL_CLASS, LOCK_METHOD); + } +} diff --git a/src/core/lombok/javac/handlers/HandleLockedUtil.java b/src/core/lombok/javac/handlers/HandleLockedUtil.java new file mode 100644 index 00000000..70dc9073 --- /dev/null +++ b/src/core/lombok/javac/handlers/HandleLockedUtil.java @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2021-2023 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.javac.handlers; + +import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.util.List; +import lombok.core.AST; +import lombok.ConfigurationKeys; +import lombok.Locked; +import lombok.javac.JavacNode; +import lombok.javac.JavacTreeMaker; + +import java.lang.annotation.Annotation; + +import static com.sun.tools.javac.tree.JCTree.*; +import static lombok.core.handlers.HandlerUtil.*; +import static lombok.javac.handlers.JavacHandlerUtil.*; + +/** + * Container for static utility methods used by the Locked[.Read/Write] annotations. + */ +public final class HandleLockedUtil { + private static final String INSTANCE_LOCK_NAME = "$lock"; + private static final String STATIC_LOCK_NAME = "$LOCK"; + private static final List NIL_EXPRESSION = List.nil(); + + private HandleLockedUtil() { + //Prevent instantiation + } + + /** + * See {@link #handle(String, JCAnnotation, JavacNode, Class, String, String[], String[], String)} for + * {@code lockableMethodName = null}. + */ + public static void handle(String annotationValue, JCTree.JCAnnotation ast, JavacNode annotationNode, Class annotationClass, String annotationName, String[] lockTypeClass, String[] lockImplClass) { + handle(annotationValue, ast, annotationNode, annotationClass, annotationName, lockTypeClass, lockImplClass, null); + } + + /** + * Called when an annotation is found that is likely to match {@link Locked}, {@link Locked.Read}, or + * {@link Locked.Write}. + * + * 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 ast The javac 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 annotationClass The specific annotation class. This should be one of {@code Locked}, {@code Locked.Read}, + * or {@code Locked.Write}. + * @param annotationName The name of the annotation to use when referencing it in errors. + * @param lockTypeClass The type of the variable when generating a lock to use. + * See {@link JavacHandlerUtil#chainDots(JavacNode, String, String, String...)}. + * @param lockImplClass Call the constructor of this class to generate a lock to use. + * See {@link JavacHandlerUtil#chainDots(JavacNode, String, String, String...)}. + * @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. + * @param The annotation type. + */ + public static void handle(String annotationValue, JCTree.JCAnnotation ast, JavacNode annotationNode, Class annotationClass, String annotationName, String[] lockTypeClass, String[] lockImplClass, String lockableMethodName) { + handleFlagUsage(annotationNode, ConfigurationKeys.LOCKED_FLAG_USAGE, annotationName); + + if (inNetbeansEditor(annotationNode)) return; + deleteAnnotationIfNeccessary(annotationNode, annotationClass); + + JavacNode methodNode = annotationNode.up(); + if (methodNode == null || methodNode.getKind() != AST.Kind.METHOD || !(methodNode.get() instanceof JCMethodDecl)) { + annotationNode.addError(annotationName + " is legal only on methods."); + return; + } + + JCMethodDecl method = (JCMethodDecl) methodNode.get(); + if ((method.mods.flags & Flags.ABSTRACT) != 0) { + annotationNode.addError(annotationName + " is legal only on concrete methods."); + return; + } + + JavacNode typeNode = upToTypeNode(annotationNode); + if (!isClassOrEnum(typeNode)) { + annotationNode.addError(annotationName + " is legal only on methods in classes and enums."); + return; + } + + boolean isStatic = (method.mods.flags & Flags.STATIC) != 0; + String lockName = annotationValue; + boolean autoMake = false; + + if (lockName.length() == 0) { + autoMake = true; + lockName = isStatic ? STATIC_LOCK_NAME : INSTANCE_LOCK_NAME; + } + + JavacTreeMaker maker = methodNode.getTreeMaker().at(ast.pos); + + MemberExistsResult exists = MemberExistsResult.NOT_EXISTS; + + JCExpression lockVarType = chainDots(methodNode, ast.pos, null, null, lockTypeClass); + + if (typeNode != null && typeNode.get() instanceof JCClassDecl) { + for (JCTree def : ((JCClassDecl) typeNode.get()).defs) { + if (def instanceof JCVariableDecl) { + if (((JCVariableDecl) def).name.contentEquals(lockName)) { + JCVariableDecl varDeclDef = (JCVariableDecl) def; + exists = getGeneratedBy(varDeclDef) == null ? MemberExistsResult.EXISTS_BY_USER : MemberExistsResult.EXISTS_BY_LOMBOK; + boolean st = ((varDeclDef.mods.flags) & Flags.STATIC) != 0; + + if (isStatic != st && exists == MemberExistsResult.EXISTS_BY_LOMBOK) { + annotationNode.addError("The generated field " + lockName + " does not match the static status of this method"); + return; + } + isStatic = st; + + if (exists == MemberExistsResult.EXISTS_BY_LOMBOK && !lockVarType.toString().equals(varDeclDef.vartype.toString())) { + annotationNode.addError("Expected field " + lockName + " to be of type " + lockVarType + " but got type " + varDeclDef.vartype + ". Did you mix @Locked with @Locked.Read/Write on the same generated field?"); + return; + } + } + } + } + } + + if (exists == MemberExistsResult.NOT_EXISTS) { + if (!autoMake) { + annotationNode.addError("The field " + lockName + " does not exist."); + return; + } + JCExpression lockImplType = chainDots(methodNode, ast.pos, null, null, lockImplClass); + JCNewClass lockInstance = maker.NewClass(null, NIL_EXPRESSION, lockImplType, NIL_EXPRESSION, null); + JCVariableDecl newLockField = recursiveSetGeneratedBy(maker.VarDef( + maker.Modifiers(Flags.PRIVATE | Flags.FINAL | (isStatic ? Flags.STATIC : 0)), + methodNode.toName(lockName), lockVarType, lockInstance), annotationNode); + injectFieldAndMarkGenerated(methodNode.up(), newLockField); + } + + if (method.body == null) return; + + JCExpression lockNode; + if (isStatic) { + lockNode = namePlusTypeParamsToTypeReference(maker, typeNode, methodNode.toName(lockName), false, List.nil()); + } else { + lockNode = maker.Select(maker.Ident(methodNode.toName("this")), methodNode.toName(lockName)); + } + + JCExpressionStatement acquireLock = maker.Exec(maker.Apply(NIL_EXPRESSION, maker.Select(getLockable(maker, typeNode, methodNode, lockableMethodName, lockNode), annotationNode.toName("lock")), NIL_EXPRESSION)); + JCExpressionStatement releaseLock = maker.Exec(maker.Apply(NIL_EXPRESSION, maker.Select(getLockable(maker, typeNode, methodNode, lockableMethodName, lockNode), annotationNode.toName("unlock")), NIL_EXPRESSION)); + + JCTry tryBlock = setGeneratedBy(maker.Try(method.body, List.nil(), recursiveSetGeneratedBy(maker.Block(0, List.of(releaseLock)), annotationNode)), annotationNode); + + method.body = setGeneratedBy(maker.Block(0, List.of(recursiveSetGeneratedBy(acquireLock, annotationNode), tryBlock)), annotationNode); + + methodNode.rebuild(); + } + + private static JCExpression getLockable(JavacTreeMaker maker, JavacNode typeNode, JavacNode methodNode, String lockableMethodName, JCExpression lockNode) { + if (lockableMethodName == null) { + return cloneType(maker, lockNode, typeNode); + } else { + return maker.Apply(NIL_EXPRESSION, maker.Select(cloneType(maker, lockNode, typeNode), methodNode.toName(lockableMethodName)), NIL_EXPRESSION); + } + } +} diff --git a/src/core/lombok/javac/handlers/HandleLockedWrite.java b/src/core/lombok/javac/handlers/HandleLockedWrite.java new file mode 100644 index 00000000..c726c456 --- /dev/null +++ b/src/core/lombok/javac/handlers/HandleLockedWrite.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2021-2023 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.javac.handlers; + +import lombok.core.AnnotationValues; +import lombok.core.HandlerPriority; +import lombok.Locked; +import lombok.javac.JavacAnnotationHandler; +import lombok.javac.JavacNode; +import lombok.spi.Provides; + +import static com.sun.tools.javac.tree.JCTree.*; + +/** + * Handles the {@code lombok.Locked.Write} annotation for javac. + */ +@Provides +@HandlerPriority(value = 1024) // 2^10; @NonNull must have run first, so that we wrap around the statements generated by it. +public class HandleLockedWrite extends JavacAnnotationHandler { + private static final String LOCK_METHOD = "writeLock"; + private static final String ANNOTATION_NAME = "@Locked.Write"; + private static final String[] LOCK_TYPE_CLASS = new String[]{"java", "util", "concurrent", "locks", "ReadWriteLock"}; + private static final String[] LOCK_IMPL_CLASS = new String[]{"java", "util", "concurrent", "locks", "ReentrantReadWriteLock"}; + + @Override public void handle(AnnotationValues annotation, JCAnnotation ast, JavacNode annotationNode) { + String annotationValue = annotation.getInstance().value(); + HandleLockedUtil.handle(annotationValue, ast, annotationNode, Locked.Write.class, ANNOTATION_NAME, LOCK_TYPE_CLASS, LOCK_IMPL_CLASS, LOCK_METHOD); + } +} diff --git a/src/core/lombok/javac/handlers/HandleSynchronized.java b/src/core/lombok/javac/handlers/HandleSynchronized.java index 0bf7cbe8..e4f960e7 100644 --- a/src/core/lombok/javac/handlers/HandleSynchronized.java +++ b/src/core/lombok/javac/handlers/HandleSynchronized.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2021 The Project Lombok Authors. + * Copyright (C) 2009-2023 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 @@ -60,8 +60,8 @@ public class HandleSynchronized extends JavacAnnotationHandler { handleFlagUsage(annotationNode, ConfigurationKeys.SYNCHRONIZED_FLAG_USAGE, "@Synchronized"); if (inNetbeansEditor(annotationNode)) return; - deleteAnnotationIfNeccessary(annotationNode, Synchronized.class); + JavacNode methodNode = annotationNode.up(); if (methodNode == null || methodNode.getKind() != Kind.METHOD || !(methodNode.get() instanceof JCMethodDecl)) { @@ -82,12 +82,12 @@ public class HandleSynchronized extends JavacAnnotationHandler { return; } - boolean[] isStatic = new boolean[] {(method.mods.flags & Flags.STATIC) != 0}; + boolean isStatic = (method.mods.flags & Flags.STATIC) != 0; String lockName = annotation.getInstance().value(); boolean autoMake = false; if (lockName.length() == 0) { autoMake = true; - lockName = isStatic[0] ? STATIC_LOCK_NAME : INSTANCE_LOCK_NAME; + lockName = isStatic ? STATIC_LOCK_NAME : INSTANCE_LOCK_NAME; } JavacTreeMaker maker = methodNode.getTreeMaker().at(ast.pos); @@ -100,11 +100,11 @@ public class HandleSynchronized extends JavacAnnotationHandler { if (((JCVariableDecl) def).name.contentEquals(lockName)) { exists = getGeneratedBy(def) == null ? MemberExistsResult.EXISTS_BY_USER : MemberExistsResult.EXISTS_BY_LOMBOK; boolean st = ((((JCVariableDecl) def).mods.flags) & Flags.STATIC) != 0; - if (isStatic[0] && !st) { + if (isStatic && !st) { annotationNode.addError("The field " + lockName + " is non-static and this cannot be used on this static method"); return; } - isStatic[0] = st; + isStatic = st; } } } @@ -120,7 +120,7 @@ public class HandleSynchronized extends JavacAnnotationHandler { JCNewArray newObjectArray = maker.NewArray(genJavaLangTypeRef(methodNode, ast.pos, "Object"), List.of(maker.Literal(CTC_INT, 0)), null); JCVariableDecl fieldDecl = recursiveSetGeneratedBy(maker.VarDef( - maker.Modifiers(Flags.PRIVATE | Flags.FINAL | (isStatic[0] ? Flags.STATIC : 0)), + maker.Modifiers(Flags.PRIVATE | Flags.FINAL | (isStatic ? Flags.STATIC : 0)), methodNode.toName(lockName), objectType, newObjectArray), annotationNode); injectFieldAndMarkGenerated(methodNode.up(), fieldDecl); } @@ -128,7 +128,7 @@ public class HandleSynchronized extends JavacAnnotationHandler { if (method.body == null) return; JCExpression lockNode; - if (isStatic[0]) { + if (isStatic) { lockNode = namePlusTypeParamsToTypeReference(maker, typeNode, methodNode.toName(lockName), false, List.nil()); } else { lockNode = maker.Select(maker.Ident(methodNode.toName("this")), methodNode.toName(lockName)); diff --git a/test/transform/resource/after-delombok/LockedInRecord.java b/test/transform/resource/after-delombok/LockedInRecord.java new file mode 100644 index 00000000..14d5a4ef --- /dev/null +++ b/test/transform/resource/after-delombok/LockedInRecord.java @@ -0,0 +1,6 @@ +// version 14: +public record LockedInRecord(String a, String b) { + public void foo() { + String foo = "bar"; + } +} diff --git a/test/transform/resource/after-delombok/LockedName.java b/test/transform/resource/after-delombok/LockedName.java new file mode 100644 index 00000000..1121ce1c --- /dev/null +++ b/test/transform/resource/after-delombok/LockedName.java @@ -0,0 +1,29 @@ +import java.util.concurrent.locks.*; +class LockedName { + private final Lock basicLock = new ReentrantLock(); + private final ReadWriteLock rwLock = new ReentrantReadWriteLock(); + void test() { + this.basicLock.lock(); + try { + System.out.println("one"); + } finally { + this.basicLock.unlock(); + } + } + void test2() { + this.rwLock.readLock().lock(); + try { + System.out.println("two"); + } finally { + this.rwLock.readLock().unlock(); + } + } + void test3() { + this.rwLock.writeLock().lock(); + try { + System.out.println("three"); + } finally { + this.rwLock.writeLock().unlock(); + } + } +} diff --git a/test/transform/resource/after-delombok/LockedOnStatic.java b/test/transform/resource/after-delombok/LockedOnStatic.java new file mode 100644 index 00000000..766b6ea1 --- /dev/null +++ b/test/transform/resource/after-delombok/LockedOnStatic.java @@ -0,0 +1,24 @@ +class LockedOnStatic { + static class Inner { + private static final java.util.concurrent.locks.Lock LCK = new java.util.concurrent.locks.ReentrantLock(); + public void foo() { + LockedOnStatic.Inner.LCK.lock(); + try { + System.out.println(); + } finally { + LockedOnStatic.Inner.LCK.unlock(); + } + } + } + class Inner2 { + private final java.util.concurrent.locks.ReentrantLock LCK = new java.util.concurrent.locks.ReentrantLock(); + public void foo() { + this.LCK.lock(); + try { + System.out.println(); + } finally { + this.LCK.unlock(); + } + } + } +} diff --git a/test/transform/resource/after-delombok/LockedPlain.java b/test/transform/resource/after-delombok/LockedPlain.java new file mode 100644 index 00000000..bdc4e2a4 --- /dev/null +++ b/test/transform/resource/after-delombok/LockedPlain.java @@ -0,0 +1,80 @@ +class LockedPlain { + @java.lang.SuppressWarnings("all") + private final java.util.concurrent.locks.Lock $lock = new java.util.concurrent.locks.ReentrantLock(); + void test() { + this.$lock.lock(); + try { + System.out.println("one"); + } finally { + this.$lock.unlock(); + } + } + void test2() { + this.$lock.lock(); + try { + System.out.println("two"); + } finally { + this.$lock.unlock(); + } + } +} +class LockedPlainStatic { + @java.lang.SuppressWarnings("all") + private static final java.util.concurrent.locks.Lock $LOCK = new java.util.concurrent.locks.ReentrantLock(); + static void test() { + LockedPlainStatic.$LOCK.lock(); + try { + System.out.println("three"); + } finally { + LockedPlainStatic.$LOCK.unlock(); + } + } + static void test2() { + LockedPlainStatic.$LOCK.lock(); + try { + System.out.println("four"); + } finally { + LockedPlainStatic.$LOCK.unlock(); + } + } +} +class LockedPlainRead { + @java.lang.SuppressWarnings("all") + private static final java.util.concurrent.locks.ReadWriteLock $LOCK = new java.util.concurrent.locks.ReentrantReadWriteLock(); + static void test() { + LockedPlainRead.$LOCK.readLock().lock(); + try { + System.out.println("five"); + } finally { + LockedPlainRead.$LOCK.readLock().unlock(); + } + } + static void test2() { + LockedPlainRead.$LOCK.readLock().lock(); + try { + System.out.println("six"); + } finally { + LockedPlainRead.$LOCK.readLock().unlock(); + } + } +} +class LockedPlainWrite { + @java.lang.SuppressWarnings("all") + private final java.util.concurrent.locks.ReadWriteLock $lock = new java.util.concurrent.locks.ReentrantReadWriteLock(); + void test() { + this.$lock.writeLock().lock(); + try { + System.out.println("seven"); + } finally { + this.$lock.writeLock().unlock(); + } + } + void test2() { + this.$lock.writeLock().lock(); + try { + System.out.println("eight"); + } finally { + this.$lock.writeLock().unlock(); + } + } +} diff --git a/test/transform/resource/after-delombok/LockedStaticMix.java b/test/transform/resource/after-delombok/LockedStaticMix.java new file mode 100644 index 00000000..e5461248 --- /dev/null +++ b/test/transform/resource/after-delombok/LockedStaticMix.java @@ -0,0 +1,34 @@ +class LockedGeneratedStaticMismatch { + @java.lang.SuppressWarnings("all") + private static final java.util.concurrent.locks.Lock $LOCK = new java.util.concurrent.locks.ReentrantLock(); + static void test() { + LockedGeneratedStaticMismatch.$LOCK.lock(); + try { + System.out.println("one"); + } finally { + LockedGeneratedStaticMismatch.$LOCK.unlock(); + } + } + void test2() { + System.out.println("two"); + } +} +class LockedUserStaticMismatch { + private static final java.util.concurrent.locks.Lock userLock = new java.util.concurrent.locks.ReentrantLock(); + static void test() { + LockedUserStaticMismatch.userLock.lock(); + try { + System.out.println("one"); + } finally { + LockedUserStaticMismatch.userLock.unlock(); + } + } + void test2() { + LockedUserStaticMismatch.userLock.lock(); + try { + System.out.println("two"); + } finally { + LockedUserStaticMismatch.userLock.unlock(); + } + } +} diff --git a/test/transform/resource/after-delombok/LockedTypeMismatch.java b/test/transform/resource/after-delombok/LockedTypeMismatch.java new file mode 100644 index 00000000..eb0a4b19 --- /dev/null +++ b/test/transform/resource/after-delombok/LockedTypeMismatch.java @@ -0,0 +1,34 @@ +class LockedGeneratedTypeMismatch { + @java.lang.SuppressWarnings("all") + private final java.util.concurrent.locks.Lock $lock = new java.util.concurrent.locks.ReentrantLock(); + void test() { + this.$lock.lock(); + try { + System.out.println("one"); + } finally { + this.$lock.unlock(); + } + } + void test2() { + System.out.println("two"); + } +} +class LockedUserTypeMismatch { + private final java.util.concurrent.locks.Lock userLock = new java.util.concurrent.locks.ReentrantLock(); + void test() { + this.userLock.lock(); + try { + System.out.println("one"); + } finally { + this.userLock.unlock(); + } + } + void test2() { + this.userLock.readLock().lock(); + try { + System.out.println("two"); + } finally { + this.userLock.readLock().unlock(); + } + } +} diff --git a/test/transform/resource/after-ecj/LockedInRecord.java b/test/transform/resource/after-ecj/LockedInRecord.java new file mode 100644 index 00000000..9801227a --- /dev/null +++ b/test/transform/resource/after-ecj/LockedInRecord.java @@ -0,0 +1,14 @@ +import lombok.Locked; +public record LockedInRecord(String a, String b) { +/* Implicit */ private final String a; +/* Implicit */ private final String b; + private final java.util.concurrent.locks.Lock $lock = new java.util.concurrent.locks.ReentrantLock(); + public LockedInRecord(String a, String b) { + super(); + .a = a; + .b = b; + } + public @Locked void foo() { + String foo = "bar"; + } +} diff --git a/test/transform/resource/after-ecj/LockedName.java b/test/transform/resource/after-ecj/LockedName.java new file mode 100644 index 00000000..e42fdf92 --- /dev/null +++ b/test/transform/resource/after-ecj/LockedName.java @@ -0,0 +1,41 @@ +import java.util.concurrent.locks.*; +class LockedName { + private final Lock basicLock = new ReentrantLock(); + private final ReadWriteLock rwLock = new ReentrantReadWriteLock(); + LockedName() { + super(); + } + @lombok.Locked("basicLock") void test() { + this.basicLock.lock(); + try + { + System.out.println("one"); + } + finally + { + this.basicLock.unlock(); + } + } + @lombok.Locked.Read("rwLock") void test2() { + this.rwLock.readLock().lock(); + try + { + System.out.println("two"); + } + finally + { + this.rwLock.readLock().unlock(); + } + } + @lombok.Locked.Write("rwLock") void test3() { + this.rwLock.writeLock().lock(); + try + { + System.out.println("three"); + } + finally + { + this.rwLock.writeLock().unlock(); + } + } +} diff --git a/test/transform/resource/after-ecj/LockedOnStatic.java b/test/transform/resource/after-ecj/LockedOnStatic.java new file mode 100644 index 00000000..a994b2f5 --- /dev/null +++ b/test/transform/resource/after-ecj/LockedOnStatic.java @@ -0,0 +1,41 @@ +class LockedOnStatic { + static class Inner { + private static final java.util.concurrent.locks.Lock LCK = new java.util.concurrent.locks.ReentrantLock(); + () { + } + Inner() { + super(); + } + public @lombok.Locked("LCK") void foo() { + LockedOnStatic.Inner.LCK.lock(); + try + { + System.out.println(); + } + finally + { + LockedOnStatic.Inner.LCK.unlock(); + } + } + } + class Inner2 { + private final java.util.concurrent.locks.ReentrantLock LCK = new java.util.concurrent.locks.ReentrantLock(); + Inner2() { + super(); + } + public @lombok.Locked("LCK") void foo() { + this.LCK.lock(); + try + { + System.out.println(); + } + finally + { + this.LCK.unlock(); + } + } + } + LockedOnStatic() { + super(); + } +} diff --git a/test/transform/resource/after-ecj/LockedPlain.java b/test/transform/resource/after-ecj/LockedPlain.java new file mode 100644 index 00000000..f311c193 --- /dev/null +++ b/test/transform/resource/after-ecj/LockedPlain.java @@ -0,0 +1,117 @@ +import lombok.Locked; +class LockedPlain { + private final java.util.concurrent.locks.Lock $lock = new java.util.concurrent.locks.ReentrantLock(); + LockedPlain() { + super(); + } + @Locked void test() { + this.$lock.lock(); + try + { + System.out.println("one"); + } + finally + { + this.$lock.unlock(); + } + } + @Locked void test2() { + this.$lock.lock(); + try + { + System.out.println("two"); + } + finally + { + this.$lock.unlock(); + } + } +} +class LockedPlainStatic { + private static final java.util.concurrent.locks.Lock $LOCK = new java.util.concurrent.locks.ReentrantLock(); + () { + } + LockedPlainStatic() { + super(); + } + static @Locked void test() { + LockedPlainStatic.$LOCK.lock(); + try + { + System.out.println("three"); + } + finally + { + LockedPlainStatic.$LOCK.unlock(); + } + } + static @Locked void test2() { + LockedPlainStatic.$LOCK.lock(); + try + { + System.out.println("four"); + } + finally + { + LockedPlainStatic.$LOCK.unlock(); + } + } +} +class LockedPlainRead { + private static final java.util.concurrent.locks.ReadWriteLock $LOCK = new java.util.concurrent.locks.ReentrantReadWriteLock(); + () { + } + LockedPlainRead() { + super(); + } + static @Locked.Read void test() { + LockedPlainRead.$LOCK.readLock().lock(); + try + { + System.out.println("five"); + } + finally + { + LockedPlainRead.$LOCK.readLock().unlock(); + } + } + static @Locked.Read void test2() { + LockedPlainRead.$LOCK.readLock().lock(); + try + { + System.out.println("six"); + } + finally + { + LockedPlainRead.$LOCK.readLock().unlock(); + } + } +} +class LockedPlainWrite { + private final java.util.concurrent.locks.ReadWriteLock $lock = new java.util.concurrent.locks.ReentrantReadWriteLock(); + LockedPlainWrite() { + super(); + } + @Locked.Write void test() { + this.$lock.writeLock().lock(); + try + { + System.out.println("seven"); + } + finally + { + this.$lock.writeLock().unlock(); + } + } + @Locked.Write void test2() { + this.$lock.writeLock().lock(); + try + { + System.out.println("eight"); + } + finally + { + this.$lock.writeLock().unlock(); + } + } +} diff --git a/test/transform/resource/after-ecj/LockedStaticMix.java b/test/transform/resource/after-ecj/LockedStaticMix.java new file mode 100644 index 00000000..98c8e804 --- /dev/null +++ b/test/transform/resource/after-ecj/LockedStaticMix.java @@ -0,0 +1,52 @@ +class LockedGeneratedStaticMismatch { + private static final java.util.concurrent.locks.Lock $LOCK = new java.util.concurrent.locks.ReentrantLock(); + () { + } + LockedGeneratedStaticMismatch() { + super(); + } + static @lombok.Locked void test() { + LockedGeneratedStaticMismatch.$LOCK.lock(); + try + { + System.out.println("one"); + } + finally + { + LockedGeneratedStaticMismatch.$LOCK.unlock(); + } + } + @lombok.Locked("$LOCK") void test2() { + System.out.println("two"); + } +} +class LockedUserStaticMismatch { + private static final java.util.concurrent.locks.Lock userLock = new java.util.concurrent.locks.ReentrantLock(); + () { + } + LockedUserStaticMismatch() { + super(); + } + static @lombok.Locked("userLock") void test() { + LockedUserStaticMismatch.userLock.lock(); + try + { + System.out.println("one"); + } + finally + { + LockedUserStaticMismatch.userLock.unlock(); + } + } + @lombok.Locked("userLock") void test2() { + LockedUserStaticMismatch.userLock.lock(); + try + { + System.out.println("two"); + } + finally + { + LockedUserStaticMismatch.userLock.unlock(); + } + } +} diff --git a/test/transform/resource/after-ecj/LockedTypeMismatch.java b/test/transform/resource/after-ecj/LockedTypeMismatch.java new file mode 100644 index 00000000..390ec572 --- /dev/null +++ b/test/transform/resource/after-ecj/LockedTypeMismatch.java @@ -0,0 +1,48 @@ +class LockedGeneratedTypeMismatch { + private final java.util.concurrent.locks.Lock $lock = new java.util.concurrent.locks.ReentrantLock(); + LockedGeneratedTypeMismatch() { + super(); + } + @lombok.Locked void test() { + this.$lock.lock(); + try + { + System.out.println("one"); + } + finally + { + this.$lock.unlock(); + } + } + @lombok.Locked.Read void test2() { + System.out.println("two"); + } +} +class LockedUserTypeMismatch { + private final java.util.concurrent.locks.Lock userLock = new java.util.concurrent.locks.ReentrantLock(); + LockedUserTypeMismatch() { + super(); + } + @lombok.Locked("userLock") void test() { + this.userLock.lock(); + try + { + System.out.println("one"); + } + finally + { + this.userLock.unlock(); + } + } + @lombok.Locked.Read("userLock") void test2() { + this.userLock.readLock().lock(); + try + { + System.out.println("two"); + } + finally + { + this.userLock.readLock().unlock(); + } + } +} diff --git a/test/transform/resource/before/LockedInRecord.java b/test/transform/resource/before/LockedInRecord.java new file mode 100644 index 00000000..8d368998 --- /dev/null +++ b/test/transform/resource/before/LockedInRecord.java @@ -0,0 +1,10 @@ +// version 14: + +import lombok.Locked; + +public record LockedInRecord(String a, String b) { + @Locked + public void foo() { + String foo = "bar"; + } +} diff --git a/test/transform/resource/before/LockedName.java b/test/transform/resource/before/LockedName.java new file mode 100644 index 00000000..66fe7d95 --- /dev/null +++ b/test/transform/resource/before/LockedName.java @@ -0,0 +1,14 @@ +import java.util.concurrent.locks.*; +class LockedName { + private final Lock basicLock = new ReentrantLock(); + private final ReadWriteLock rwLock = new ReentrantReadWriteLock(); + @lombok.Locked("basicLock") void test() { + System.out.println("one"); + } + @lombok.Locked.Read("rwLock") void test2() { + System.out.println("two"); + } + @lombok.Locked.Write("rwLock") void test3() { + System.out.println("three"); + } +} diff --git a/test/transform/resource/before/LockedOnStatic.java b/test/transform/resource/before/LockedOnStatic.java new file mode 100644 index 00000000..4c1cb2a7 --- /dev/null +++ b/test/transform/resource/before/LockedOnStatic.java @@ -0,0 +1,16 @@ +class LockedOnStatic { + static class Inner { + private static final java.util.concurrent.locks.Lock LCK = new java.util.concurrent.locks.ReentrantLock(); + @lombok.Locked("LCK") + public void foo() { + System.out.println(); + } + } + class Inner2 { + private final java.util.concurrent.locks.ReentrantLock LCK = new java.util.concurrent.locks.ReentrantLock(); + @lombok.Locked("LCK") + public void foo() { + System.out.println(); + } + } +} diff --git a/test/transform/resource/before/LockedPlain.java b/test/transform/resource/before/LockedPlain.java new file mode 100644 index 00000000..5ebe25f4 --- /dev/null +++ b/test/transform/resource/before/LockedPlain.java @@ -0,0 +1,33 @@ +import lombok.Locked; +class LockedPlain { + @Locked void test() { + System.out.println("one"); + } + @Locked void test2() { + System.out.println("two"); + } +} +class LockedPlainStatic { + @Locked static void test() { + System.out.println("three"); + } + @Locked static void test2() { + System.out.println("four"); + } +} +class LockedPlainRead { + @Locked.Read static void test() { + System.out.println("five"); + } + @Locked.Read static void test2() { + System.out.println("six"); + } +} +class LockedPlainWrite { + @Locked.Write void test() { + System.out.println("seven"); + } + @Locked.Write void test2() { + System.out.println("eight"); + } +} diff --git a/test/transform/resource/before/LockedStaticMix.java b/test/transform/resource/before/LockedStaticMix.java new file mode 100644 index 00000000..8d8605a0 --- /dev/null +++ b/test/transform/resource/before/LockedStaticMix.java @@ -0,0 +1,17 @@ +class LockedGeneratedStaticMismatch { + @lombok.Locked static void test() { + System.out.println("one"); + } + @lombok.Locked("$LOCK") void test2() { + System.out.println("two"); + } +} +class LockedUserStaticMismatch { + private static final java.util.concurrent.locks.Lock userLock = new java.util.concurrent.locks.ReentrantLock(); + @lombok.Locked("userLock") static void test() { + System.out.println("one"); + } + @lombok.Locked("userLock") void test2() { + System.out.println("two"); + } +} diff --git a/test/transform/resource/before/LockedTypeMismatch.java b/test/transform/resource/before/LockedTypeMismatch.java new file mode 100644 index 00000000..8ee3a932 --- /dev/null +++ b/test/transform/resource/before/LockedTypeMismatch.java @@ -0,0 +1,17 @@ +class LockedGeneratedTypeMismatch { + @lombok.Locked void test() { + System.out.println("one"); + } + @lombok.Locked.Read void test2() { + System.out.println("two"); + } +} +class LockedUserTypeMismatch { + private final java.util.concurrent.locks.Lock userLock = new java.util.concurrent.locks.ReentrantLock(); + @lombok.Locked("userLock") void test() { + System.out.println("one"); + } + @lombok.Locked.Read("userLock") void test2() { + System.out.println("two"); + } +} diff --git a/test/transform/resource/messages-delombok/LockedInRecord.java.messages b/test/transform/resource/messages-delombok/LockedInRecord.java.messages new file mode 100644 index 00000000..0165764d --- /dev/null +++ b/test/transform/resource/messages-delombok/LockedInRecord.java.messages @@ -0,0 +1 @@ +5 @Locked is legal only on methods in classes and enums. diff --git a/test/transform/resource/messages-delombok/LockedStaticMix.java.messages b/test/transform/resource/messages-delombok/LockedStaticMix.java.messages new file mode 100644 index 00000000..9320bb37 --- /dev/null +++ b/test/transform/resource/messages-delombok/LockedStaticMix.java.messages @@ -0,0 +1 @@ +5 The generated field LOCK does not match the static status of this method diff --git a/test/transform/resource/messages-delombok/LockedTypeMismatch.java.messages b/test/transform/resource/messages-delombok/LockedTypeMismatch.java.messages new file mode 100644 index 00000000..c53b9f29 --- /dev/null +++ b/test/transform/resource/messages-delombok/LockedTypeMismatch.java.messages @@ -0,0 +1 @@ +5 Expected field lock to be of type java.util.concurrent.locks.ReadWriteLock but got type java.util.concurrent.locks.Lock. Did you mix @Locked with @Locked.Read/Write on the same generated field? diff --git a/test/transform/resource/messages-ecj/LockedInRecord.java.messages b/test/transform/resource/messages-ecj/LockedInRecord.java.messages new file mode 100644 index 00000000..1e921a76 --- /dev/null +++ b/test/transform/resource/messages-ecj/LockedInRecord.java.messages @@ -0,0 +1 @@ +6 @Locked is legal only on methods in classes and enums. diff --git a/test/transform/resource/messages-ecj/LockedStaticMix.java.messages b/test/transform/resource/messages-ecj/LockedStaticMix.java.messages new file mode 100644 index 00000000..9320bb37 --- /dev/null +++ b/test/transform/resource/messages-ecj/LockedStaticMix.java.messages @@ -0,0 +1 @@ +5 The generated field LOCK does not match the static status of this method diff --git a/test/transform/resource/messages-ecj/LockedTypeMismatch.java.messages b/test/transform/resource/messages-ecj/LockedTypeMismatch.java.messages new file mode 100644 index 00000000..a1a81b4a --- /dev/null +++ b/test/transform/resource/messages-ecj/LockedTypeMismatch.java.messages @@ -0,0 +1,2 @@ +5 Expected field lock to be of type java.util.concurrent.locks.ReadWriteLock but got type java.util.concurrent.locks.Lock. Did you mix @Locked with @Locked.Read/Write on the same generated field? +14 The method readLock() is undefined for the type Lock diff --git a/test/transform/resource/messages-idempotent/LockedTypeMismatch.java.messages b/test/transform/resource/messages-idempotent/LockedTypeMismatch.java.messages new file mode 100644 index 00000000..195f1466 --- /dev/null +++ b/test/transform/resource/messages-idempotent/LockedTypeMismatch.java.messages @@ -0,0 +1,2 @@ +27 cannot find symbol +31 cannot find symbol diff --git a/website/templates/features/Locked.html b/website/templates/features/Locked.html new file mode 100644 index 00000000..105bf6dc --- /dev/null +++ b/website/templates/features/Locked.html @@ -0,0 +1,38 @@ +<#import "../_features.html" as f> + +<@f.scaffold title="@Locked" logline="Pop it and lock it! ReentrantLock, now with less hassle."> + <@f.history> +

+ @Locked was introduced in lombok v1.20. +

+ + + <@f.overview> +

+ @Locked wraps all code in a method into a block that acquires a java.util.concurrent.locks.ReentrantLock first, and unlocks it when exiting the method. It is a lot like @Synchronized. +

+ You can optionally name a field, which must be a ReentrantLock; in that case, lombok locks on that field. Otherwise, the annotation defaults to a field named $LOCK (on static methods) / $lock (on instance methods), which lombok will generate if the field does not exist yet. +

+ Additionally, there are the @Locked.Read and @Locked.Write annotations. These use a java.util.concurrent.locks.ReadWriteLock (specifically, ReentrantReadWriteLock). Methods annotated with @Locked.Write will lock on the write lock and methods annotated with @Locked.Read will lock on the read lock. When required, a java.util.concurrent.locks.ReentrantReadWriteLock is generated. +

+ When using Virtual threads (introduced in Java 20), these locks are recommended compared to what @Synchronized does. +

+ + + <@f.snippets name="Locked" /> + + <@f.confKeys> +
+ lombok.locked.flagUsage = [warning | error] (default: not set) +
+ Lombok will flag any usage of @Locked as a warning or error if configured. +
+ + + <@f.smallPrint> +

+ Because @Locked.Read and @Locked.Write use a different type of lock than @Locked, these annotations cannot be used on the same lock object without explicitly specifying the name of the field containing the lock object.
+ The name of the default field of the @Locked, @Locked.Read, and @Locked.Write annotations is the same, so it is not possible to mix the basic @Locked annotation with the other two using the default name. +

+ + diff --git a/website/templates/features/index.html b/website/templates/features/index.html index 9e2f53e8..37d98d65 100644 --- a/website/templates/features/index.html +++ b/website/templates/features/index.html @@ -66,6 +66,10 @@ synchronized done right: Don't expose your locks. + <@main.feature title="@Locked" href="Locked"> + Pop it and lock it! ReentrantLock, now with less hassle. + + <@main.feature title="@With" href="With"> Immutable 'setters' - methods that create a clone but with one changed field. diff --git a/website/usageExamples/experimental/LockedExample_post.jpage b/website/usageExamples/experimental/LockedExample_post.jpage new file mode 100644 index 00000000..0a20ebec --- /dev/null +++ b/website/usageExamples/experimental/LockedExample_post.jpage @@ -0,0 +1,32 @@ +public class LockedExample { + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + private final Lock baseLock = new ReentrantLock(); + private int value = 0; + + public int getValue() { + this.lock.readLock().lock(); + try { + return value; + } finally { + this.lock.readLock().unlock(); + } + } + + public void setValue(int newValue) { + this.lock.writeLock().lock(); + try { + value = newValue; + } finally { + this.lock.writeLock().unlock(); + } + } + + public void foo() { + this.baseLock.lock(); + try { + System.out.println("bar"); + } finally { + this.baseLock.unlock(); + } + } +} diff --git a/website/usageExamples/experimental/LockedExample_pre.jpage b/website/usageExamples/experimental/LockedExample_pre.jpage new file mode 100644 index 00000000..b741d235 --- /dev/null +++ b/website/usageExamples/experimental/LockedExample_pre.jpage @@ -0,0 +1,20 @@ +import lombok.Locked; + +public class LockedExample { + private int value = 0; + + @Locked.Read + public int getValue() { + return value; + } + + @Locked.Write + public void setValue(int newValue) { + value = newValue; + } + + @Locked("baseLock") + public void foo() { + System.out.println("bar"); + } +}