Skip to content

Commit

Permalink
Prototype fluent setter.
Browse files Browse the repository at this point in the history
  • Loading branch information
robin committed Apr 17, 2010
1 parent 5f276b6 commit ee60a6c
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 23 deletions.
1 change: 1 addition & 0 deletions src/core/lombok/Setter.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,5 @@
* If you want your setter to be non-public, you can specify an alternate access level here.
*/
lombok.AccessLevel value() default lombok.AccessLevel.PUBLIC;
boolean fluent() default false;
}
10 changes: 10 additions & 0 deletions src/core/lombok/core/handlers/TransformationsUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,14 @@ private static String buildName(String prefix, String suffix) {
return String.format("%s%s", prefix, suffix);
}

/**
* @param fieldName the name of the field
* @return the fluent setter name for this field - same as the field name.
*/
public static String toFluentSetterName(CharSequence fieldName) {
return fieldName.toString();
}

public static List<String> toAllGetterNames(CharSequence fieldName, boolean isBoolean) {
if (!isBoolean) return Collections.singletonList(toGetterName(fieldName, false));

Expand Down Expand Up @@ -146,4 +154,6 @@ public static List<String> toAllGetterNames(CharSequence fieldName, boolean isBo

return new ArrayList<String>(names);
}


}
64 changes: 50 additions & 14 deletions src/core/lombok/eclipse/handlers/HandleSetter.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,19 @@
*/
package lombok.eclipse.handlers;

import static lombok.eclipse.Eclipse.*;
import static lombok.eclipse.handlers.EclipseHandlerUtil.*;
import static lombok.eclipse.Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG;
import static lombok.eclipse.Eclipse.annotationTypeMatches;
import static lombok.eclipse.Eclipse.copyAnnotations;
import static lombok.eclipse.Eclipse.copyType;
import static lombok.eclipse.handlers.EclipseHandlerUtil.findAnnotations;
import static lombok.eclipse.handlers.EclipseHandlerUtil.generateNullCheck;
import static lombok.eclipse.handlers.EclipseHandlerUtil.injectMethod;
import static lombok.eclipse.handlers.EclipseHandlerUtil.methodExists;
import static lombok.eclipse.handlers.EclipseHandlerUtil.toEclipseModifier;

import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;

import lombok.AccessLevel;
import lombok.Setter;
Expand All @@ -39,11 +48,14 @@
import org.eclipse.jdt.internal.compiler.ast.Annotation;
import org.eclipse.jdt.internal.compiler.ast.Argument;
import org.eclipse.jdt.internal.compiler.ast.Assignment;
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.MethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.NameReference;
import org.eclipse.jdt.internal.compiler.ast.ReturnStatement;
import org.eclipse.jdt.internal.compiler.ast.SingleNameReference;
import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference;
import org.eclipse.jdt.internal.compiler.ast.Statement;
import org.eclipse.jdt.internal.compiler.ast.ThisReference;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
Expand Down Expand Up @@ -79,27 +91,33 @@ public void generateSetterForField(EclipseNode fieldNode, ASTNode pos) {
}
}

createSetterForField(AccessLevel.PUBLIC, fieldNode, fieldNode, pos, false);
createSetterForField(AccessLevel.PUBLIC, fieldNode, fieldNode, pos, false, false);
}

public boolean handle(AnnotationValues<Setter> annotation, Annotation ast, EclipseNode annotationNode) {
EclipseNode fieldNode = annotationNode.up();
if (fieldNode.getKind() != Kind.FIELD) return false;
AccessLevel level = annotation.getInstance().value();
Setter annotationInstance = annotation.getInstance();
AccessLevel level = annotationInstance.value();
boolean fluent = annotationInstance.fluent();

if (level == AccessLevel.NONE) return true;

return createSetterForField(level, fieldNode, annotationNode, annotationNode.get(), true);
return createSetterForField(level, fieldNode, annotationNode, annotationNode.get(), true, fluent);
}

private boolean createSetterForField(AccessLevel level,
EclipseNode fieldNode, EclipseNode errorNode, ASTNode pos, boolean whineIfExists) {
EclipseNode fieldNode, EclipseNode errorNode, ASTNode pos, boolean whineIfExists, boolean fluent) {
if (fieldNode.getKind() != Kind.FIELD) {
errorNode.addError("@Setter is only supported on a field.");
return true;
}

FieldDeclaration field = (FieldDeclaration) fieldNode.get();
String setterName = TransformationsUtil.toSetterName(new String(field.name));
String fieldName = new String(field.name);

// TODO: handle clash between fluent & non-fluent setters
String setterName = fluent ? TransformationsUtil.toFluentSetterName(fieldName) : TransformationsUtil.toSetterName(fieldName);

int modifier = toEclipseModifier(level) | (field.modifiers & ClassFileConstants.AccStatic);

Expand All @@ -109,29 +127,33 @@ private boolean createSetterForField(AccessLevel level,
case EXISTS_BY_USER:
if (whineIfExists) errorNode.addWarning(
String.format("Not generating %s(%s %s): A method with that name already exists",
setterName, field.type, new String(field.name)));
setterName, field.type, fieldName));
return true;
default:
case NOT_EXISTS:
//continue with creating the setter
}

