diff --git a/.gitignore b/.gitignore
index c9dcccb544..7325f384dd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,3 +25,13 @@
/jvm.locations
/testenv
/gpg.keyring
+
+
+#eclipse files
+
+org.eclipse.core.resources.prefs
+org.eclipse.jdt.core.prefs
+target/
+.classpath
+.project
+.settings
\ No newline at end of file
diff --git a/src/core/lombok/Onstruct.java b/src/core/lombok/Onstruct.java
new file mode 100644
index 0000000000..acb641c7ba
--- /dev/null
+++ b/src/core/lombok/Onstruct.java
@@ -0,0 +1,135 @@
+package lombok;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * The {@link Onstruct} annotation declares variables based on getters of an
+ * object.
+ * The variables names are the one specified. If the annotation has parameters
+ * for prefix and suffix, those parameters are added to the variable
+ * names.
+ * The getter is the existing method in the object's class verifying
+ *
+ * - return non-void type
+ * - requires no argument
+ * - match the variable name specified, prefixed by get|is, and ignoring case.
+ * In the order :
+ *
+ * - getName is selected if exists
+ * - isName is selected if exists
+ * - getname (ignoring case) is selected if exists only ONE. compiling error
+ * if several found
+ * - isname (ignoring case) is selected if exists only ONE. compiling error if
+ * several found
+ * - name is selected if exists
+ * - name (ignoring case) is selected if exists only ONE. compiling error if
+ * several found
+ * - if no matching method exists, error
+ *
+ *
+ *
+ *
+ *
+ * It MUST only be applied to typed declarations. No garantee is present for var
+ * declaration.
+ *
+ *
+ *
+ *
+ * Before:
+ *
+ *
+ * @Onstruct(pre = "b_") Object author, name, editiondate, purchasable = mybook;
+ *
+ *
+ * After:
+ *
+ *
+ * var b_author = mybook.getAuthor();
+ * var b_name = mybook.getName();
+ * var b_editiondate = mybook.getEditionDate();
+ * var b_purchasable = mybook.isPurchasable();
+ *
+ *
+ */
+@Target(ElementType.LOCAL_VARIABLE)
+@Retention(RetentionPolicy.SOURCE)
+public @interface Onstruct {
+
+ //
+ // variable generation
+ //
+
+ /**
+ * prefix to start the created var name with. Default is empty
+ */
+ String pre() default "";
+
+ /**
+ * suffix to append to created var name. Default is empty
+ */
+ String suf() default "";
+
+ /**
+ * if true, should camel case the variable name. Only applied when prefix is
+ * non blank. Default is false.
+ */
+ boolean cml() default false;
+
+ //
+ // method generation
+ //
+
+ /**
+ * how to build the getter for a var name
+ */
+ public enum SourceType {
+ GET("get", true), BOOL("is", true), FLUENT("", false);
+
+ /** prefix to start the getter method with*/
+ public final String pre;
+
+ /** should we uppercase the first letter of the variable in the method name ? */
+ public final boolean cml;
+
+ SourceType(String pre, boolean cml) {
+ this.pre = pre;
+ this.cml = cml;
+ }
+ }
+
+ public SourceType source() default SourceType.GET;
+
+ public enum Cml {
+ CML(true), NOCML(false), SOURCE(null);
+ ;
+
+ public final Boolean cml;
+
+ Cml(Boolean cml) {
+ this.cml = cml;
+ }
+ }
+
+ /**
+ * can't set default value to null or non-constant values :/
+ */
+ static final String NULLSTRING = "NULLSTRING";
+
+ /**
+ * overwrite the {@link #source()} prefix to start the getter call by.
+ * Default is {@link #NULLSTRING} to not overwrite
+ */
+ String methodPre() default NULLSTRING;
+
+ /**
+ * Overwrite the {@link #source()}'s method camel case. If
+ * {@link Cml#SOURCE}(default), don't overwrite. If {@link Cml#CML}, should
+ * camel case the method call. If {@link Cml#NOCML}, don't camel case it.
+ */
+ Cml methodCml() default Cml.SOURCE;
+
+}
diff --git a/src/core/lombok/core/handlers/OnstructUtils.java b/src/core/lombok/core/handlers/OnstructUtils.java
new file mode 100644
index 0000000000..eae92e8505
--- /dev/null
+++ b/src/core/lombok/core/handlers/OnstructUtils.java
@@ -0,0 +1,28 @@
+package lombok.core.handlers;
+
+import lombok.Onstruct;
+import lombok.Onstruct.Cml;
+
+public class OnstructUtils {
+
+ public static String varName(String requestedName, Onstruct instance) {
+ String prefix = instance.pre();
+ String suffix = instance.suf();
+ boolean cml = instance.cml() && prefix != null && !prefix.isEmpty();
+ return (prefix != null ? prefix : "") + (cml ? cml(requestedName) : requestedName) + (suffix != null ? suffix : "");
+ }
+
+ public static String methodName(String requestedName, Onstruct instance) {
+ String methodPrefix = instance.source().pre;
+ if (!instance.methodPre().equals(Onstruct.NULLSTRING)) methodPrefix = instance.methodPre();
+ boolean cml = instance.source().cml;
+ if (instance.methodCml() != Cml.SOURCE) cml = instance.methodCml().cml;
+ if (methodPrefix == null || methodPrefix.isEmpty()) methodPrefix = "";
+ return methodPrefix + (cml ? cml(requestedName) : requestedName);
+ }
+
+ public static String cml(String name) {
+ return name.substring(0, 1).toUpperCase() + name.substring(1);
+ }
+
+}
diff --git a/src/core/lombok/eclipse/handlers/HandleOnstruct.java b/src/core/lombok/eclipse/handlers/HandleOnstruct.java
new file mode 100644
index 0000000000..059c6d34f8
--- /dev/null
+++ b/src/core/lombok/eclipse/handlers/HandleOnstruct.java
@@ -0,0 +1,30 @@
+package lombok.eclipse.handlers;
+
+import java.io.PrintStream;
+
+import org.eclipse.jdt.internal.compiler.ast.Annotation;
+
+import lombok.Onstruct;
+import lombok.core.AnnotationValues;
+import lombok.core.HandlerPriority;
+import lombok.eclipse.DeferUntilPostDiet;
+import lombok.eclipse.EclipseASTVisitor;
+import lombok.eclipse.EclipseAnnotationHandler;
+import lombok.eclipse.EclipseNode;
+import lombok.spi.Provides;
+
+@Provides
+@DeferUntilPostDiet
+@HandlerPriority(65536) // same as HandleValue // TODO
+public class HandleOnstruct extends EclipseAnnotationHandler {
+
+ public static final HandleOnstruct INSTANCE = new HandleOnstruct();
+
+ @Override public void handle(AnnotationValues annotation, Annotation ast, EclipseNode annotationNode) {
+ PrintStream stream = System.out;
+ stream.println("got annotation on " + ast);
+ annotationNode.up().traverse(new EclipseASTVisitor.Printer(true, stream, true));
+ }
+
+
+}
diff --git a/src/core/lombok/javac/handlers/HandleOnstruct.java b/src/core/lombok/javac/handlers/HandleOnstruct.java
new file mode 100644
index 0000000000..1cdadc66eb
--- /dev/null
+++ b/src/core/lombok/javac/handlers/HandleOnstruct.java
@@ -0,0 +1,134 @@
+package lombok.javac.handlers;
+
+import static lombok.javac.handlers.JavacHandlerUtil.deleteAnnotationIfNeccessary;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import com.sun.tools.javac.tree.JCTree;
+import com.sun.tools.javac.tree.JCTree.JCAnnotation;
+import com.sun.tools.javac.tree.JCTree.JCExpression;
+import com.sun.tools.javac.tree.JCTree.JCIdent;
+import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
+
+import lombok.Onstruct;
+import lombok.core.AST.Kind;
+import lombok.core.AnnotationValues;
+import lombok.core.HandlerPriority;
+import lombok.core.LombokNode;
+import lombok.core.handlers.OnstructUtils;
+import lombok.eclipse.DeferUntilPostDiet;
+import lombok.javac.JavacAnnotationHandler;
+import lombok.javac.JavacNode;
+import lombok.javac.JavacTreeMaker;
+import lombok.spi.Provides;
+
+@Provides
+@DeferUntilPostDiet
+@HandlerPriority(65536) // same as HandleValue // TODO
+public class HandleOnstruct extends JavacAnnotationHandler {
+
+
+ /**
+ * find the siblings with same kind and annotation. Copy of
+ * {@link LombokNode#upFromAnnotationToFields()} with same kind and no check
+ * on the parent.
+ *
+ */
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ public static Collection upFromAnnotationToSameKind(JavacNode node) {
+ if (node.getKind() != Kind.ANNOTATION) return Collections.emptyList();
+ JavacNode declaration = node.up();
+ if (declaration == null) return Collections.emptyList();
+
+ List fields = new ArrayList();
+
+ for (JavacNode potentialField : declaration.up().down()) {
+ if (potentialField.getKind() != declaration.getKind()) continue;
+ for (JavacNode child : potentialField.down()) {
+ if (child.getKind() != Kind.ANNOTATION) continue;
+ if (child.get() == node.get()) fields.add(potentialField);
+ }
+ }
+
+ return fields;
+ }
+
+ /**
+ * retrieve the children statements from a list of node
+ */
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ protected static List findChildrenStatements(Collection fields) {
+ List ret = new ArrayList();
+ for (JavacNode f : fields) {
+ for (JavacNode potentialStatement : f.down()) {
+ if (potentialStatement.getKind() == Kind.STATEMENT) {
+ ret.add(potentialStatement.get());
+ }
+ }
+ }
+ return ret;
+ }
+
+ @Override public void handle(AnnotationValues annotation, JCAnnotation ast, JavacNode annotationNode) {
+ Collection annotatedVariables = upFromAnnotationToSameKind(annotationNode);
+ JavacNode parentNode = annotationNode.up();
+ Onstruct annotationInstance = annotation.getInstance();
+ deleteAnnotationIfNeccessary(annotationNode, Onstruct.class);
+
+ List statements = findChildrenStatements(annotatedVariables);
+ // sanity checks on statements. Among the variables declaration, there
+ // must be exactly one statement.
+ if (statements.isEmpty()) {
+ annotationNode.addError("no assignment. Requires one identifier assignment.");
+ return;
+ }
+ if (statements.size() > 1) {
+ annotationNode.addError("Too many assignments:" + statements + " Requires exactly one identifier assignment.");
+ return;
+ }
+
+ JCTree tree = statements.get(0);
+ JCTree.JCIdent ident;
+ String varName = null;
+ // sanity checks on the assignment. It must be an identifier.
+ if (tree instanceof JCTree.JCIdent) {
+ ident = (JCTree.JCIdent) tree;
+ varName = (ident.name.toString());
+ } else {
+ annotationNode.addError("invalid assignment" + tree + " : must be an identifier");
+ return;
+ }
+ if (varName == null) {
+ annotationNode.addError("assignement is null . Must be an identifier");
+ return;
+ }
+
+ for (JavacNode f : annotatedVariables) {
+ handleVarDeclaration(f, annotationInstance, parentNode, ident);
+ }
+ // parentNode.rebuild();
+ }
+
+ private void handleVarDeclaration(JavacNode varNode, Onstruct annotationInstance, JavacNode parentNode, JCIdent ident) {
+ String varName = OnstructUtils.varName(varNode.getName(), annotationInstance);
+ String methName = OnstructUtils.methodName(varNode.getName(), annotationInstance);
+ JCTree elem = varNode.get();
+ JavacTreeMaker maker = varNode.getTreeMaker();
+ // System.out.println("create : var " + varName + " = " + ident.name + "." + methName + "();");
+ if (elem instanceof JCVariableDecl) {
+ JCVariableDecl variable = (JCVariableDecl) elem;
+ variable.type = JavacHandlerUtil.chainDots(varNode, "lombok", "var").type;
+ JCExpression methCall = maker.Select(ident, varNode.toName(methName));
+ variable.init = maker.Apply(com.sun.tools.javac.util.List.nil(),
+ methCall,
+ com.sun.tools.javac.util.List.nil());
+ variable.name = varNode.toName(varName);
+ System.out.println("replaced with " + variable);
+ } else
+ System.err.println(varNode.get());
+ }
+
+}
diff --git a/test/transform/resource/before/OnstructBook.java b/test/transform/resource/before/OnstructBook.java
new file mode 100644
index 0000000000..a834852ca6
--- /dev/null
+++ b/test/transform/resource/before/OnstructBook.java
@@ -0,0 +1,49 @@
+import java.util.Date;
+import java.util.Objects;
+
+import lombok.AllArgsConstructor;
+import lombok.Onstruct;
+import lombok.Onstruct.Cml;
+import lombok.Onstruct.SourceType;
+import lombok.core.PrintAST;
+import lombok.experimental.Accessors;
+
+public class OnstructBook {
+
+ @lombok.Getter
+ @AllArgsConstructor
+ public static class Book {
+
+ private String author;
+ @Accessors(fluent = true)
+ private String name;
+ private Date editionDate;
+ private boolean purchasable;
+
+ }
+
+ void test() {
+ Book mybook = new Book("author0", "bookname0", new Date(), true);
+ @Onstruct
+ Object author, editionDate = mybook;// var author = mybook.getAuthor()
+ @Onstruct(source=SourceType.FLUENT)
+ Object name = mybook;// var name = mybook.name()
+ @Onstruct(source=SourceType.BOOL)
+ Object purchasable = mybook;// var purchasable = mybook.isPurchasable()
+ }
+
+ void testVarPrefix() {
+ Book mybook = new Book("author0", "bookname0", new Date(), true);
+ @Onstruct(pre = "b_")
+ Object author, editionDate = mybook;
+ @Onstruct(pre="b_", methodPre = "is")
+ Object purchasable = mybook;
+ }
+
+ void testOverWriteSourceType() {
+ Book mybook = new Book("author0", "bookname0", new Date(), true);
+ @Onstruct(source = SourceType.FLUENT, methodCml = Cml.CML)
+ Object name=mybook; // var name = mybook.Name()
+ }
+
+}