From 21375449db051852ec76824845e6a6af88395d9e Mon Sep 17 00:00:00 2001 From: Pim van der Loos Date: Fri, 28 May 2021 12:25:31 +0200 Subject: [PATCH 1/9] Add Locked, Locked.Read, and Locked.Write annotations - Added Locked[.Read/Write] annotations which can be applied to method to lock the method body using a Reentrant[ReadWrite]Lock. --- AUTHORS | 1 + src/core/lombok/ConfigurationKeys.java | 13 +- src/core/lombok/experimental/Locked.java | 62 ++++++ .../lombok/javac/handlers/HandleLocked.java | 46 ++++ .../javac/handlers/HandleLockedRead.java | 49 +++++ .../javac/handlers/HandleLockedUtil.java | 198 ++++++++++++++++++ .../javac/handlers/HandleLockedWrite.java | 49 +++++ 7 files changed, 416 insertions(+), 2 deletions(-) create mode 100644 src/core/lombok/experimental/Locked.java create mode 100644 src/core/lombok/javac/handlers/HandleLocked.java create mode 100644 src/core/lombok/javac/handlers/HandleLockedRead.java create mode 100644 src/core/lombok/javac/handlers/HandleLockedUtil.java create mode 100644 src/core/lombok/javac/handlers/HandleLockedWrite.java 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 93bcb85f..87f26122 100644 --- a/src/core/lombok/ConfigurationKeys.java +++ b/src/core/lombok/ConfigurationKeys.java @@ -391,7 +391,7 @@ public class ConfigurationKeys { * If set, any usage of {@code @Synchronized} results in a warning / error. */ public static final ConfigurationKey SYNCHRONIZED_FLAG_USAGE = new ConfigurationKey("lombok.synchronized.flagUsage", "Emit a warning or error if @Synchronized is used.") {}; - + // ----- val ----- /** @@ -623,7 +623,16 @@ public class ConfigurationKeys { * If set, any usage of {@code @Helper} results in a warning / error. */ 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 ----- /** diff --git a/src/core/lombok/experimental/Locked.java b/src/core/lombok/experimental/Locked.java new file mode 100644 index 00000000..61e59ee4 --- /dev/null +++ b/src/core/lombok/experimental/Locked.java @@ -0,0 +1,62 @@ +package lombok.experimental; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.concurrent.locks.ReadWriteLock; + +/** + * Uses a {@link java.util.concurrent.locks.Lock} as a more flexible alternative to the 'synchronized' keyword with + * optional increased throughput when making use of read and write locks. + *

+ * 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. The contents + * of the fields will be serializable. + *

+ * 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 ReadWriteLock#readLock()}. + */ + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.SOURCE) + public @interface Read { + /** + * Optional: specify the name of a different field to lock on. If this field doesn't already exist, it is generated + * automatically. + * + * @return Name of the field to lock on (blank = generate one). + */ + String value() default ""; + } + + /** + * Locks using a {@link ReadWriteLock#writeLock()}. + */ + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.SOURCE) + public @interface Write { + /** + * Optional: specify the name of a different field to lock on. If this field doesn't already exist, it is generated + * automatically. + * + * @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. If this field doesn't already exist, it is generated + * automatically. + * + * @return Name of the field to lock on (blank = generate one). + */ + String value() default ""; +} diff --git a/src/core/lombok/javac/handlers/HandleLocked.java b/src/core/lombok/javac/handlers/HandleLocked.java new file mode 100644 index 00000000..7eb1c5c9 --- /dev/null +++ b/src/core/lombok/javac/handlers/HandleLocked.java @@ -0,0 +1,46 @@ +/* + * 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.javac.handlers; + +import lombok.core.AnnotationValues; +import lombok.core.HandlerPriority; +import lombok.experimental.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_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_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..30be1cc3 --- /dev/null +++ b/src/core/lombok/javac/handlers/HandleLockedRead.java @@ -0,0 +1,49 @@ +/* + * 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.javac.handlers; + +import lombok.core.AnnotationValues; +import lombok.core.HandlerPriority; +import lombok.experimental.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_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_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..9e51eeba --- /dev/null +++ b/src/core/lombok/javac/handlers/HandleLockedUtil.java @@ -0,0 +1,198 @@ +/* + * 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.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.ConfigurationKeys; +import lombok.core.AST; +import lombok.experimental.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.handleFlagUsage; +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)} for + * {@code lockableMethodName = null}. + */ + public static void handle(String annotationValue, JCTree.JCAnnotation ast, + JavacNode annotationNode, Class annotationClass, + String annotationName, String[] lockClass) { + handle(annotationValue, ast, annotationNode, annotationClass, annotationName, lockClass, 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 lockClass The type of 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[] lockClass, + 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 = new boolean[]{(method.mods.flags & Flags.STATIC) != 0}; + String lockName = annotationValue; + + if (lockName.length() == 0) { + lockName = isStatic[0] ? STATIC_LOCK_NAME : INSTANCE_LOCK_NAME; + } + + JavacTreeMaker maker = methodNode.getTreeMaker().at(ast.pos); + + MemberExistsResult exists = MemberExistsResult.NOT_EXISTS; + + JCExpression lockType = chainDots(methodNode, lockClass); + + 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[0] != st && exists == MemberExistsResult.EXISTS_BY_LOMBOK) { + annotationNode.addError("The generated field " + lockName + + " does not match the static status of this method"); + return; + } + isStatic[0] = st; + + if (exists == MemberExistsResult.EXISTS_BY_LOMBOK && + !lockType.toString().equals(varDeclDef.vartype.toString())) { + annotationNode.addError("Expected field " + lockName + " to be of type " + lockType + + " but got type " + varDeclDef.vartype + "!" + + " Did you mix @Locked with @Locked.Read/Write on the same generated field?"); + return; + } + } + } + } + } + + if (exists == MemberExistsResult.NOT_EXISTS) { + JCNewClass lockInstance = maker.NewClass(null, NIL_EXPRESSION, lockType, NIL_EXPRESSION, null); + JCVariableDecl newLockField = recursiveSetGeneratedBy(maker.VarDef( + maker.Modifiers(Flags.PRIVATE | Flags.FINAL | (isStatic[0] ? Flags.STATIC : 0)), + methodNode.toName(lockName), lockType, lockInstance + ), annotationNode); + + injectFieldAndMarkGenerated(methodNode.up(), newLockField); + } + + if (method.body == null || typeNode == null) return; + + JCExpression lockNode; + if (isStatic[0]) { + lockNode = namePlusTypeParamsToTypeReference(maker, typeNode, methodNode.toName(lockName), false, + List.nil()); + } else { + lockNode = maker.Select(maker.Ident(methodNode.toName("this")), methodNode.toName(lockName)); + } + + recursiveSetGeneratedBy(lockNode, annotationNode); + + final JCExpression lockable; + if (lockableMethodName == null) { + lockable = lockNode; + } else { + lockable = maker.Apply(NIL_EXPRESSION, + maker.Select(lockNode, methodNode.toName(lockableMethodName)), + NIL_EXPRESSION); + } + + JCExpressionStatement acquireLock = + maker.Exec(maker.Apply(NIL_EXPRESSION, maker.Select(lockable, typeNode.toName("lock")), NIL_EXPRESSION)); + JCExpressionStatement releaseLock = + maker.Exec(maker.Apply(NIL_EXPRESSION, maker.Select(lockable, typeNode.toName("unlock")), NIL_EXPRESSION)); + + JCTry tryBlock = recursiveSetGeneratedBy(maker.Try(maker.Block(0L, method.body.stats), + List.nil(), + maker.Block(0L, List.of(releaseLock))), + annotationNode); + + method.body.stats = List.of(tryBlock) + .prepend(recursiveSetGeneratedBy(acquireLock, annotationNode)); + methodNode.rebuild(); + } +} diff --git a/src/core/lombok/javac/handlers/HandleLockedWrite.java b/src/core/lombok/javac/handlers/HandleLockedWrite.java new file mode 100644 index 00000000..cbcb2f32 --- /dev/null +++ b/src/core/lombok/javac/handlers/HandleLockedWrite.java @@ -0,0 +1,49 @@ +/* + * 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.javac.handlers; + +import lombok.core.AnnotationValues; +import lombok.core.HandlerPriority; +import lombok.experimental.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_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_CLASS, LOCK_METHOD); + } +} From 5e31bb253c09a5462ee2308001f2554aa54df218 Mon Sep 17 00:00:00 2001 From: Pim van der Loos Date: Mon, 31 May 2021 13:30:19 +0200 Subject: [PATCH 2/9] Add Eclipse implementation for Locked annotations --- .../lombok/eclipse/handlers/HandleLocked.java | 55 ++++ .../eclipse/handlers/HandleLockedRead.java | 56 ++++ .../eclipse/handlers/HandleLockedUtil.java | 270 ++++++++++++++++++ .../eclipse/handlers/HandleLockedWrite.java | 56 ++++ 4 files changed, 437 insertions(+) create mode 100644 src/core/lombok/eclipse/handlers/HandleLocked.java create mode 100644 src/core/lombok/eclipse/handlers/HandleLockedRead.java create mode 100644 src/core/lombok/eclipse/handlers/HandleLockedUtil.java create mode 100644 src/core/lombok/eclipse/handlers/HandleLockedWrite.java diff --git a/src/core/lombok/eclipse/handlers/HandleLocked.java b/src/core/lombok/eclipse/handlers/HandleLocked.java new file mode 100644 index 00000000..ccc52533 --- /dev/null +++ b/src/core/lombok/eclipse/handlers/HandleLocked.java @@ -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 { + 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 annotation, Annotation source, EclipseNode annotationNode) { + String annotationValue = annotation.getInstance().value(); + HandleLockedUtil.handle(annotationValue, source, annotationNode, ANNOTATION_NAME, LOCK_CLASS); + } + + @Override public void preHandle(AnnotationValues annotation, Annotation source, EclipseNode annotationNode) { + String annotationValue = annotation.getInstance().value(); + HandleLockedUtil.preHandle(annotationValue, LOCK_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..5cbf1c44 --- /dev/null +++ b/src/core/lombok/eclipse/handlers/HandleLockedRead.java @@ -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 { + 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 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 annotation, Annotation source, EclipseNode annotationNode) { + String annotationValue = annotation.getInstance().value(); + HandleLockedUtil.preHandle(annotationValue, LOCK_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..a016b280 --- /dev/null +++ b/src/core/lombok/eclipse/handlers/HandleLockedUtil.java @@ -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; + } +} diff --git a/src/core/lombok/eclipse/handlers/HandleLockedWrite.java b/src/core/lombok/eclipse/handlers/HandleLockedWrite.java new file mode 100644 index 00000000..b2ee2d56 --- /dev/null +++ b/src/core/lombok/eclipse/handlers/HandleLockedWrite.java @@ -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 { + 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 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 annotation, Annotation source, EclipseNode annotationNode) { + String annotationValue = annotation.getInstance().value(); + HandleLockedUtil.preHandle(annotationValue, LOCK_CLASS, annotationNode); + } +} From cb25fce7aaa3013dfffd760f9904d990469b2dcb Mon Sep 17 00:00:00 2001 From: Pim van der Loos Date: Mon, 31 May 2021 13:31:36 +0200 Subject: [PATCH 3/9] Add tests for Locked annotations --- .../after-delombok/LockedInRecord.java | 6 + .../resource/after-delombok/LockedName.java | 31 +++++ .../after-delombok/LockedOnStatic.java | 24 ++++ .../resource/after-delombok/LockedPlain.java | 80 ++++++++++++ .../after-delombok/LockedStaticMix.java | 34 +++++ .../after-delombok/LockedTypeMismatch.java | 34 +++++ .../resource/after-ecj/LockedInRecord.java | 14 +++ .../resource/after-ecj/LockedName.java | 40 ++++++ .../resource/after-ecj/LockedOnStatic.java | 41 ++++++ .../resource/after-ecj/LockedPlain.java | 117 ++++++++++++++++++ .../resource/after-ecj/LockedStaticMix.java | 52 ++++++++ .../after-ecj/LockedTypeMismatch.java | 48 +++++++ .../resource/before/LockedInRecord.java | 10 ++ .../transform/resource/before/LockedName.java | 11 ++ .../resource/before/LockedOnStatic.java | 16 +++ .../resource/before/LockedPlain.java | 33 +++++ .../resource/before/LockedStaticMix.java | 17 +++ .../resource/before/LockedTypeMismatch.java | 17 +++ .../LockedInRecord.java.messages | 1 + .../LockedStaticMix.java.messages | 1 + .../LockedTypeMismatch.java.messages | 1 + .../messages-ecj/LockedInRecord.java.messages | 1 + .../LockedStaticMix.java.messages | 1 + .../LockedTypeMismatch.java.messages | 2 + .../LockedTypeMismatch.java.messages | 2 + 25 files changed, 634 insertions(+) create mode 100644 test/transform/resource/after-delombok/LockedInRecord.java create mode 100644 test/transform/resource/after-delombok/LockedName.java create mode 100644 test/transform/resource/after-delombok/LockedOnStatic.java create mode 100644 test/transform/resource/after-delombok/LockedPlain.java create mode 100644 test/transform/resource/after-delombok/LockedStaticMix.java create mode 100644 test/transform/resource/after-delombok/LockedTypeMismatch.java create mode 100644 test/transform/resource/after-ecj/LockedInRecord.java create mode 100644 test/transform/resource/after-ecj/LockedName.java create mode 100644 test/transform/resource/after-ecj/LockedOnStatic.java create mode 100644 test/transform/resource/after-ecj/LockedPlain.java create mode 100644 test/transform/resource/after-ecj/LockedStaticMix.java create mode 100644 test/transform/resource/after-ecj/LockedTypeMismatch.java create mode 100644 test/transform/resource/before/LockedInRecord.java create mode 100644 test/transform/resource/before/LockedName.java create mode 100644 test/transform/resource/before/LockedOnStatic.java create mode 100644 test/transform/resource/before/LockedPlain.java create mode 100644 test/transform/resource/before/LockedStaticMix.java create mode 100644 test/transform/resource/before/LockedTypeMismatch.java create mode 100644 test/transform/resource/messages-delombok/LockedInRecord.java.messages create mode 100644 test/transform/resource/messages-delombok/LockedStaticMix.java.messages create mode 100644 test/transform/resource/messages-delombok/LockedTypeMismatch.java.messages create mode 100644 test/transform/resource/messages-ecj/LockedInRecord.java.messages create mode 100644 test/transform/resource/messages-ecj/LockedStaticMix.java.messages create mode 100644 test/transform/resource/messages-ecj/LockedTypeMismatch.java.messages create mode 100644 test/transform/resource/messages-idempotent/LockedTypeMismatch.java.messages 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..1c6e1083 --- /dev/null +++ b/test/transform/resource/after-delombok/LockedName.java @@ -0,0 +1,31 @@ +class LockedName { + @java.lang.SuppressWarnings("all") + private final java.util.concurrent.locks.ReentrantLock basicLock = new java.util.concurrent.locks.ReentrantLock(); + @java.lang.SuppressWarnings("all") + private final java.util.concurrent.locks.ReentrantReadWriteLock rwLock = new java.util.concurrent.locks.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..1280b858 --- /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.ReentrantLock 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..302b967b --- /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.ReentrantLock 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.ReentrantLock 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.ReentrantReadWriteLock 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.ReentrantReadWriteLock 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..201c9d7e --- /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.ReentrantLock 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.ReentrantLock 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..bbd916f4 --- /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.ReentrantLock 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.ReentrantLock 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..54a5ec0d --- /dev/null +++ b/test/transform/resource/after-ecj/LockedInRecord.java @@ -0,0 +1,14 @@ +import lombok.experimental.Locked; +public record LockedInRecord(java lock, String a) { + private final java.util.concurrent.locks.ReentrantLock lock = new java.util.concurrent.locks.ReentrantLock(); +/* Implicit */ private final String a; +/* Implicit */ private final String b; + 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..3fb996de --- /dev/null +++ b/test/transform/resource/after-ecj/LockedName.java @@ -0,0 +1,40 @@ +class LockedName { + private final java.util.concurrent.locks.ReentrantLock basicLock = new java.util.concurrent.locks.ReentrantLock(); + private final java.util.concurrent.locks.ReentrantReadWriteLock rwLock = new java.util.concurrent.locks.ReentrantReadWriteLock(); + LockedName() { + super(); + } + @lombok.experimental.Locked("basicLock") void test() { + this.basicLock.lock(); + try + { + System.out.println("one"); + } + finally + { + this.basicLock.unlock(); + } + } + @lombok.experimental.Locked.Read("rwLock") void test2() { + this.rwLock.readLock().lock(); + try + { + System.out.println("two"); + } + finally + { + this.rwLock.readLock().unlock(); + } + } + @lombok.experimental.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..02d00721 --- /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.ReentrantLock LCK = new java.util.concurrent.locks.ReentrantLock(); + () { + } + Inner() { + super(); + } + public @lombok.experimental.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.experimental.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..b0b91719 --- /dev/null +++ b/test/transform/resource/after-ecj/LockedPlain.java @@ -0,0 +1,117 @@ +import lombok.experimental.Locked; +class LockedPlain { + private final java.util.concurrent.locks.ReentrantLock lock = new java.util.concurrent.locks.ReentrantLock(); + LockedPlain() { + super(); + } + @lombok.experimental.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.ReentrantLock LOCK = new java.util.concurrent.locks.ReentrantLock(); + () { + } + LockedPlainStatic() { + super(); + } + static @lombok.experimental.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.ReentrantReadWriteLock LOCK = new java.util.concurrent.locks.ReentrantReadWriteLock(); + () { + } + LockedPlainRead() { + super(); + } + static @lombok.experimental.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.ReentrantReadWriteLock lock = new java.util.concurrent.locks.ReentrantReadWriteLock(); + LockedPlainWrite() { + super(); + } + @lombok.experimental.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..3ec8ca2d --- /dev/null +++ b/test/transform/resource/after-ecj/LockedStaticMix.java @@ -0,0 +1,52 @@ +class LockedGeneratedStaticMismatch { + private static final java.util.concurrent.locks.ReentrantLock LOCK = new java.util.concurrent.locks.ReentrantLock(); + () { + } + LockedGeneratedStaticMismatch() { + super(); + } + static @lombok.experimental.Locked void test() { + LockedGeneratedStaticMismatch.LOCK.lock(); + try + { + System.out.println("one"); + } + finally + { + LockedGeneratedStaticMismatch.LOCK.unlock(); + } + } + @lombok.experimental.Locked("LOCK") void test2() { + System.out.println("two"); + } +} +class LockedUserStaticMismatch { + private static final java.util.concurrent.locks.ReentrantLock userLock = new java.util.concurrent.locks.ReentrantLock(); + () { + } + LockedUserStaticMismatch() { + super(); + } + static @lombok.experimental.Locked("userLock") void test() { + LockedUserStaticMismatch.userLock.lock(); + try + { + System.out.println("one"); + } + finally + { + LockedUserStaticMismatch.userLock.unlock(); + } + } + @lombok.experimental.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..57e3f44a --- /dev/null +++ b/test/transform/resource/after-ecj/LockedTypeMismatch.java @@ -0,0 +1,48 @@ +class LockedGeneratedTypeMismatch { + private final java.util.concurrent.locks.ReentrantLock lock = new java.util.concurrent.locks.ReentrantLock(); + LockedGeneratedTypeMismatch() { + super(); + } + @lombok.experimental.Locked void test() { + this.lock.lock(); + try + { + System.out.println("one"); + } + finally + { + this.lock.unlock(); + } + } + @lombok.experimental.Locked.Read void test2() { + System.out.println("two"); + } +} +class LockedUserTypeMismatch { + private final java.util.concurrent.locks.ReentrantLock userLock = new java.util.concurrent.locks.ReentrantLock(); + LockedUserTypeMismatch() { + super(); + } + @lombok.experimental.Locked("userLock") void test() { + this.userLock.lock(); + try + { + System.out.println("one"); + } + finally + { + this.userLock.unlock(); + } + } + @lombok.experimental.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..9bb16001 --- /dev/null +++ b/test/transform/resource/before/LockedInRecord.java @@ -0,0 +1,10 @@ +// version 14: + +import lombok.experimental.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..3a73e5a3 --- /dev/null +++ b/test/transform/resource/before/LockedName.java @@ -0,0 +1,11 @@ +class LockedName { + @lombok.experimental.Locked("basicLock") void test() { + System.out.println("one"); + } + @lombok.experimental.Locked.Read("rwLock") void test2() { + System.out.println("two"); + } + @lombok.experimental.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..4da5af4c --- /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.ReentrantLock LCK = new java.util.concurrent.locks.ReentrantLock(); + @lombok.experimental.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.experimental.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..7bcba019 --- /dev/null +++ b/test/transform/resource/before/LockedPlain.java @@ -0,0 +1,33 @@ +import lombok.experimental.Locked; +class LockedPlain { + @lombok.experimental.Locked void test() { + System.out.println("one"); + } + @Locked void test2() { + System.out.println("two"); + } +} +class LockedPlainStatic { + @lombok.experimental.Locked static void test() { + System.out.println("three"); + } + @Locked static void test2() { + System.out.println("four"); + } +} +class LockedPlainRead { + @lombok.experimental.Locked.Read static void test() { + System.out.println("five"); + } + @Locked.Read static void test2() { + System.out.println("six"); + } +} +class LockedPlainWrite { + @lombok.experimental.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..9506ac04 --- /dev/null +++ b/test/transform/resource/before/LockedStaticMix.java @@ -0,0 +1,17 @@ +class LockedGeneratedStaticMismatch { + @lombok.experimental.Locked static void test() { + System.out.println("one"); + } + @lombok.experimental.Locked("LOCK") void test2() { + System.out.println("two"); + } +} +class LockedUserStaticMismatch { + private static final java.util.concurrent.locks.ReentrantLock userLock = new java.util.concurrent.locks.ReentrantLock(); + @lombok.experimental.Locked("userLock") static void test() { + System.out.println("one"); + } + @lombok.experimental.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..67e96b56 --- /dev/null +++ b/test/transform/resource/before/LockedTypeMismatch.java @@ -0,0 +1,17 @@ +class LockedGeneratedTypeMismatch { + @lombok.experimental.Locked void test() { + System.out.println("one"); + } + @lombok.experimental.Locked.Read void test2() { + System.out.println("two"); + } +} +class LockedUserTypeMismatch { + private final java.util.concurrent.locks.ReentrantLock userLock = new java.util.concurrent.locks.ReentrantLock(); + @lombok.experimental.Locked("userLock") void test() { + System.out.println("one"); + } + @lombok.experimental.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..ad329880 --- /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.ReentrantReadWriteLock but got type java.util.concurrent.locks.ReentrantLock! 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..ac159d72 --- /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.ReentrantReadWriteLock but got type java.util.concurrent.locks.ReentrantLock! Did you mix @Locked with @Locked.Read/Write on the same generated field? +14 The method readLock() is undefined for the type ReentrantLock 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 From 0129a0d18b1bbc126e1125c9053cfe9741c0ea5c Mon Sep 17 00:00:00 2001 From: Pim van der Loos Date: Mon, 31 May 2021 13:32:05 +0200 Subject: [PATCH 4/9] Add website documentation for Locked annotations --- .../features/experimental/Locked.html | 45 +++++++++++++++++++ .../features/experimental/index.html | 4 ++ .../experimental/LockedExample_post.jpage | 32 +++++++++++++ .../experimental/LockedExample_pre.jpage | 20 +++++++++ 4 files changed, 101 insertions(+) create mode 100644 website/templates/features/experimental/Locked.html create mode 100644 website/usageExamples/experimental/LockedExample_post.jpage create mode 100644 website/usageExamples/experimental/LockedExample_pre.jpage diff --git a/website/templates/features/experimental/Locked.html b/website/templates/features/experimental/Locked.html new file mode 100644 index 00000000..cd4f599c --- /dev/null +++ b/website/templates/features/experimental/Locked.html @@ -0,0 +1,45 @@ +<#import "../_features.html" as f> + +<@f.scaffold title="@Locked" logline="TODO"> + <@f.history> +

+ @Locked was introduced as an experimental feature in lombok v1.18.21. +

+ + + <@f.experimental> +
    +
  • + New feature – community feedback requested. +
  • +
+ Current status: unknown + + + <@f.overview> +

+ @Locked is an alternative to @Synchronized that uses Java locks for more extensive locking operations. The annotation locks on the provided field if one is provided. If no name is provided, the annotation will default to LOCK when used on static methods and to lock for non-static methods. If no field with the specified name exists, one will be generated for you. +

+ The base @Locked annotation uses a java.util.concurrent.locks.Lock for locking. When required, a java.util.concurrent.locks.ReentrantLock is generated.
+ Additionally, there are the @Locked.Read and @Locked.Write annotations. These use a java.util.concurrent.locks.ReadWriteLock. 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. +

+ + + <@f.snippets name="experimental/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.
+ 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/experimental/index.html b/website/templates/features/experimental/index.html index a50a4670..12c404bb 100644 --- a/website/templates/features/experimental/index.html +++ b/website/templates/features/experimental/index.html @@ -75,6 +75,10 @@ <@main.feature title="@StandardException" href="StandardException"> Standard.. Exceptional? This is not just an oxymoron, it's convenient! + + <@main.feature title="@Locked" href="Locked"> + TODO + <@f.confKeys> diff --git a/website/usageExamples/experimental/LockedExample_post.jpage b/website/usageExamples/experimental/LockedExample_post.jpage new file mode 100644 index 00000000..86bce0e9 --- /dev/null +++ b/website/usageExamples/experimental/LockedExample_post.jpage @@ -0,0 +1,32 @@ +public class LockedExample { + private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + private final ReentrantLock 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..fdfc7446 --- /dev/null +++ b/website/usageExamples/experimental/LockedExample_pre.jpage @@ -0,0 +1,20 @@ +import lombok.experimental.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"); + } +} From dfddf8f52c60551588aa4dde5a77df60ffb578a9 Mon Sep 17 00:00:00 2001 From: Pim van der Loos Date: Mon, 31 May 2021 18:31:52 +0200 Subject: [PATCH 5/9] Update style to better resemble rest of the project --- .../lombok/eclipse/handlers/HandleLocked.java | 3 +- .../eclipse/handlers/HandleLockedRead.java | 3 +- .../eclipse/handlers/HandleLockedUtil.java | 35 +++++--------- .../eclipse/handlers/HandleLockedWrite.java | 3 +- src/core/lombok/experimental/Locked.java | 2 +- .../javac/handlers/HandleLockedRead.java | 6 +-- .../javac/handlers/HandleLockedUtil.java | 48 +++++++------------ .../javac/handlers/HandleLockedWrite.java | 6 +-- 8 files changed, 35 insertions(+), 71 deletions(-) diff --git a/src/core/lombok/eclipse/handlers/HandleLocked.java b/src/core/lombok/eclipse/handlers/HandleLocked.java index ccc52533..5c3049d2 100644 --- a/src/core/lombok/eclipse/handlers/HandleLocked.java +++ b/src/core/lombok/eclipse/handlers/HandleLocked.java @@ -40,8 +40,7 @@ import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; public class HandleLocked extends EclipseAnnotationHandler { 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() }; + "concurrent".toCharArray(), "locks".toCharArray(), "ReentrantLock".toCharArray() }; @Override public void handle(AnnotationValues annotation, Annotation source, EclipseNode annotationNode) { String annotationValue = annotation.getInstance().value(); diff --git a/src/core/lombok/eclipse/handlers/HandleLockedRead.java b/src/core/lombok/eclipse/handlers/HandleLockedRead.java index 5cbf1c44..623a0a1c 100644 --- a/src/core/lombok/eclipse/handlers/HandleLockedRead.java +++ b/src/core/lombok/eclipse/handlers/HandleLockedRead.java @@ -41,8 +41,7 @@ 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_CLASS = new char[][] { TypeConstants.JAVA, TypeConstants.UTIL, - "concurrent".toCharArray(), "locks".toCharArray(), - "ReentrantReadWriteLock".toCharArray() }; + "concurrent".toCharArray(), "locks".toCharArray(), "ReentrantReadWriteLock".toCharArray() }; @Override public void handle(AnnotationValues annotation, Annotation source, EclipseNode annotationNode) { String annotationValue = annotation.getInstance().value(); diff --git a/src/core/lombok/eclipse/handlers/HandleLockedUtil.java b/src/core/lombok/eclipse/handlers/HandleLockedUtil.java index a016b280..15d06e57 100644 --- a/src/core/lombok/eclipse/handlers/HandleLockedUtil.java +++ b/src/core/lombok/eclipse/handlers/HandleLockedUtil.java @@ -80,8 +80,7 @@ public final class HandleLockedUtil { createLockField(annotationValue, annotationNode, lockClass, new boolean[] { method.isStatic() }, false); } - private static char[] createLockField(String name, EclipseNode annotationNode, char[][] lockClass, - boolean[] isStatic, boolean reportErrors) { + private static char[] createLockField(String name, EclipseNode annotationNode, char[][] lockClass, boolean[] isStatic, boolean reportErrors) { char[] lockName = name.toCharArray(); Annotation source = (Annotation) annotationNode.get(); @@ -109,9 +108,8 @@ public final class HandleLockedUtil { 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?"); + 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; @@ -136,11 +134,10 @@ public final class HandleLockedUtil { } /** - * See {@link #handle(String, Annotation, EclipseNode, String, char[][], char[])} for + * 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) { + public static void handle(String annotationValue, Annotation ast, EclipseNode annotationNode, String annotationName, char[][] lockClass) { handle(annotationValue, ast, annotationNode, annotationName, lockClass, null); } @@ -163,7 +160,7 @@ public final class HandleLockedUtil { * itself can be locked/unlocked. */ public static void handle(String annotationValue, Annotation source, EclipseNode annotationNode, - String annotationName, char[][] lockClass, char[] lockableMethodName) { + String annotationName, char[][] lockClass, char[] lockableMethodName) { handleFlagUsage(annotationNode, ConfigurationKeys.LOCKED_FLAG_USAGE, annotationName); int p1 = source.sourceStart -1; @@ -171,8 +168,7 @@ public final class HandleLockedUtil { long pos = (((long) p1) << 32) | p2; EclipseNode methodNode = annotationNode.up(); - if (methodNode == null || methodNode.getKind() != AST.Kind.METHOD || - !(methodNode.get() instanceof MethodDeclaration)) { + if (methodNode == null || methodNode.getKind() != AST.Kind.METHOD || !(methodNode.get() instanceof MethodDeclaration)) { annotationNode.addError(annotationName + " is legal only on methods."); return; } @@ -202,10 +198,8 @@ public final class HandleLockedUtil { 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); + 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; @@ -222,8 +216,7 @@ public final class HandleLockedUtil { } private static Statement getLockingStatement(ASTNode source, EclipseNode typeNode, char[] lockMethod, - char[] lockableObjectName, char[] lockableMethodName, boolean isStatic, - int p1, int p2, long pos) { + 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; @@ -234,7 +227,7 @@ public final class HandleLockedUtil { } private static Expression getLockable(ASTNode source, EclipseNode typeNode, char[] lockName, - char[] lockableMethodName, boolean isStatic, int p1, int p2, long pos) { + char[] lockableMethodName, boolean isStatic, int p1, int p2, long pos) { Reference lockVariable; if (isStatic) { char[][] n = getQualifiedInnerName(typeNode, lockName); @@ -261,10 +254,4 @@ public final class HandleLockedUtil { } 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; - } } diff --git a/src/core/lombok/eclipse/handlers/HandleLockedWrite.java b/src/core/lombok/eclipse/handlers/HandleLockedWrite.java index b2ee2d56..9aecfbf3 100644 --- a/src/core/lombok/eclipse/handlers/HandleLockedWrite.java +++ b/src/core/lombok/eclipse/handlers/HandleLockedWrite.java @@ -41,8 +41,7 @@ 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_CLASS = new char[][] { TypeConstants.JAVA, TypeConstants.UTIL, - "concurrent".toCharArray(), "locks".toCharArray(), - "ReentrantReadWriteLock".toCharArray() }; + "concurrent".toCharArray(), "locks".toCharArray(), "ReentrantReadWriteLock".toCharArray() }; @Override public void handle(AnnotationValues annotation, Annotation source, EclipseNode annotationNode) { String annotationValue = annotation.getInstance().value(); diff --git a/src/core/lombok/experimental/Locked.java b/src/core/lombok/experimental/Locked.java index 61e59ee4..164a5c6c 100644 --- a/src/core/lombok/experimental/Locked.java +++ b/src/core/lombok/experimental/Locked.java @@ -17,7 +17,7 @@ import java.util.concurrent.locks.ReadWriteLock; * 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. + * Complete documentation is found at the project lombok features page for @Locked. */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) diff --git a/src/core/lombok/javac/handlers/HandleLockedRead.java b/src/core/lombok/javac/handlers/HandleLockedRead.java index 30be1cc3..ab22082c 100644 --- a/src/core/lombok/javac/handlers/HandleLockedRead.java +++ b/src/core/lombok/javac/handlers/HandleLockedRead.java @@ -38,12 +38,10 @@ import static com.sun.tools.javac.tree.JCTree.*; 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_CLASS = new String[]{"java", "util", "concurrent", "locks", - "ReentrantReadWriteLock"}; + private static final String[] LOCK_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_CLASS, LOCK_METHOD); + HandleLockedUtil.handle(annotationValue, ast, annotationNode, Locked.Read.class, ANNOTATION_NAME, LOCK_CLASS, LOCK_METHOD); } } diff --git a/src/core/lombok/javac/handlers/HandleLockedUtil.java b/src/core/lombok/javac/handlers/HandleLockedUtil.java index 9e51eeba..6675aa30 100644 --- a/src/core/lombok/javac/handlers/HandleLockedUtil.java +++ b/src/core/lombok/javac/handlers/HandleLockedUtil.java @@ -53,8 +53,7 @@ public final class HandleLockedUtil { * {@code lockableMethodName = null}. */ public static void handle(String annotationValue, JCTree.JCAnnotation ast, - JavacNode annotationNode, Class annotationClass, - String annotationName, String[] lockClass) { + JavacNode annotationNode, Class annotationClass, String annotationName, String[] lockClass) { handle(annotationValue, ast, annotationNode, annotationClass, annotationName, lockClass, null); } @@ -81,18 +80,15 @@ public final class HandleLockedUtil { * 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[] lockClass, - String lockableMethodName) { + public static void handle(String annotationValue, JCTree.JCAnnotation ast, JavacNode annotationNode, + Class annotationClass, String annotationName, String[] lockClass, 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)) { + if (methodNode == null || methodNode.getKind() != AST.Kind.METHOD || !(methodNode.get() instanceof JCMethodDecl)) { annotationNode.addError(annotationName + " is legal only on methods."); return; } @@ -127,22 +123,18 @@ public final class HandleLockedUtil { 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; + exists = getGeneratedBy(varDeclDef) == null ? MemberExistsResult.EXISTS_BY_USER : MemberExistsResult.EXISTS_BY_LOMBOK; boolean st = ((varDeclDef.mods.flags) & Flags.STATIC) != 0; if (isStatic[0] != st && exists == MemberExistsResult.EXISTS_BY_LOMBOK) { - annotationNode.addError("The generated field " + lockName + - " does not match the static status of this method"); + annotationNode.addError("The generated field " + lockName + " does not match the static status of this method"); return; } isStatic[0] = st; - if (exists == MemberExistsResult.EXISTS_BY_LOMBOK && - !lockType.toString().equals(varDeclDef.vartype.toString())) { - annotationNode.addError("Expected field " + lockName + " to be of type " + lockType + - " but got type " + varDeclDef.vartype + "!" + - " Did you mix @Locked with @Locked.Read/Write on the same generated field?"); + if (exists == MemberExistsResult.EXISTS_BY_LOMBOK && !lockType.toString().equals(varDeclDef.vartype.toString())) { + annotationNode.addError("Expected field " + lockName + " to be of type " + lockType + " but got type " + + varDeclDef.vartype + "! Did you mix @Locked with @Locked.Read/Write on the same generated field?"); return; } } @@ -164,8 +156,7 @@ public final class HandleLockedUtil { JCExpression lockNode; if (isStatic[0]) { - lockNode = namePlusTypeParamsToTypeReference(maker, typeNode, methodNode.toName(lockName), false, - List.nil()); + lockNode = namePlusTypeParamsToTypeReference(maker, typeNode, methodNode.toName(lockName), false, List.nil()); } else { lockNode = maker.Select(maker.Ident(methodNode.toName("this")), methodNode.toName(lockName)); } @@ -176,23 +167,16 @@ public final class HandleLockedUtil { if (lockableMethodName == null) { lockable = lockNode; } else { - lockable = maker.Apply(NIL_EXPRESSION, - maker.Select(lockNode, methodNode.toName(lockableMethodName)), - NIL_EXPRESSION); + lockable = maker.Apply(NIL_EXPRESSION, maker.Select(lockNode, methodNode.toName(lockableMethodName)), NIL_EXPRESSION); } - JCExpressionStatement acquireLock = - maker.Exec(maker.Apply(NIL_EXPRESSION, maker.Select(lockable, typeNode.toName("lock")), NIL_EXPRESSION)); - JCExpressionStatement releaseLock = - maker.Exec(maker.Apply(NIL_EXPRESSION, maker.Select(lockable, typeNode.toName("unlock")), NIL_EXPRESSION)); + JCExpressionStatement acquireLock = maker.Exec(maker.Apply(NIL_EXPRESSION, maker.Select(lockable, typeNode.toName("lock")), NIL_EXPRESSION)); + JCExpressionStatement releaseLock = maker.Exec(maker.Apply(NIL_EXPRESSION, maker.Select(lockable, typeNode.toName("unlock")), NIL_EXPRESSION)); - JCTry tryBlock = recursiveSetGeneratedBy(maker.Try(maker.Block(0L, method.body.stats), - List.nil(), - maker.Block(0L, List.of(releaseLock))), - annotationNode); + JCTry tryBlock = recursiveSetGeneratedBy(maker.Try(maker.Block(0L, method.body.stats), List.nil(), + maker.Block(0L, List.of(releaseLock))), annotationNode); - method.body.stats = List.of(tryBlock) - .prepend(recursiveSetGeneratedBy(acquireLock, annotationNode)); + method.body.stats = List.of(tryBlock).prepend(recursiveSetGeneratedBy(acquireLock, annotationNode)); methodNode.rebuild(); } } diff --git a/src/core/lombok/javac/handlers/HandleLockedWrite.java b/src/core/lombok/javac/handlers/HandleLockedWrite.java index cbcb2f32..0a8283be 100644 --- a/src/core/lombok/javac/handlers/HandleLockedWrite.java +++ b/src/core/lombok/javac/handlers/HandleLockedWrite.java @@ -38,12 +38,10 @@ import static com.sun.tools.javac.tree.JCTree.*; 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_CLASS = new String[]{"java", "util", "concurrent", "locks", - "ReentrantReadWriteLock"}; + private static final String[] LOCK_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_CLASS, LOCK_METHOD); + HandleLockedUtil.handle(annotationValue, ast, annotationNode, Locked.Write.class, ANNOTATION_NAME, LOCK_CLASS, LOCK_METHOD); } } From 58b38dcaa516435746a4513d76a32508e714c380 Mon Sep 17 00:00:00 2001 From: Pim van der Loos Date: Tue, 25 Jan 2022 14:24:18 +0100 Subject: [PATCH 6/9] Apply feedback - Applied the feedback received on the first version: + Using the handleExperimentalFlagUsage to reflect the fact this is an experimental feature. + Replace the 1-len boolean[] with 1) A regular boolean for javac (no need for mutability), and 2) An AtomicBoolean for ecj (more fitting for mutable boolean). + Replace the typeReferenceNameEquals method with a much cleaner Arrays.deepEquals call. --- .../eclipse/handlers/HandleLockedUtil.java | 36 +++++++------------ .../javac/handlers/HandleLockedUtil.java | 16 ++++----- 2 files changed, 21 insertions(+), 31 deletions(-) diff --git a/src/core/lombok/eclipse/handlers/HandleLockedUtil.java b/src/core/lombok/eclipse/handlers/HandleLockedUtil.java index 15d06e57..ceb05709 100644 --- a/src/core/lombok/eclipse/handlers/HandleLockedUtil.java +++ b/src/core/lombok/eclipse/handlers/HandleLockedUtil.java @@ -39,12 +39,12 @@ 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 java.util.concurrent.atomic.AtomicBoolean; -import static lombok.core.handlers.HandlerUtil.handleFlagUsage; +import static lombok.core.handlers.HandlerUtil.handleExperimentalFlagUsage; import static lombok.eclipse.handlers.EclipseHandlerUtil.upToTypeNode; import static lombok.eclipse.handlers.EclipseHandlerUtil.*; @@ -61,31 +61,21 @@ public final class 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); + createLockField(annotationValue, annotationNode, lockClass, new AtomicBoolean(method.isStatic()), false); } - private static char[] createLockField(String name, EclipseNode annotationNode, char[][] lockClass, boolean[] isStatic, boolean reportErrors) { + private static char[] createLockField(String name, EclipseNode annotationNode, char[][] lockClass, AtomicBoolean 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; + lockName = isStatic.get() ? STATIC_LOCK_NAME : INSTANCE_LOCK_NAME; } EclipseNode typeNode = upToTypeNode(annotationNode); @@ -101,13 +91,13 @@ public final class HandleLockedUtil { 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 (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[0] = st; + isStatic.set(st); - if (exists == MemberExistsResult.EXISTS_BY_LOMBOK && !typeReferenceNameEquals(lockType, def.type)) { + 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; @@ -121,7 +111,7 @@ public final class HandleLockedUtil { FieldDeclaration fieldDecl = setGeneratedBy(new FieldDeclaration(lockName, 0, -1), source); fieldDecl.declarationSourceEnd = -1; - fieldDecl.modifiers = (isStatic[0] ? Modifier.STATIC : 0) | Modifier.FINAL | Modifier.PRIVATE; + fieldDecl.modifiers = (isStatic.get() ? 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); @@ -161,7 +151,7 @@ public final class HandleLockedUtil { */ public static void handle(String annotationValue, Annotation source, EclipseNode annotationNode, String annotationName, char[][] lockClass, char[] lockableMethodName) { - handleFlagUsage(annotationNode, ConfigurationKeys.LOCKED_FLAG_USAGE, annotationName); + handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.LOCKED_FLAG_USAGE, annotationName); int p1 = source.sourceStart -1; int p2 = source.sourceStart -2; @@ -185,7 +175,7 @@ public final class HandleLockedUtil { return; } - boolean[] isStatic = { method.isStatic() }; + AtomicBoolean isStatic = new AtomicBoolean(method.isStatic()); char[] lockName = createLockField(annotationValue, annotationNode, lockClass, isStatic, true); if (lockName == null) return; if (method.statements == null) return; @@ -198,8 +188,8 @@ public final class HandleLockedUtil { 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); + 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; diff --git a/src/core/lombok/javac/handlers/HandleLockedUtil.java b/src/core/lombok/javac/handlers/HandleLockedUtil.java index 6675aa30..a001e984 100644 --- a/src/core/lombok/javac/handlers/HandleLockedUtil.java +++ b/src/core/lombok/javac/handlers/HandleLockedUtil.java @@ -33,7 +33,7 @@ import lombok.javac.JavacTreeMaker; import java.lang.annotation.Annotation; import static com.sun.tools.javac.tree.JCTree.*; -import static lombok.core.handlers.HandlerUtil.handleFlagUsage; +import static lombok.core.handlers.HandlerUtil.handleExperimentalFlagUsage; import static lombok.javac.handlers.JavacHandlerUtil.*; /** @@ -82,7 +82,7 @@ public final class HandleLockedUtil { */ public static void handle(String annotationValue, JCTree.JCAnnotation ast, JavacNode annotationNode, Class annotationClass, String annotationName, String[] lockClass, String lockableMethodName) { - handleFlagUsage(annotationNode, ConfigurationKeys.LOCKED_FLAG_USAGE, annotationName); + handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.LOCKED_FLAG_USAGE, annotationName); if (inNetbeansEditor(annotationNode)) return; deleteAnnotationIfNeccessary(annotationNode, annotationClass); @@ -105,11 +105,11 @@ public final class HandleLockedUtil { return; } - boolean[] isStatic = new boolean[]{(method.mods.flags & Flags.STATIC) != 0}; + boolean isStatic = (method.mods.flags & Flags.STATIC) != 0; String lockName = annotationValue; if (lockName.length() == 0) { - lockName = isStatic[0] ? STATIC_LOCK_NAME : INSTANCE_LOCK_NAME; + lockName = isStatic ? STATIC_LOCK_NAME : INSTANCE_LOCK_NAME; } JavacTreeMaker maker = methodNode.getTreeMaker().at(ast.pos); @@ -126,11 +126,11 @@ public final class HandleLockedUtil { exists = getGeneratedBy(varDeclDef) == null ? MemberExistsResult.EXISTS_BY_USER : MemberExistsResult.EXISTS_BY_LOMBOK; boolean st = ((varDeclDef.mods.flags) & Flags.STATIC) != 0; - if (isStatic[0] != st && exists == MemberExistsResult.EXISTS_BY_LOMBOK) { + 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[0] = st; + isStatic = st; if (exists == MemberExistsResult.EXISTS_BY_LOMBOK && !lockType.toString().equals(varDeclDef.vartype.toString())) { annotationNode.addError("Expected field " + lockName + " to be of type " + lockType + " but got type " + @@ -145,7 +145,7 @@ public final class HandleLockedUtil { if (exists == MemberExistsResult.NOT_EXISTS) { JCNewClass lockInstance = maker.NewClass(null, NIL_EXPRESSION, lockType, NIL_EXPRESSION, null); JCVariableDecl newLockField = 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), lockType, lockInstance ), annotationNode); @@ -155,7 +155,7 @@ public final class HandleLockedUtil { if (method.body == null || typeNode == 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)); From d9740f5bbbce76b5e5db887e5bac2958be4b515e Mon Sep 17 00:00:00 2001 From: Pim van der Loos Date: Thu, 12 Jan 2023 22:24:41 +0100 Subject: [PATCH 7/9] Fix incorrect javac recursiveSetGeneratedBy usage - The javac handler for the Locked annotations applied recursiveSetGeneratedBy to too many things, which could cause issues in certain situations. Reducing the scope on which it is used and using setGeneratedBy for the rest resolved the problems. --- src/core/lombok/javac/handlers/HandleLockedUtil.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/core/lombok/javac/handlers/HandleLockedUtil.java b/src/core/lombok/javac/handlers/HandleLockedUtil.java index a001e984..f5e60eba 100644 --- a/src/core/lombok/javac/handlers/HandleLockedUtil.java +++ b/src/core/lombok/javac/handlers/HandleLockedUtil.java @@ -160,7 +160,6 @@ public final class HandleLockedUtil { } else { lockNode = maker.Select(maker.Ident(methodNode.toName("this")), methodNode.toName(lockName)); } - recursiveSetGeneratedBy(lockNode, annotationNode); final JCExpression lockable; @@ -168,15 +167,17 @@ public final class HandleLockedUtil { lockable = lockNode; } else { lockable = maker.Apply(NIL_EXPRESSION, maker.Select(lockNode, methodNode.toName(lockableMethodName)), NIL_EXPRESSION); + recursiveSetGeneratedBy(lockable, annotationNode); } JCExpressionStatement acquireLock = maker.Exec(maker.Apply(NIL_EXPRESSION, maker.Select(lockable, typeNode.toName("lock")), NIL_EXPRESSION)); JCExpressionStatement releaseLock = maker.Exec(maker.Apply(NIL_EXPRESSION, maker.Select(lockable, typeNode.toName("unlock")), NIL_EXPRESSION)); - JCTry tryBlock = recursiveSetGeneratedBy(maker.Try(maker.Block(0L, method.body.stats), List.nil(), - maker.Block(0L, List.of(releaseLock))), annotationNode); + JCBlock finallyBlock = setGeneratedBy(maker.Block(0, List.of(recursiveSetGeneratedBy(releaseLock, annotationNode))), annotationNode); + JCTry tryBlock = setGeneratedBy(maker.Try(method.body, List.nil(), finallyBlock), annotationNode); + + method.body = setGeneratedBy(maker.Block(0, List.of(recursiveSetGeneratedBy(acquireLock, annotationNode), tryBlock)), annotationNode); - method.body.stats = List.of(tryBlock).prepend(recursiveSetGeneratedBy(acquireLock, annotationNode)); methodNode.rebuild(); } } From 567b098e997010ba6adc4eea90c482311e273d03 Mon Sep 17 00:00:00 2001 From: Pim van der Loos Date: Sat, 15 Apr 2023 16:08:23 +0200 Subject: [PATCH 8/9] Fix tests --- .../javac/handlers/HandleLockedUtil.java | 43 ++++++++----------- .../resource/after-ecj/LockedInRecord.java | 4 +- 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/src/core/lombok/javac/handlers/HandleLockedUtil.java b/src/core/lombok/javac/handlers/HandleLockedUtil.java index f5e60eba..9f9deaa2 100644 --- a/src/core/lombok/javac/handlers/HandleLockedUtil.java +++ b/src/core/lombok/javac/handlers/HandleLockedUtil.java @@ -52,8 +52,7 @@ public final class HandleLockedUtil { * See {@link #handle(String, JCAnnotation, JavacNode, Class, String, String[], String)} for * {@code lockableMethodName = null}. */ - public static void handle(String annotationValue, JCTree.JCAnnotation ast, - JavacNode annotationNode, Class annotationClass, String annotationName, String[] lockClass) { + public static void handle(String annotationValue, JCTree.JCAnnotation ast, JavacNode annotationNode, Class annotationClass, String annotationName, String[] lockClass) { handle(annotationValue, ast, annotationNode, annotationClass, annotationName, lockClass, null); } @@ -80,8 +79,7 @@ public final class HandleLockedUtil { * 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[] lockClass, String lockableMethodName) { + public static void handle(String annotationValue, JCTree.JCAnnotation ast, JavacNode annotationNode, Class annotationClass, String annotationName, String[] lockClass, String lockableMethodName) { handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.LOCKED_FLAG_USAGE, annotationName); if (inNetbeansEditor(annotationNode)) return; @@ -116,7 +114,7 @@ public final class HandleLockedUtil { MemberExistsResult exists = MemberExistsResult.NOT_EXISTS; - JCExpression lockType = chainDots(methodNode, lockClass); + JCExpression lockType = chainDots(methodNode, ast.pos, null, null, lockClass); if (typeNode != null && typeNode.get() instanceof JCClassDecl) { for (JCTree def : ((JCClassDecl) typeNode.get()).defs) { @@ -133,8 +131,7 @@ public final class HandleLockedUtil { isStatic = st; if (exists == MemberExistsResult.EXISTS_BY_LOMBOK && !lockType.toString().equals(varDeclDef.vartype.toString())) { - annotationNode.addError("Expected field " + lockName + " to be of type " + lockType + " but got type " + - varDeclDef.vartype + "! Did you mix @Locked with @Locked.Read/Write on the same generated field?"); + annotationNode.addError("Expected field " + lockName + " to be of type " + lockType + " but got type " + varDeclDef.vartype + "! Did you mix @Locked with @Locked.Read/Write on the same generated field?"); return; } } @@ -143,16 +140,14 @@ public final class HandleLockedUtil { } if (exists == MemberExistsResult.NOT_EXISTS) { - JCNewClass lockInstance = maker.NewClass(null, NIL_EXPRESSION, lockType, NIL_EXPRESSION, null); + JCNewClass lockInstance = maker.NewClass(null, NIL_EXPRESSION, cloneType(maker, lockType, typeNode), NIL_EXPRESSION, null); JCVariableDecl newLockField = recursiveSetGeneratedBy(maker.VarDef( maker.Modifiers(Flags.PRIVATE | Flags.FINAL | (isStatic ? Flags.STATIC : 0)), - methodNode.toName(lockName), lockType, lockInstance - ), annotationNode); - + methodNode.toName(lockName), lockType, lockInstance), annotationNode); injectFieldAndMarkGenerated(methodNode.up(), newLockField); } - if (method.body == null || typeNode == null) return; + if (method.body == null) return; JCExpression lockNode; if (isStatic) { @@ -160,24 +155,22 @@ public final class HandleLockedUtil { } else { lockNode = maker.Select(maker.Ident(methodNode.toName("this")), methodNode.toName(lockName)); } - recursiveSetGeneratedBy(lockNode, annotationNode); - final JCExpression lockable; - if (lockableMethodName == null) { - lockable = lockNode; - } else { - lockable = maker.Apply(NIL_EXPRESSION, maker.Select(lockNode, methodNode.toName(lockableMethodName)), NIL_EXPRESSION); - recursiveSetGeneratedBy(lockable, annotationNode); - } + 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)); - JCExpressionStatement acquireLock = maker.Exec(maker.Apply(NIL_EXPRESSION, maker.Select(lockable, typeNode.toName("lock")), NIL_EXPRESSION)); - JCExpressionStatement releaseLock = maker.Exec(maker.Apply(NIL_EXPRESSION, maker.Select(lockable, typeNode.toName("unlock")), NIL_EXPRESSION)); - - JCBlock finallyBlock = setGeneratedBy(maker.Block(0, List.of(recursiveSetGeneratedBy(releaseLock, annotationNode))), annotationNode); - JCTry tryBlock = setGeneratedBy(maker.Try(method.body, List.nil(), finallyBlock), annotationNode); + 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/test/transform/resource/after-ecj/LockedInRecord.java b/test/transform/resource/after-ecj/LockedInRecord.java index 54a5ec0d..ebb660c4 100644 --- a/test/transform/resource/after-ecj/LockedInRecord.java +++ b/test/transform/resource/after-ecj/LockedInRecord.java @@ -1,8 +1,8 @@ import lombok.experimental.Locked; -public record LockedInRecord(java lock, String a) { - private final java.util.concurrent.locks.ReentrantLock lock = new java.util.concurrent.locks.ReentrantLock(); +public record LockedInRecord(String a, String b) { /* Implicit */ private final String a; /* Implicit */ private final String b; + private final java.util.concurrent.locks.ReentrantLock lock = new java.util.concurrent.locks.ReentrantLock(); public LockedInRecord(String a, String b) { super(); .a = a; From 566f01be47a3c67fb8b91c720180694d389d3bb5 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Mon, 25 Sep 2023 06:19:36 +0200 Subject: [PATCH 9/9] [review] [#3506] PR reviewed; passes review and passes tests. --- src/core/lombok/ConfigurationKeys.java | 10 +- src/core/lombok/Locked.java | 80 ++++++++++++++ src/core/lombok/Synchronized.java | 2 +- .../lombok/eclipse/handlers/HandleLocked.java | 18 +-- .../eclipse/handlers/HandleLockedRead.java | 18 +-- .../eclipse/handlers/HandleLockedUtil.java | 104 +++++++++--------- .../eclipse/handlers/HandleLockedWrite.java | 14 ++- src/core/lombok/experimental/Locked.java | 62 ----------- .../lombok/javac/handlers/HandleLocked.java | 11 +- .../javac/handlers/HandleLockedRead.java | 11 +- .../javac/handlers/HandleLockedUtil.java | 89 ++++++++------- .../javac/handlers/HandleLockedWrite.java | 11 +- .../javac/handlers/HandleSynchronized.java | 16 +-- .../resource/after-delombok/LockedName.java | 8 +- .../after-delombok/LockedOnStatic.java | 2 +- .../resource/after-delombok/LockedPlain.java | 40 +++---- .../after-delombok/LockedStaticMix.java | 8 +- .../after-delombok/LockedTypeMismatch.java | 8 +- .../resource/after-ecj/LockedInRecord.java | 4 +- .../resource/after-ecj/LockedName.java | 11 +- .../resource/after-ecj/LockedOnStatic.java | 6 +- .../resource/after-ecj/LockedPlain.java | 50 ++++----- .../resource/after-ecj/LockedStaticMix.java | 16 +-- .../after-ecj/LockedTypeMismatch.java | 16 +-- .../resource/before/LockedInRecord.java | 2 +- .../transform/resource/before/LockedName.java | 9 +- .../resource/before/LockedOnStatic.java | 6 +- .../resource/before/LockedPlain.java | 10 +- .../resource/before/LockedStaticMix.java | 10 +- .../resource/before/LockedTypeMismatch.java | 10 +- .../LockedTypeMismatch.java.messages | 2 +- .../LockedTypeMismatch.java.messages | 4 +- website/templates/features/Locked.html | 38 +++++++ .../features/experimental/Locked.html | 45 -------- .../features/experimental/index.html | 4 - website/templates/features/index.html | 4 + .../experimental/LockedExample_post.jpage | 10 +- .../experimental/LockedExample_pre.jpage | 8 +- 38 files changed, 406 insertions(+), 371 deletions(-) create mode 100644 src/core/lombok/Locked.java delete mode 100644 src/core/lombok/experimental/Locked.java create mode 100644 website/templates/features/Locked.html delete mode 100644 website/templates/features/experimental/Locked.html diff --git a/src/core/lombok/ConfigurationKeys.java b/src/core/lombok/ConfigurationKeys.java index 87f26122..8571ea39 100644 --- a/src/core/lombok/ConfigurationKeys.java +++ b/src/core/lombok/ConfigurationKeys.java @@ -391,7 +391,7 @@ public class ConfigurationKeys { * If set, any usage of {@code @Synchronized} results in a warning / error. */ public static final ConfigurationKey SYNCHRONIZED_FLAG_USAGE = new ConfigurationKey("lombok.synchronized.flagUsage", "Emit a warning or error if @Synchronized is used.") {}; - + // ----- val ----- /** @@ -623,18 +623,18 @@ public class ConfigurationKeys { * If set, any usage of {@code @Helper} results in a warning / error. */ 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 index 5c3049d2..240a6498 100644 --- a/src/core/lombok/eclipse/handlers/HandleLocked.java +++ b/src/core/lombok/eclipse/handlers/HandleLocked.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2021 The Project Lombok Authors. + * 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 @@ -26,7 +26,7 @@ import lombok.core.HandlerPriority; import lombok.eclipse.DeferUntilPostDiet; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; -import lombok.experimental.Locked; +import lombok.Locked; import lombok.spi.Provides; import org.eclipse.jdt.internal.compiler.ast.Annotation; import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; @@ -39,16 +39,18 @@ import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; @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_CLASS = new char[][] { TypeConstants.JAVA, TypeConstants.UTIL, - "concurrent".toCharArray(), "locks".toCharArray(), "ReentrantLock".toCharArray() }; - + 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_CLASS); + 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_CLASS, annotationNode); + 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 index 623a0a1c..a293467a 100644 --- a/src/core/lombok/eclipse/handlers/HandleLockedRead.java +++ b/src/core/lombok/eclipse/handlers/HandleLockedRead.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2021 The Project Lombok Authors. + * 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 @@ -26,7 +26,7 @@ import lombok.core.HandlerPriority; import lombok.eclipse.DeferUntilPostDiet; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; -import lombok.experimental.Locked; +import lombok.Locked; import lombok.spi.Provides; import org.eclipse.jdt.internal.compiler.ast.Annotation; import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; @@ -40,16 +40,18 @@ import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; 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_CLASS = new char[][] { TypeConstants.JAVA, TypeConstants.UTIL, - "concurrent".toCharArray(), "locks".toCharArray(), "ReentrantReadWriteLock".toCharArray() }; - + 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_CLASS, LOCK_METHOD); + 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_CLASS, annotationNode); + 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 index ceb05709..64fe3591 100644 --- a/src/core/lombok/eclipse/handlers/HandleLockedUtil.java +++ b/src/core/lombok/eclipse/handlers/HandleLockedUtil.java @@ -1,4 +1,4 @@ -/* Copyright (C) 2009-2021 The Project Lombok Authors. +/* 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 @@ -23,6 +23,7 @@ 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; @@ -44,45 +45,44 @@ import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.concurrent.atomic.AtomicBoolean; -import static lombok.core.handlers.HandlerUtil.handleExperimentalFlagUsage; -import static lombok.eclipse.handlers.EclipseHandlerUtil.upToTypeNode; +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[] 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[][] lockClass, EclipseNode annotationNode) { + + 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, lockClass, new AtomicBoolean(method.isStatic()), false); + + createLockField(annotationValue, annotationNode, lockTypeClass, lockImplClass, new AtomicBoolean(method.isStatic()), false); } - - private static char[] createLockField(String name, EclipseNode annotationNode, char[][] lockClass, AtomicBoolean isStatic, boolean reportErrors) { + + 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(lockClass, new long[] { 0, 0, 0, 0, 0 }); - + + 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) { @@ -96,41 +96,41 @@ public final class HandleLockedUtil { 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?"); + " 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(lockClass, new long[] { 0, 0, 0, 0, 0 }), source); - fieldDecl.type = setGeneratedBy(new QualifiedTypeReference(lockClass, new long[] { 0, 0, 0, 0, 0 }), 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[][] lockClass) { - handle(annotationValue, ast, annotationNode, annotationName, lockClass, 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. * @@ -144,69 +144,72 @@ public final class HandleLockedUtil { * 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 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[][] lockClass, char[] lockableMethodName) { - handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.LOCKED_FLAG_USAGE, annotationName); - + 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, lockClass, isStatic, true); + 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) { + 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; @@ -215,9 +218,10 @@ public final class HandleLockedUtil { 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) { + char[] lockableMethodName, boolean isStatic, int p1, int p2, long pos) { + Reference lockVariable; if (isStatic) { char[][] n = getQualifiedInnerName(typeNode, lockName); @@ -231,7 +235,7 @@ public final class HandleLockedUtil { ((FieldReference) lockVariable).receiver = thisReference; } setGeneratedBy(lockVariable, source); - + Expression lockable; if (lockableMethodName == null) lockable = lockVariable; else { diff --git a/src/core/lombok/eclipse/handlers/HandleLockedWrite.java b/src/core/lombok/eclipse/handlers/HandleLockedWrite.java index 9aecfbf3..6192c76b 100644 --- a/src/core/lombok/eclipse/handlers/HandleLockedWrite.java +++ b/src/core/lombok/eclipse/handlers/HandleLockedWrite.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2021 The Project Lombok Authors. + * 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 @@ -26,7 +26,7 @@ import lombok.core.HandlerPriority; import lombok.eclipse.DeferUntilPostDiet; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; -import lombok.experimental.Locked; +import lombok.Locked; import lombok.spi.Provides; import org.eclipse.jdt.internal.compiler.ast.Annotation; import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; @@ -40,16 +40,18 @@ import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; 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_CLASS = new char[][] { TypeConstants.JAVA, TypeConstants.UTIL, - "concurrent".toCharArray(), "locks".toCharArray(), "ReentrantReadWriteLock".toCharArray() }; + 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_CLASS, LOCK_METHOD); + 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_CLASS, annotationNode); + HandleLockedUtil.preHandle(annotationValue, LOCK_TYPE_CLASS, LOCK_IMPL_CLASS, annotationNode); } } diff --git a/src/core/lombok/experimental/Locked.java b/src/core/lombok/experimental/Locked.java deleted file mode 100644 index 164a5c6c..00000000 --- a/src/core/lombok/experimental/Locked.java +++ /dev/null @@ -1,62 +0,0 @@ -package lombok.experimental; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.util.concurrent.locks.ReadWriteLock; - -/** - * Uses a {@link java.util.concurrent.locks.Lock} as a more flexible alternative to the 'synchronized' keyword with - * optional increased throughput when making use of read and write locks. - *

- * 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. The contents - * of the fields will be serializable. - *

- * 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 ReadWriteLock#readLock()}. - */ - @Target(ElementType.METHOD) - @Retention(RetentionPolicy.SOURCE) - public @interface Read { - /** - * Optional: specify the name of a different field to lock on. If this field doesn't already exist, it is generated - * automatically. - * - * @return Name of the field to lock on (blank = generate one). - */ - String value() default ""; - } - - /** - * Locks using a {@link ReadWriteLock#writeLock()}. - */ - @Target(ElementType.METHOD) - @Retention(RetentionPolicy.SOURCE) - public @interface Write { - /** - * Optional: specify the name of a different field to lock on. If this field doesn't already exist, it is generated - * automatically. - * - * @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. If this field doesn't already exist, it is generated - * automatically. - * - * @return Name of the field to lock on (blank = generate one). - */ - String value() default ""; -} diff --git a/src/core/lombok/javac/handlers/HandleLocked.java b/src/core/lombok/javac/handlers/HandleLocked.java index 7eb1c5c9..538ec29b 100644 --- a/src/core/lombok/javac/handlers/HandleLocked.java +++ b/src/core/lombok/javac/handlers/HandleLocked.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2021 The Project Lombok Authors. + * 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 @@ -23,7 +23,7 @@ package lombok.javac.handlers; import lombok.core.AnnotationValues; import lombok.core.HandlerPriority; -import lombok.experimental.Locked; +import lombok.Locked; import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; import lombok.spi.Provides; @@ -37,10 +37,11 @@ import static com.sun.tools.javac.tree.JCTree.*; @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_CLASS = new String[]{"java", "util", "concurrent", "locks", "ReentrantLock"}; - + 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_CLASS); + 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 index ab22082c..be3a14a6 100644 --- a/src/core/lombok/javac/handlers/HandleLockedRead.java +++ b/src/core/lombok/javac/handlers/HandleLockedRead.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2021 The Project Lombok Authors. + * 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 @@ -23,7 +23,7 @@ package lombok.javac.handlers; import lombok.core.AnnotationValues; import lombok.core.HandlerPriority; -import lombok.experimental.Locked; +import lombok.Locked; import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; import lombok.spi.Provides; @@ -38,10 +38,11 @@ import static com.sun.tools.javac.tree.JCTree.*; 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_CLASS = new String[]{"java", "util", "concurrent", "locks", "ReentrantReadWriteLock"}; - + 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_CLASS, LOCK_METHOD); + 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 index 9f9deaa2..70dc9073 100644 --- a/src/core/lombok/javac/handlers/HandleLockedUtil.java +++ b/src/core/lombok/javac/handlers/HandleLockedUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2021 The Project Lombok Authors. + * 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 @@ -24,38 +24,38 @@ 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.ConfigurationKeys; import lombok.core.AST; -import lombok.experimental.Locked; +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.handleExperimentalFlagUsage; +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 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)} for + * 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[] lockClass) { - handle(annotationValue, ast, annotationNode, annotationClass, annotationName, lockClass, 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}. @@ -72,50 +72,54 @@ public final class HandleLockedUtil { * @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 lockClass The type of lock to use. + * @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[] lockClass, String lockableMethodName) { - handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.LOCKED_FLAG_USAGE, annotationName); - + 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 lockType = chainDots(methodNode, ast.pos, null, null, lockClass); - + + 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) { @@ -123,49 +127,54 @@ public final class HandleLockedUtil { 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 && !lockType.toString().equals(varDeclDef.vartype.toString())) { - annotationNode.addError("Expected field " + lockName + " to be of type " + lockType + " but got type " + varDeclDef.vartype + "! Did you mix @Locked with @Locked.Read/Write on the same generated field?"); + + 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) { - JCNewClass lockInstance = maker.NewClass(null, NIL_EXPRESSION, cloneType(maker, lockType, typeNode), NIL_EXPRESSION, null); + 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), lockType, lockInstance), annotationNode); + 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); diff --git a/src/core/lombok/javac/handlers/HandleLockedWrite.java b/src/core/lombok/javac/handlers/HandleLockedWrite.java index 0a8283be..c726c456 100644 --- a/src/core/lombok/javac/handlers/HandleLockedWrite.java +++ b/src/core/lombok/javac/handlers/HandleLockedWrite.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2021 The Project Lombok Authors. + * 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 @@ -23,7 +23,7 @@ package lombok.javac.handlers; import lombok.core.AnnotationValues; import lombok.core.HandlerPriority; -import lombok.experimental.Locked; +import lombok.Locked; import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; import lombok.spi.Provides; @@ -38,10 +38,11 @@ import static com.sun.tools.javac.tree.JCTree.*; 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_CLASS = new String[]{"java", "util", "concurrent", "locks", "ReentrantReadWriteLock"}; - + 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_CLASS, LOCK_METHOD); + 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/LockedName.java b/test/transform/resource/after-delombok/LockedName.java index 1c6e1083..1121ce1c 100644 --- a/test/transform/resource/after-delombok/LockedName.java +++ b/test/transform/resource/after-delombok/LockedName.java @@ -1,9 +1,7 @@ +import java.util.concurrent.locks.*; class LockedName { - @java.lang.SuppressWarnings("all") - private final java.util.concurrent.locks.ReentrantLock basicLock = new java.util.concurrent.locks.ReentrantLock(); - @java.lang.SuppressWarnings("all") - private final java.util.concurrent.locks.ReentrantReadWriteLock rwLock = new java.util.concurrent.locks.ReentrantReadWriteLock(); - + private final Lock basicLock = new ReentrantLock(); + private final ReadWriteLock rwLock = new ReentrantReadWriteLock(); void test() { this.basicLock.lock(); try { diff --git a/test/transform/resource/after-delombok/LockedOnStatic.java b/test/transform/resource/after-delombok/LockedOnStatic.java index 1280b858..766b6ea1 100644 --- a/test/transform/resource/after-delombok/LockedOnStatic.java +++ b/test/transform/resource/after-delombok/LockedOnStatic.java @@ -1,6 +1,6 @@ class LockedOnStatic { static class Inner { - private static final java.util.concurrent.locks.ReentrantLock LCK = new java.util.concurrent.locks.ReentrantLock(); + private static final java.util.concurrent.locks.Lock LCK = new java.util.concurrent.locks.ReentrantLock(); public void foo() { LockedOnStatic.Inner.LCK.lock(); try { diff --git a/test/transform/resource/after-delombok/LockedPlain.java b/test/transform/resource/after-delombok/LockedPlain.java index 302b967b..bdc4e2a4 100644 --- a/test/transform/resource/after-delombok/LockedPlain.java +++ b/test/transform/resource/after-delombok/LockedPlain.java @@ -1,80 +1,80 @@ class LockedPlain { @java.lang.SuppressWarnings("all") - private final java.util.concurrent.locks.ReentrantLock lock = new java.util.concurrent.locks.ReentrantLock(); + private final java.util.concurrent.locks.Lock $lock = new java.util.concurrent.locks.ReentrantLock(); void test() { - this.lock.lock(); + this.$lock.lock(); try { System.out.println("one"); } finally { - this.lock.unlock(); + this.$lock.unlock(); } } void test2() { - this.lock.lock(); + this.$lock.lock(); try { System.out.println("two"); } finally { - this.lock.unlock(); + this.$lock.unlock(); } } } class LockedPlainStatic { @java.lang.SuppressWarnings("all") - private static final java.util.concurrent.locks.ReentrantLock LOCK = new java.util.concurrent.locks.ReentrantLock(); + private static final java.util.concurrent.locks.Lock $LOCK = new java.util.concurrent.locks.ReentrantLock(); static void test() { - LockedPlainStatic.LOCK.lock(); + LockedPlainStatic.$LOCK.lock(); try { System.out.println("three"); } finally { - LockedPlainStatic.LOCK.unlock(); + LockedPlainStatic.$LOCK.unlock(); } } static void test2() { - LockedPlainStatic.LOCK.lock(); + LockedPlainStatic.$LOCK.lock(); try { System.out.println("four"); } finally { - LockedPlainStatic.LOCK.unlock(); + LockedPlainStatic.$LOCK.unlock(); } } } class LockedPlainRead { @java.lang.SuppressWarnings("all") - private static final java.util.concurrent.locks.ReentrantReadWriteLock LOCK = new java.util.concurrent.locks.ReentrantReadWriteLock(); + private static final java.util.concurrent.locks.ReadWriteLock $LOCK = new java.util.concurrent.locks.ReentrantReadWriteLock(); static void test() { - LockedPlainRead.LOCK.readLock().lock(); + LockedPlainRead.$LOCK.readLock().lock(); try { System.out.println("five"); } finally { - LockedPlainRead.LOCK.readLock().unlock(); + LockedPlainRead.$LOCK.readLock().unlock(); } } static void test2() { - LockedPlainRead.LOCK.readLock().lock(); + LockedPlainRead.$LOCK.readLock().lock(); try { System.out.println("six"); } finally { - LockedPlainRead.LOCK.readLock().unlock(); + LockedPlainRead.$LOCK.readLock().unlock(); } } } class LockedPlainWrite { @java.lang.SuppressWarnings("all") - private final java.util.concurrent.locks.ReentrantReadWriteLock lock = new java.util.concurrent.locks.ReentrantReadWriteLock(); + private final java.util.concurrent.locks.ReadWriteLock $lock = new java.util.concurrent.locks.ReentrantReadWriteLock(); void test() { - this.lock.writeLock().lock(); + this.$lock.writeLock().lock(); try { System.out.println("seven"); } finally { - this.lock.writeLock().unlock(); + this.$lock.writeLock().unlock(); } } void test2() { - this.lock.writeLock().lock(); + this.$lock.writeLock().lock(); try { System.out.println("eight"); } finally { - this.lock.writeLock().unlock(); + this.$lock.writeLock().unlock(); } } } diff --git a/test/transform/resource/after-delombok/LockedStaticMix.java b/test/transform/resource/after-delombok/LockedStaticMix.java index 201c9d7e..e5461248 100644 --- a/test/transform/resource/after-delombok/LockedStaticMix.java +++ b/test/transform/resource/after-delombok/LockedStaticMix.java @@ -1,12 +1,12 @@ class LockedGeneratedStaticMismatch { @java.lang.SuppressWarnings("all") - private static final java.util.concurrent.locks.ReentrantLock LOCK = new java.util.concurrent.locks.ReentrantLock(); + private static final java.util.concurrent.locks.Lock $LOCK = new java.util.concurrent.locks.ReentrantLock(); static void test() { - LockedGeneratedStaticMismatch.LOCK.lock(); + LockedGeneratedStaticMismatch.$LOCK.lock(); try { System.out.println("one"); } finally { - LockedGeneratedStaticMismatch.LOCK.unlock(); + LockedGeneratedStaticMismatch.$LOCK.unlock(); } } void test2() { @@ -14,7 +14,7 @@ class LockedGeneratedStaticMismatch { } } class LockedUserStaticMismatch { - private static final java.util.concurrent.locks.ReentrantLock userLock = new java.util.concurrent.locks.ReentrantLock(); + private static final java.util.concurrent.locks.Lock userLock = new java.util.concurrent.locks.ReentrantLock(); static void test() { LockedUserStaticMismatch.userLock.lock(); try { diff --git a/test/transform/resource/after-delombok/LockedTypeMismatch.java b/test/transform/resource/after-delombok/LockedTypeMismatch.java index bbd916f4..eb0a4b19 100644 --- a/test/transform/resource/after-delombok/LockedTypeMismatch.java +++ b/test/transform/resource/after-delombok/LockedTypeMismatch.java @@ -1,12 +1,12 @@ class LockedGeneratedTypeMismatch { @java.lang.SuppressWarnings("all") - private final java.util.concurrent.locks.ReentrantLock lock = new java.util.concurrent.locks.ReentrantLock(); + private final java.util.concurrent.locks.Lock $lock = new java.util.concurrent.locks.ReentrantLock(); void test() { - this.lock.lock(); + this.$lock.lock(); try { System.out.println("one"); } finally { - this.lock.unlock(); + this.$lock.unlock(); } } void test2() { @@ -14,7 +14,7 @@ class LockedGeneratedTypeMismatch { } } class LockedUserTypeMismatch { - private final java.util.concurrent.locks.ReentrantLock userLock = new java.util.concurrent.locks.ReentrantLock(); + private final java.util.concurrent.locks.Lock userLock = new java.util.concurrent.locks.ReentrantLock(); void test() { this.userLock.lock(); try { diff --git a/test/transform/resource/after-ecj/LockedInRecord.java b/test/transform/resource/after-ecj/LockedInRecord.java index ebb660c4..9801227a 100644 --- a/test/transform/resource/after-ecj/LockedInRecord.java +++ b/test/transform/resource/after-ecj/LockedInRecord.java @@ -1,8 +1,8 @@ -import lombok.experimental.Locked; +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.ReentrantLock lock = new java.util.concurrent.locks.ReentrantLock(); + private final java.util.concurrent.locks.Lock $lock = new java.util.concurrent.locks.ReentrantLock(); public LockedInRecord(String a, String b) { super(); .a = a; diff --git a/test/transform/resource/after-ecj/LockedName.java b/test/transform/resource/after-ecj/LockedName.java index 3fb996de..e42fdf92 100644 --- a/test/transform/resource/after-ecj/LockedName.java +++ b/test/transform/resource/after-ecj/LockedName.java @@ -1,10 +1,11 @@ +import java.util.concurrent.locks.*; class LockedName { - private final java.util.concurrent.locks.ReentrantLock basicLock = new java.util.concurrent.locks.ReentrantLock(); - private final java.util.concurrent.locks.ReentrantReadWriteLock rwLock = new java.util.concurrent.locks.ReentrantReadWriteLock(); + private final Lock basicLock = new ReentrantLock(); + private final ReadWriteLock rwLock = new ReentrantReadWriteLock(); LockedName() { super(); } - @lombok.experimental.Locked("basicLock") void test() { + @lombok.Locked("basicLock") void test() { this.basicLock.lock(); try { @@ -15,7 +16,7 @@ class LockedName { this.basicLock.unlock(); } } - @lombok.experimental.Locked.Read("rwLock") void test2() { + @lombok.Locked.Read("rwLock") void test2() { this.rwLock.readLock().lock(); try { @@ -26,7 +27,7 @@ class LockedName { this.rwLock.readLock().unlock(); } } - @lombok.experimental.Locked.Write("rwLock") void test3() { + @lombok.Locked.Write("rwLock") void test3() { this.rwLock.writeLock().lock(); try { diff --git a/test/transform/resource/after-ecj/LockedOnStatic.java b/test/transform/resource/after-ecj/LockedOnStatic.java index 02d00721..a994b2f5 100644 --- a/test/transform/resource/after-ecj/LockedOnStatic.java +++ b/test/transform/resource/after-ecj/LockedOnStatic.java @@ -1,12 +1,12 @@ class LockedOnStatic { static class Inner { - private static final java.util.concurrent.locks.ReentrantLock LCK = new java.util.concurrent.locks.ReentrantLock(); + private static final java.util.concurrent.locks.Lock LCK = new java.util.concurrent.locks.ReentrantLock(); () { } Inner() { super(); } - public @lombok.experimental.Locked("LCK") void foo() { + public @lombok.Locked("LCK") void foo() { LockedOnStatic.Inner.LCK.lock(); try { @@ -23,7 +23,7 @@ class LockedOnStatic { Inner2() { super(); } - public @lombok.experimental.Locked("LCK") void foo() { + public @lombok.Locked("LCK") void foo() { this.LCK.lock(); try { diff --git a/test/transform/resource/after-ecj/LockedPlain.java b/test/transform/resource/after-ecj/LockedPlain.java index b0b91719..f311c193 100644 --- a/test/transform/resource/after-ecj/LockedPlain.java +++ b/test/transform/resource/after-ecj/LockedPlain.java @@ -1,117 +1,117 @@ -import lombok.experimental.Locked; +import lombok.Locked; class LockedPlain { - private final java.util.concurrent.locks.ReentrantLock lock = new java.util.concurrent.locks.ReentrantLock(); + private final java.util.concurrent.locks.Lock $lock = new java.util.concurrent.locks.ReentrantLock(); LockedPlain() { super(); } - @lombok.experimental.Locked void test() { - this.lock.lock(); + @Locked void test() { + this.$lock.lock(); try { System.out.println("one"); } finally { - this.lock.unlock(); + this.$lock.unlock(); } } @Locked void test2() { - this.lock.lock(); + this.$lock.lock(); try { System.out.println("two"); } finally { - this.lock.unlock(); + this.$lock.unlock(); } } } class LockedPlainStatic { - private static final java.util.concurrent.locks.ReentrantLock LOCK = new java.util.concurrent.locks.ReentrantLock(); + private static final java.util.concurrent.locks.Lock $LOCK = new java.util.concurrent.locks.ReentrantLock(); () { } LockedPlainStatic() { super(); } - static @lombok.experimental.Locked void test() { - LockedPlainStatic.LOCK.lock(); + static @Locked void test() { + LockedPlainStatic.$LOCK.lock(); try { System.out.println("three"); } finally { - LockedPlainStatic.LOCK.unlock(); + LockedPlainStatic.$LOCK.unlock(); } } static @Locked void test2() { - LockedPlainStatic.LOCK.lock(); + LockedPlainStatic.$LOCK.lock(); try { System.out.println("four"); } finally { - LockedPlainStatic.LOCK.unlock(); + LockedPlainStatic.$LOCK.unlock(); } } } class LockedPlainRead { - private static final java.util.concurrent.locks.ReentrantReadWriteLock LOCK = new java.util.concurrent.locks.ReentrantReadWriteLock(); + private static final java.util.concurrent.locks.ReadWriteLock $LOCK = new java.util.concurrent.locks.ReentrantReadWriteLock(); () { } LockedPlainRead() { super(); } - static @lombok.experimental.Locked.Read void test() { - LockedPlainRead.LOCK.readLock().lock(); + static @Locked.Read void test() { + LockedPlainRead.$LOCK.readLock().lock(); try { System.out.println("five"); } finally { - LockedPlainRead.LOCK.readLock().unlock(); + LockedPlainRead.$LOCK.readLock().unlock(); } } static @Locked.Read void test2() { - LockedPlainRead.LOCK.readLock().lock(); + LockedPlainRead.$LOCK.readLock().lock(); try { System.out.println("six"); } finally { - LockedPlainRead.LOCK.readLock().unlock(); + LockedPlainRead.$LOCK.readLock().unlock(); } } } class LockedPlainWrite { - private final java.util.concurrent.locks.ReentrantReadWriteLock lock = new java.util.concurrent.locks.ReentrantReadWriteLock(); + private final java.util.concurrent.locks.ReadWriteLock $lock = new java.util.concurrent.locks.ReentrantReadWriteLock(); LockedPlainWrite() { super(); } - @lombok.experimental.Locked.Write void test() { - this.lock.writeLock().lock(); + @Locked.Write void test() { + this.$lock.writeLock().lock(); try { System.out.println("seven"); } finally { - this.lock.writeLock().unlock(); + this.$lock.writeLock().unlock(); } } @Locked.Write void test2() { - this.lock.writeLock().lock(); + this.$lock.writeLock().lock(); try { System.out.println("eight"); } finally { - this.lock.writeLock().unlock(); + this.$lock.writeLock().unlock(); } } } diff --git a/test/transform/resource/after-ecj/LockedStaticMix.java b/test/transform/resource/after-ecj/LockedStaticMix.java index 3ec8ca2d..98c8e804 100644 --- a/test/transform/resource/after-ecj/LockedStaticMix.java +++ b/test/transform/resource/after-ecj/LockedStaticMix.java @@ -1,33 +1,33 @@ class LockedGeneratedStaticMismatch { - private static final java.util.concurrent.locks.ReentrantLock LOCK = new java.util.concurrent.locks.ReentrantLock(); + private static final java.util.concurrent.locks.Lock $LOCK = new java.util.concurrent.locks.ReentrantLock(); () { } LockedGeneratedStaticMismatch() { super(); } - static @lombok.experimental.Locked void test() { - LockedGeneratedStaticMismatch.LOCK.lock(); + static @lombok.Locked void test() { + LockedGeneratedStaticMismatch.$LOCK.lock(); try { System.out.println("one"); } finally { - LockedGeneratedStaticMismatch.LOCK.unlock(); + LockedGeneratedStaticMismatch.$LOCK.unlock(); } } - @lombok.experimental.Locked("LOCK") void test2() { + @lombok.Locked("$LOCK") void test2() { System.out.println("two"); } } class LockedUserStaticMismatch { - private static final java.util.concurrent.locks.ReentrantLock userLock = new java.util.concurrent.locks.ReentrantLock(); + private static final java.util.concurrent.locks.Lock userLock = new java.util.concurrent.locks.ReentrantLock(); () { } LockedUserStaticMismatch() { super(); } - static @lombok.experimental.Locked("userLock") void test() { + static @lombok.Locked("userLock") void test() { LockedUserStaticMismatch.userLock.lock(); try { @@ -38,7 +38,7 @@ class LockedUserStaticMismatch { LockedUserStaticMismatch.userLock.unlock(); } } - @lombok.experimental.Locked("userLock") void test2() { + @lombok.Locked("userLock") void test2() { LockedUserStaticMismatch.userLock.lock(); try { diff --git a/test/transform/resource/after-ecj/LockedTypeMismatch.java b/test/transform/resource/after-ecj/LockedTypeMismatch.java index 57e3f44a..390ec572 100644 --- a/test/transform/resource/after-ecj/LockedTypeMismatch.java +++ b/test/transform/resource/after-ecj/LockedTypeMismatch.java @@ -1,29 +1,29 @@ class LockedGeneratedTypeMismatch { - private final java.util.concurrent.locks.ReentrantLock lock = new java.util.concurrent.locks.ReentrantLock(); + private final java.util.concurrent.locks.Lock $lock = new java.util.concurrent.locks.ReentrantLock(); LockedGeneratedTypeMismatch() { super(); } - @lombok.experimental.Locked void test() { - this.lock.lock(); + @lombok.Locked void test() { + this.$lock.lock(); try { System.out.println("one"); } finally { - this.lock.unlock(); + this.$lock.unlock(); } } - @lombok.experimental.Locked.Read void test2() { + @lombok.Locked.Read void test2() { System.out.println("two"); } } class LockedUserTypeMismatch { - private final java.util.concurrent.locks.ReentrantLock userLock = new java.util.concurrent.locks.ReentrantLock(); + private final java.util.concurrent.locks.Lock userLock = new java.util.concurrent.locks.ReentrantLock(); LockedUserTypeMismatch() { super(); } - @lombok.experimental.Locked("userLock") void test() { + @lombok.Locked("userLock") void test() { this.userLock.lock(); try { @@ -34,7 +34,7 @@ class LockedUserTypeMismatch { this.userLock.unlock(); } } - @lombok.experimental.Locked.Read("userLock") void test2() { + @lombok.Locked.Read("userLock") void test2() { this.userLock.readLock().lock(); try { diff --git a/test/transform/resource/before/LockedInRecord.java b/test/transform/resource/before/LockedInRecord.java index 9bb16001..8d368998 100644 --- a/test/transform/resource/before/LockedInRecord.java +++ b/test/transform/resource/before/LockedInRecord.java @@ -1,6 +1,6 @@ // version 14: -import lombok.experimental.Locked; +import lombok.Locked; public record LockedInRecord(String a, String b) { @Locked diff --git a/test/transform/resource/before/LockedName.java b/test/transform/resource/before/LockedName.java index 3a73e5a3..66fe7d95 100644 --- a/test/transform/resource/before/LockedName.java +++ b/test/transform/resource/before/LockedName.java @@ -1,11 +1,14 @@ +import java.util.concurrent.locks.*; class LockedName { - @lombok.experimental.Locked("basicLock") void test() { + private final Lock basicLock = new ReentrantLock(); + private final ReadWriteLock rwLock = new ReentrantReadWriteLock(); + @lombok.Locked("basicLock") void test() { System.out.println("one"); } - @lombok.experimental.Locked.Read("rwLock") void test2() { + @lombok.Locked.Read("rwLock") void test2() { System.out.println("two"); } - @lombok.experimental.Locked.Write("rwLock") void test3() { + @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 index 4da5af4c..4c1cb2a7 100644 --- a/test/transform/resource/before/LockedOnStatic.java +++ b/test/transform/resource/before/LockedOnStatic.java @@ -1,14 +1,14 @@ class LockedOnStatic { static class Inner { - private static final java.util.concurrent.locks.ReentrantLock LCK = new java.util.concurrent.locks.ReentrantLock(); - @lombok.experimental.Locked("LCK") + 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.experimental.Locked("LCK") + @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 index 7bcba019..5ebe25f4 100644 --- a/test/transform/resource/before/LockedPlain.java +++ b/test/transform/resource/before/LockedPlain.java @@ -1,6 +1,6 @@ -import lombok.experimental.Locked; +import lombok.Locked; class LockedPlain { - @lombok.experimental.Locked void test() { + @Locked void test() { System.out.println("one"); } @Locked void test2() { @@ -8,7 +8,7 @@ class LockedPlain { } } class LockedPlainStatic { - @lombok.experimental.Locked static void test() { + @Locked static void test() { System.out.println("three"); } @Locked static void test2() { @@ -16,7 +16,7 @@ class LockedPlainStatic { } } class LockedPlainRead { - @lombok.experimental.Locked.Read static void test() { + @Locked.Read static void test() { System.out.println("five"); } @Locked.Read static void test2() { @@ -24,7 +24,7 @@ class LockedPlainRead { } } class LockedPlainWrite { - @lombok.experimental.Locked.Write void test() { + @Locked.Write void test() { System.out.println("seven"); } @Locked.Write void test2() { diff --git a/test/transform/resource/before/LockedStaticMix.java b/test/transform/resource/before/LockedStaticMix.java index 9506ac04..8d8605a0 100644 --- a/test/transform/resource/before/LockedStaticMix.java +++ b/test/transform/resource/before/LockedStaticMix.java @@ -1,17 +1,17 @@ class LockedGeneratedStaticMismatch { - @lombok.experimental.Locked static void test() { + @lombok.Locked static void test() { System.out.println("one"); } - @lombok.experimental.Locked("LOCK") void test2() { + @lombok.Locked("$LOCK") void test2() { System.out.println("two"); } } class LockedUserStaticMismatch { - private static final java.util.concurrent.locks.ReentrantLock userLock = new java.util.concurrent.locks.ReentrantLock(); - @lombok.experimental.Locked("userLock") static void test() { + 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.experimental.Locked("userLock") void test2() { + @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 index 67e96b56..8ee3a932 100644 --- a/test/transform/resource/before/LockedTypeMismatch.java +++ b/test/transform/resource/before/LockedTypeMismatch.java @@ -1,17 +1,17 @@ class LockedGeneratedTypeMismatch { - @lombok.experimental.Locked void test() { + @lombok.Locked void test() { System.out.println("one"); } - @lombok.experimental.Locked.Read void test2() { + @lombok.Locked.Read void test2() { System.out.println("two"); } } class LockedUserTypeMismatch { - private final java.util.concurrent.locks.ReentrantLock userLock = new java.util.concurrent.locks.ReentrantLock(); - @lombok.experimental.Locked("userLock") void test() { + private final java.util.concurrent.locks.Lock userLock = new java.util.concurrent.locks.ReentrantLock(); + @lombok.Locked("userLock") void test() { System.out.println("one"); } - @lombok.experimental.Locked.Read("userLock") void test2() { + @lombok.Locked.Read("userLock") void test2() { System.out.println("two"); } } diff --git a/test/transform/resource/messages-delombok/LockedTypeMismatch.java.messages b/test/transform/resource/messages-delombok/LockedTypeMismatch.java.messages index ad329880..c53b9f29 100644 --- a/test/transform/resource/messages-delombok/LockedTypeMismatch.java.messages +++ b/test/transform/resource/messages-delombok/LockedTypeMismatch.java.messages @@ -1 +1 @@ -5 Expected field lock to be of type java.util.concurrent.locks.ReentrantReadWriteLock but got type java.util.concurrent.locks.ReentrantLock! Did you mix @Locked with @Locked.Read/Write on the same generated field? +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/LockedTypeMismatch.java.messages b/test/transform/resource/messages-ecj/LockedTypeMismatch.java.messages index ac159d72..a1a81b4a 100644 --- a/test/transform/resource/messages-ecj/LockedTypeMismatch.java.messages +++ b/test/transform/resource/messages-ecj/LockedTypeMismatch.java.messages @@ -1,2 +1,2 @@ -5 Expected field lock to be of type java.util.concurrent.locks.ReentrantReadWriteLock but got type java.util.concurrent.locks.ReentrantLock! Did you mix @Locked with @Locked.Read/Write on the same generated field? -14 The method readLock() is undefined for the type ReentrantLock +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/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/experimental/Locked.html b/website/templates/features/experimental/Locked.html deleted file mode 100644 index cd4f599c..00000000 --- a/website/templates/features/experimental/Locked.html +++ /dev/null @@ -1,45 +0,0 @@ -<#import "../_features.html" as f> - -<@f.scaffold title="@Locked" logline="TODO"> - <@f.history> -

- @Locked was introduced as an experimental feature in lombok v1.18.21. -

- - - <@f.experimental> -
    -
  • - New feature – community feedback requested. -
  • -
- Current status: unknown - - - <@f.overview> -

- @Locked is an alternative to @Synchronized that uses Java locks for more extensive locking operations. The annotation locks on the provided field if one is provided. If no name is provided, the annotation will default to LOCK when used on static methods and to lock for non-static methods. If no field with the specified name exists, one will be generated for you. -

- The base @Locked annotation uses a java.util.concurrent.locks.Lock for locking. When required, a java.util.concurrent.locks.ReentrantLock is generated.
- Additionally, there are the @Locked.Read and @Locked.Write annotations. These use a java.util.concurrent.locks.ReadWriteLock. 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. -

- - - <@f.snippets name="experimental/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.
- 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/experimental/index.html b/website/templates/features/experimental/index.html index 12c404bb..a50a4670 100644 --- a/website/templates/features/experimental/index.html +++ b/website/templates/features/experimental/index.html @@ -75,10 +75,6 @@ <@main.feature title="@StandardException" href="StandardException"> Standard.. Exceptional? This is not just an oxymoron, it's convenient! - - <@main.feature title="@Locked" href="Locked"> - TODO - <@f.confKeys> 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 index 86bce0e9..0a20ebec 100644 --- a/website/usageExamples/experimental/LockedExample_post.jpage +++ b/website/usageExamples/experimental/LockedExample_post.jpage @@ -1,8 +1,8 @@ public class LockedExample { - private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); - private final ReentrantLock baseLock = new ReentrantLock(); + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + private final Lock baseLock = new ReentrantLock(); private int value = 0; - + public int getValue() { this.lock.readLock().lock(); try { @@ -11,7 +11,7 @@ public class LockedExample { this.lock.readLock().unlock(); } } - + public void setValue(int newValue) { this.lock.writeLock().lock(); try { @@ -20,7 +20,7 @@ public class LockedExample { this.lock.writeLock().unlock(); } } - + public void foo() { this.baseLock.lock(); try { diff --git a/website/usageExamples/experimental/LockedExample_pre.jpage b/website/usageExamples/experimental/LockedExample_pre.jpage index fdfc7446..b741d235 100644 --- a/website/usageExamples/experimental/LockedExample_pre.jpage +++ b/website/usageExamples/experimental/LockedExample_pre.jpage @@ -1,18 +1,18 @@ -import lombok.experimental.Locked; +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");