MethodDeclaration method = generateSetter((TypeDeclaration) fieldNode.up().get(), field, setterName, modifier, pos);
MethodDeclaration method = generateSetter((TypeDeclaration) fieldNode.up().get(), field, setterName, modifier, fluent, pos);

injectMethod(fieldNode.up(), method);

return true;
}

private MethodDeclaration generateSetter(TypeDeclaration parent, FieldDeclaration field, String name,
int modifier, ASTNode source) {
int modifier, boolean fluent, ASTNode source) {

int pS = source.sourceStart, pE = source.sourceEnd;
long p = (long)pS << 32 | pE;
MethodDeclaration method = new MethodDeclaration(parent.compilationResult);
Eclipse.setGeneratedBy(method, source);
method.modifiers = modifier;
method.returnType = TypeReference.baseTypeReference(TypeIds.T_void, 0);
if (fluent) {
method.returnType = new SingleTypeReference(parent.name, p);
} else {
method.returnType = TypeReference.baseTypeReference(TypeIds.T_void, 0);
}
method.returnType.sourceStart = pS; method.returnType.sourceEnd = pE;
Eclipse.setGeneratedBy(method.returnType, source);
method.annotations = null;
Expand All @@ -158,13 +180,27 @@ private MethodDeclaration generateSetter(TypeDeclaration parent, FieldDeclaratio

Annotation[] nonNulls = findAnnotations(field, TransformationsUtil.NON_NULL_PATTERN);
Annotation[] nullables = findAnnotations(field, TransformationsUtil.NULLABLE_PATTERN);

List<Statement> statements = new ArrayList<Statement>();

if (nonNulls.length == 0) {
method.statements = new Statement[] { assignment };
statements.add(assignment);
} else {
Statement nullCheck = generateNullCheck(field, source);
if (nullCheck != null) method.statements = new Statement[] { nullCheck, assignment };
else method.statements = new Statement[] { assignment };
if (nullCheck != null) statements.add(nullCheck);
statements.add(assignment);
}

if (fluent) {
Expression thisExpression = new ThisReference(pS, pE);
Eclipse.setGeneratedBy(thisExpression, source);
Statement returnStatement = new ReturnStatement(thisExpression, pS, pE);
Eclipse.setGeneratedBy(returnStatement, source);
statements.add(returnStatement);
}

method.statements = statements.toArray(new Statement[statements.size()]);

Annotation[] copiedAnnotations = copyAnnotations(nonNulls, nullables, source);
if (copiedAnnotations.length != 0) param.annotations = copiedAnnotations;
return method;
Expand Down
37 changes: 28 additions & 9 deletions src/core/lombok/javac/handlers/HandleSetter.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import com.sun.tools.javac.tree.JCTree.JCAnnotation;
import com.sun.tools.javac.tree.JCTree.JCAssign;
import com.sun.tools.javac.tree.JCTree.JCBlock;
import com.sun.tools.javac.tree.JCTree.JCClassDecl;
import com.sun.tools.javac.tree.JCTree.JCExpression;
import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
Expand Down Expand Up @@ -86,28 +87,32 @@ public void generateSetterForField(JavacNode fieldNode, DiagnosticPosition pos)
}
}

createSetterForField(AccessLevel.PUBLIC, fieldNode, fieldNode, false);
createSetterForField(AccessLevel.PUBLIC, fieldNode, fieldNode, false, false);
}

@Override public boolean handle(AnnotationValues<Setter> annotation, JCAnnotation ast, JavacNode annotationNode) {
markAnnotationAsProcessed(annotationNode, Setter.class);
JavacNode fieldNode = annotationNode.up();
AccessLevel level = annotation.getInstance().value();
Setter annotationInstance = annotation.getInstance();
AccessLevel level = annotationInstance.value();
boolean fluent = annotationInstance.fluent();

if (level == AccessLevel.NONE) return true;

return createSetterForField(level, fieldNode, annotationNode, true);
return createSetterForField(level, fieldNode, annotationNode, true, fluent);
}

