Skip to content

Commit

Permalink
[Issue #94] @BoundPropertySupport should support vetoable change
Browse files Browse the repository at this point in the history
  • Loading branch information
peichhorn committed Jul 4, 2012
1 parent deb9c6f commit db5b588
Show file tree
Hide file tree
Showing 17 changed files with 344 additions and 269 deletions.
39 changes: 2 additions & 37 deletions src/core/lombok/BoundPropertySupport.java
Expand Up @@ -27,44 +27,9 @@
import java.lang.annotation.*;

/**
* Transformses the annotated Java class into a JavaBean.
* <p>
* Before:
*
* <pre>
* &#064;GenerateJavaBean
* public class Mountain {
*
* }
* </pre>
*
* After:
*
* <pre>
* public class Mountain {
* private volatile transient PropertyChangeSupport $propertySupport;
*
* public void addPropertyChangeListener(PropertyChangeListener listener) {
* getPropertySupport().addPropertyChangeListener(listener);
* }
*
* public void removePropertyChangeListener(PropertyChangeListener listener) {
* getPropertySupport().removePropertyChangeListener(listener);
* }
*
* private java.beans.PropertyChangeSupport getPropertySupport() {
* if (this.$propertySupport == null) {
* synchronized (this) {
* if (this.$propertySupport == null) {
* this.$propertySupport = new java.beans.PropertyChangeSupport(this);
* }
* }
* }
* return this.$propertySupport;
* }
* }
* </pre>
* @deprecated doesn't do anything useful anymore.. everything is handled by @BoundSetter
*/
@Deprecated
@Target(TYPE)
@Retention(SOURCE)
public @interface BoundPropertySupport {
Expand Down
19 changes: 15 additions & 4 deletions src/core/lombok/BoundSetter.java
Expand Up @@ -37,7 +37,7 @@
* <pre>
* public class Mountain {
*
* &#064;GenerateBoundSetter
* &#064;BoundSetter
* private String name;
* }
* </pre>
Expand All @@ -50,9 +50,9 @@
*
* private String name;
*
* public void setName(String value) {
* String oldValue = name;
* firstName = value;
* public void setName(String name) {
* String oldValue = this.name;
* this.name = name;
* getPropertySupport().firePropertyChange(PROP_NAME, oldValue, name);
* }
* }
Expand All @@ -66,4 +66,15 @@
* If you want your setter to be non-public, you can specify an alternate access level here.
*/
AccessLevel value() default PUBLIC;

/**
* When true, it indicates that bound properties should be a constrained properties according
* to the JavaBeans spec and therefore subject to listeners vetoing the property change.
*/
boolean vetoable() default false;

/**
*
*/
boolean throwVetoException() default false;
}
3 changes: 3 additions & 0 deletions src/core/lombok/ast/IType.java
Expand Up @@ -23,6 +23,7 @@

import java.util.List;

import lombok.core.AnnotationValues;
import lombok.core.LombokNode;

public interface IType<METHOD_TYPE extends IMethod<?, ?, ?, ?>, FIELD_TYPE extends IField<?, ?, ?>, LOMBOK_NODE_TYPE extends LombokNode<?, ?, ?>, AST_BASE_TYPE, AST_TYPE_DECL_TYPE, AST_METHOD_DECL_TYPE> {
Expand Down Expand Up @@ -58,6 +59,8 @@ public interface IType<METHOD_TYPE extends IMethod<?, ?, ?, ?>, FIELD_TYPE exten

public LOMBOK_NODE_TYPE node();

public <A extends java.lang.annotation.Annotation> AnnotationValues<A> getAnnotationValue(Class<A> expectedType);

public LOMBOK_NODE_TYPE getAnnotation(Class<? extends java.lang.annotation.Annotation> clazz);

public LOMBOK_NODE_TYPE getAnnotation(final String typeName);
Expand Down
81 changes: 0 additions & 81 deletions src/core/lombok/core/handlers/BoundPropertySupportHandler.java

This file was deleted.

122 changes: 113 additions & 9 deletions src/core/lombok/core/handlers/BoundSetterHandler.java
Expand Up @@ -26,6 +26,11 @@
import static lombok.core.util.ErrorMessages.*;
import static lombok.core.util.Names.camelCaseToConstant;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.beans.PropertyVetoException;
import java.beans.VetoableChangeListener;
import java.beans.VetoableChangeSupport;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
Expand All @@ -35,20 +40,29 @@
import lombok.core.AnnotationValues;
import lombok.core.LombokNode;
import lombok.core.AST.Kind;
import lombok.core.util.As;
import lombok.core.TransformationsUtil;
import lombok.experimental.Accessors;

@RequiredArgsConstructor
public abstract class BoundSetterHandler<TYPE_TYPE extends IType<?, FIELD_TYPE, ?, ?, ?, ?>, FIELD_TYPE extends IField<?, ?, ?>, LOMBOK_NODE_TYPE extends LombokNode<?, LOMBOK_NODE_TYPE, ?>, SOURCE_TYPE> {
private static final String PROPERTY_CHANGE_SUPPORT_FIELD_NAME = "$propertyChangeSupport";
private static final String VETOABLE_CHANGE_SUPPORT_FIELD_NAME = "$vetoableChangeSupport";
private static final String PROPERTY_CHANGE_SUPPORT_METHOD_NAME = "getPropertyChangeSupport";
private static final String VETOABLE_CHANGE_SUPPORT_METHOD_NAME = "getVetoableChangeSupport";
private static final String LISTENER_ARG_NAME = "listener";
private static final String[] PROPERTY_CHANGE_METHOD_NAMES = As.array("addPropertyChangeListener", "removePropertyChangeListener");
private static final String[] VETOABLE_CHANGE_METHOD_NAMES = As.array("addVetoableChangeListener", "removeVetoableChangeListener");
private static final String FIRE_PROPERTY_CHANGE_METHOD_NAME = "firePropertyChange";
private static final String OLD_VALUE_VARIABLE_NAME = "old";
private static final String FIRE_VETOABLE_CHANGE_METHOD_NAME = "fireVetoableChange";
private static final String OLD_VALUE_VARIABLE_NAME = "$old";
private static final String E_VALUE_VARIABLE_NAME = "$e";
private static final Pattern SETTER_PATTERN = Pattern.compile("^(?:setter|fluentsetter|boundsetter)$", Pattern.CASE_INSENSITIVE);

private final LOMBOK_NODE_TYPE annotationNode;
private final SOURCE_TYPE ast;

public void handle(final AccessLevel level) {
public void handle(final AccessLevel level, final boolean vetoable, final boolean throwVetoException) {
LOMBOK_NODE_TYPE mayBeField = annotationNode.up();
if (mayBeField == null) return;
TYPE_TYPE type = typeOf(annotationNode, ast);
Expand All @@ -69,18 +83,30 @@ public void handle(final AccessLevel level) {
annotationNode.addError(canBeUsedOnClassAndFieldOnly(BoundSetter.class));
return;
}
generateSetter(type, fields, level);
generateSetter(type, fields, level, vetoable | throwVetoException, throwVetoException);
}

protected abstract TYPE_TYPE typeOf(final LOMBOK_NODE_TYPE node, final SOURCE_TYPE ast);

protected abstract FIELD_TYPE fieldOf(final LOMBOK_NODE_TYPE node, final SOURCE_TYPE ast);

private void generateSetter(final TYPE_TYPE type, final List<FIELD_TYPE> fields, final AccessLevel level) {
private void generateSetter(final TYPE_TYPE type, final List<FIELD_TYPE> fields, final AccessLevel level, final boolean vetoable, final boolean throwVetoException) {
if (!fields.isEmpty()) {
if (!type.hasMethod(PROPERTY_CHANGE_SUPPORT_METHOD_NAME, 0)) {
generatePropertyChangeSupportFields(type);
generateGetPropertySupportMethod(type);
generatePropertyChangeListenerMethods(type);
}
if (vetoable && !type.hasMethod(VETOABLE_CHANGE_SUPPORT_METHOD_NAME, 0)) {
generateVetoableChangeSupportFields(type);
generateGetVetoableSupportMethod(type);
generateVetoableChangeListenerMethods(type);
}
}
for (FIELD_TYPE field : fields) {
String propertyNameFieldName = "PROP_" + camelCaseToConstant(field.name());
generatePropertyNameConstant(type, field, propertyNameFieldName);
generateSetter(type, field, level, propertyNameFieldName);
generateSetter(type, field, level, vetoable, throwVetoException, propertyNameFieldName);
}
}

Expand All @@ -91,7 +117,7 @@ private void generatePropertyNameConstant(final TYPE_TYPE type, final FIELD_TYPE
.withInitialization(String(propertyName)));
}

private void generateSetter(final TYPE_TYPE type, final FIELD_TYPE field, final AccessLevel level, final String propertyNameFieldName) {
private void generateSetter(final TYPE_TYPE type, final FIELD_TYPE field, final AccessLevel level, final boolean vetoable, final boolean throwVetoException, final String propertyNameFieldName) {
String fieldName = field.name();
boolean isBoolean = field.isOfType("boolean");
AnnotationValues<Accessors> accessors = AnnotationValues.of(Accessors.class, field.node());
Expand All @@ -103,10 +129,88 @@ private void generateSetter(final TYPE_TYPE type, final FIELD_TYPE field, final
if (!nonNulls.isEmpty() && !field.isPrimitive()) {
methodDecl.withStatement(If(Equal(Name(fieldName), Null())).Then(Throw(New(Type(NullPointerException.class)).withArgument(String(fieldName)))));
}
methodDecl.withStatement(LocalDecl(field.type(), oldValueName).makeFinal().withInitialization(Field(fieldName))) //
.withStatement(Assign(Field(fieldName), Name(fieldName))) //

methodDecl.withStatement(LocalDecl(field.type(), oldValueName).makeFinal().withInitialization(Field(fieldName)));

if (vetoable) {
if (throwVetoException) {
methodDecl.withThrownException(Type(PropertyVetoException.class));
methodDecl.withStatement(Call(Call(VETOABLE_CHANGE_SUPPORT_METHOD_NAME), FIRE_VETOABLE_CHANGE_METHOD_NAME) //
.withArgument(Name(propertyNameFieldName)).withArgument(Name(oldValueName)).withArgument(Name(fieldName)));
} else {
methodDecl.withStatement(Try(Block().withStatement(Call(Call(VETOABLE_CHANGE_SUPPORT_METHOD_NAME), FIRE_VETOABLE_CHANGE_METHOD_NAME) //
.withArgument(Name(propertyNameFieldName)).withArgument(Name(oldValueName)).withArgument(Name(fieldName)))) //
.Catch(Arg(Type(PropertyVetoException.class), E_VALUE_VARIABLE_NAME), Block().withStatement(Return())));
}
}

methodDecl.withStatement(Assign(Field(fieldName), Name(fieldName))) //
.withStatement(Call(Call(PROPERTY_CHANGE_SUPPORT_METHOD_NAME), FIRE_PROPERTY_CHANGE_METHOD_NAME) //
.withArgument(Name(propertyNameFieldName)).withArgument(Name(oldValueName)).withArgument(Field(fieldName)));
.withArgument(Name(propertyNameFieldName)).withArgument(Name(oldValueName)).withArgument(Name(fieldName)));
type.injectMethod(methodDecl);
}

private void generatePropertyChangeSupportFields(final TYPE_TYPE type) {
if (!type.hasField(PROPERTY_CHANGE_SUPPORT_FIELD_NAME)) {
type.injectField(FieldDecl(Type(PropertyChangeSupport.class), PROPERTY_CHANGE_SUPPORT_FIELD_NAME).makePrivate().makeTransient().makeVolatile());
}
if (!type.hasField(PROPERTY_CHANGE_SUPPORT_FIELD_NAME + "Lock")) {
type.injectField(FieldDecl(Type(Object.class).withDimensions(1), PROPERTY_CHANGE_SUPPORT_FIELD_NAME + "Lock").makePrivate().makeFinal() //
.withInitialization(NewArray(Type(Object.class)).withDimensionExpression(Number(0))));
}
}

private void generateGetPropertySupportMethod(final TYPE_TYPE type) {
if (type.hasMethod(PROPERTY_CHANGE_SUPPORT_METHOD_NAME, 0)) return;
type.injectMethod(MethodDecl(Type(PropertyChangeSupport.class), PROPERTY_CHANGE_SUPPORT_METHOD_NAME).makePrivate() //
.withStatement(If(Equal(Field(PROPERTY_CHANGE_SUPPORT_FIELD_NAME), Null())).Then(Block() //
.withStatement(Synchronized(Field(PROPERTY_CHANGE_SUPPORT_FIELD_NAME + "Lock")) //
.withStatement(If(Equal(Field(PROPERTY_CHANGE_SUPPORT_FIELD_NAME), Null())).Then(Block() //
.withStatement(Assign(Field(PROPERTY_CHANGE_SUPPORT_FIELD_NAME), New(Type(PropertyChangeSupport.class)).withArgument(This())))))))) //
.withStatement(Return(Field(PROPERTY_CHANGE_SUPPORT_FIELD_NAME))));
}

private void generatePropertyChangeListenerMethods(final TYPE_TYPE type) {
for (String methodName : PROPERTY_CHANGE_METHOD_NAMES) {
generatePropertyChangeListenerMethod(methodName, type);
}
}

private void generatePropertyChangeListenerMethod(final String methodName, final TYPE_TYPE type) {
if (type.hasMethod(methodName, 1)) return;
type.injectMethod(MethodDecl(Type("void"), methodName).makePublic().withArgument(Arg(Type(PropertyChangeListener.class), LISTENER_ARG_NAME)) //
.withStatement(Call(Call(PROPERTY_CHANGE_SUPPORT_METHOD_NAME), methodName).withArgument(Name(LISTENER_ARG_NAME))));
}

private void generateVetoableChangeSupportFields(final TYPE_TYPE type) {
if (!type.hasField(VETOABLE_CHANGE_SUPPORT_FIELD_NAME)) {
type.injectField(FieldDecl(Type(VetoableChangeSupport.class), VETOABLE_CHANGE_SUPPORT_FIELD_NAME).makePrivate().makeTransient().makeVolatile());
}
if (!type.hasField(VETOABLE_CHANGE_SUPPORT_FIELD_NAME + "Lock")) {
type.injectField(FieldDecl(Type(Object.class).withDimensions(1), VETOABLE_CHANGE_SUPPORT_FIELD_NAME + "Lock").makePrivate().makeFinal() //
.withInitialization(NewArray(Type(Object.class)).withDimensionExpression(Number(0))));
}
}

private void generateGetVetoableSupportMethod(final TYPE_TYPE type) {
if (type.hasMethod(VETOABLE_CHANGE_SUPPORT_METHOD_NAME, 0)) return;
type.injectMethod(MethodDecl(Type(VetoableChangeSupport.class), VETOABLE_CHANGE_SUPPORT_METHOD_NAME).makePrivate() //
.withStatement(If(Equal(Field(VETOABLE_CHANGE_SUPPORT_FIELD_NAME), Null())).Then(Block() //
.withStatement(Synchronized(Field(VETOABLE_CHANGE_SUPPORT_FIELD_NAME + "Lock")) //
.withStatement(If(Equal(Field(VETOABLE_CHANGE_SUPPORT_FIELD_NAME), Null())).Then(Block() //
.withStatement(Assign(Field(VETOABLE_CHANGE_SUPPORT_FIELD_NAME), New(Type(VetoableChangeSupport.class)).withArgument(This())))))))) //
.withStatement(Return(Field(VETOABLE_CHANGE_SUPPORT_FIELD_NAME))));
}

private void generateVetoableChangeListenerMethods(final TYPE_TYPE type) {
for (String methodName : VETOABLE_CHANGE_METHOD_NAMES) {
generateVetoableChangeListenerMethod(methodName, type);
}
}

private void generateVetoableChangeListenerMethod(final String methodName, final TYPE_TYPE type) {
if (type.hasMethod(methodName, 1)) return;
type.injectMethod(MethodDecl(Type("void"), methodName).makePublic().withArgument(Arg(Type(VetoableChangeListener.class), LISTENER_ARG_NAME)) //
.withStatement(Call(Call(VETOABLE_CHANGE_SUPPORT_METHOD_NAME), methodName).withArgument(Name(LISTENER_ARG_NAME))));
}
}

0 comments on commit db5b588

Please sign in to comment.