private boolean createSetterForField(AccessLevel level,
JavacNode fieldNode, JavacNode errorNode, boolean whineIfExists) {
JavacNode fieldNode, JavacNode errorNode, boolean whineIfExists, boolean fluent) {
if (fieldNode.getKind() != Kind.FIELD) {
fieldNode.addError("@Setter is only supported on a field.");
return true;
}

JCVariableDecl fieldDecl = (JCVariableDecl)fieldNode.get();
String methodName = toSetterName(fieldDecl);

// TODO: handle clash between fluent & non-fluent setters
String methodName = fluent ? toFluentSetterName(fieldDecl) : toSetterName(fieldDecl);

switch (methodExists(methodName, fieldNode, false)) {
case EXISTS_BY_LOMBOK:
Expand All @@ -124,12 +129,12 @@ private boolean createSetterForField(AccessLevel level,

long access = toJavacModifier(level) | (fieldDecl.mods.flags & Flags.STATIC);

injectMethod(fieldNode.up(), createSetter(access, fieldNode, fieldNode.getTreeMaker()));
injectMethod(fieldNode.up(), createSetter(access, fluent, fieldNode, fieldNode.getTreeMaker()));

return true;
}

private JCMethodDecl createSetter(long access, JavacNode field, TreeMaker treeMaker) {
private JCMethodDecl createSetter(long access, boolean fluent, JavacNode field, TreeMaker treeMaker) {
JCVariableDecl fieldDecl = (JCVariableDecl) field.get();

JCFieldAccess thisX = treeMaker.Select(treeMaker.Ident(field.toName("this")), fieldDecl.name);
Expand All @@ -139,6 +144,7 @@ private JCMethodDecl createSetter(long access, JavacNode field, TreeMaker treeMa
List<JCAnnotation> nonNulls = findAnnotations(field, TransformationsUtil.NON_NULL_PATTERN);
List<JCAnnotation> nullables = findAnnotations(field, TransformationsUtil.NULLABLE_PATTERN);


if (nonNulls.isEmpty()) {
statements = List.<JCStatement>of(treeMaker.Exec(assign));
} else {
Expand All @@ -147,11 +153,24 @@ private JCMethodDecl createSetter(long access, JavacNode field, TreeMaker treeMa
else statements = List.<JCStatement>of(treeMaker.Exec(assign));
}

if (fluent) {
JCStatement returnStatement = treeMaker.Return(treeMaker.Ident(field.toName("this")));
statements = statements.append(returnStatement);
}

JCBlock methodBody = treeMaker.Block(0, statements);
Name methodName = field.toName(toSetterName(fieldDecl));
Name methodName = field.toName(fluent ? toFluentSetterName(fieldDecl) : toSetterName(fieldDecl));
JCVariableDecl param = treeMaker.VarDef(treeMaker.Modifiers(Flags.FINAL, nonNulls.appendList(nullables)), fieldDecl.name, fieldDecl.vartype, null);
//WARNING: Do not use field.getSymbolTable().voidType - that field has gone through non-backwards compatible API changes within javac1.6.
JCExpression methodType = treeMaker.Type(new JCNoType(TypeTags.VOID));
JCExpression methodType;
if (fluent) {
JCClassDecl classNode = (JCClassDecl) field.up().get();
// TODO: handle parameterized types
methodType = treeMaker.Ident(classNode.name);
} else {
methodType = treeMaker.Type(new JCNoType(TypeTags.VOID));
}


List<JCTypeParameter> methodGenericParams = List.nil();
List<JCVariableDecl> parameters = List.of(param);
Expand Down
11 changes: 11 additions & 0 deletions src/core/lombok/javac/handlers/JavacHandlerUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,17 @@ public static String toSetterName(JCVariableDecl field) {
return TransformationsUtil.toSetterName(fieldName);
}

/**
* @return the likely fluent setter name for the stated field. (e.g. private boolean foo; to setFoo).

This comment has been minimized.

Copy link
@rewbs

rewbs Apr 18, 2010

Owner

Forgot to change the example in the comment - the fluent setter name is just foo().

*
* Convenient wrapper around {@link TransformationsUtil#toFluentSetterName(CharSequence)}.
*/
public static String toFluentSetterName(JCVariableDecl field) {
CharSequence fieldName = field.name;

return TransformationsUtil.toFluentSetterName(fieldName);
}

/** Serves as return value for the methods that check for the existence of fields and methods. */
public enum MemberExistsResult {
NOT_EXISTS, EXISTS_BY_USER, EXISTS_BY_LOMBOK;
Expand Down
21 changes: 21 additions & 0 deletions test/delombok/resource/after/SetterFluent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
class SetterFluent {
int nonfluent;
int fluent;
int nonfluent_accessLevel;
int fluent_accessLevel;

public void setNonfluent(final int nonfluent) {
this.nonfluent = nonfluent;
}
public SetterFluent fluent(final int fluent) {
this.fluent = fluent;
return this;
}
private void setNonfluent_accessLevel(final int nonfluent_accessLevel) {
this.nonfluent_accessLevel = nonfluent_accessLevel;
}
private SetterFluent fluent_accessLevel(final int fluent_accessLevel) {
this.fluent_accessLevel = fluent_accessLevel;
return this;
}
}
7 changes: 7 additions & 0 deletions test/delombok/resource/before/SetterFluent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import lombok.Setter;
class SetterFluent {
@Setter(fluent=false) int nonfluent;
@Setter(fluent=true) int fluent;
@Setter(fluent=false, lombok.AccessLevel.PRIVATE) int nonfluent_accessLevel;
@Setter(fluent=true, lombok.AccessLevel.PRIVATE) int fluent_accessLevel;
}

0 comments on commit ee60a6c

Please sign in to comment.