Contents

[Java] 1. Lombok

[Java] 1. Lombok

Introduction

  • Lombok is a Java library that can automatically generate repetitive code for Java classes, such as getter, setter, equals, and hashCode methods.

Principles

  • Lombok’s working principle is based on Annotation Processors and the Java Compiler API.

  • When Lombok is discovered by the compiler, it uses annotation processors to modify Java code. During this process, Lombok checks for specific annotations in classes and generates corresponding code based on these annotations, such as getter and setter methods.

  • Specifically, Lombok uses the Apache BCEL (Bytecode Engineering Library) to directly manipulate Java bytecode, rather than through reflection or runtime operations. This way, Lombok can make modifications during compilation, thus improving performance and efficiency.

Annotations

  • Define a clean Node class, after compilation it looks like:
1
2
3
4
public class Node {
    private Long item1;
    private String item2;
}
1
2
3
4
5
6
7
public class Node {
    private Long item1;
    private String item2;

    public Node() {
    }
}
  • To explain, the Java compiler automatically provides a no-argument constructor because any class cannot be without a constructor.

Entity Classes

@Data & @EqualsAndHashCode & @Getter & @Setter & @ToString

1
2
3
4
5
@Data
public class Node {
    private Long item1;
    private String item2;
}
  • Our most commonly used @Data is actually a collection of basic annotations, such as @Getter, @Setter, @EqualsAndHashCode, @ToString.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
public class Node {
  private Long item1;
  private String item2;

  public Node() {
  }

  public Long getItem1() {
    return this.item1;
  }

  public String getItem2() {
    return this.item2;
  }

  public void setItem1(Long item1) {
    this.item1 = item1;
  }

  public void setItem2(String item2) {
    this.item2 = item2;
  }

  public boolean equals(Object o) {
    if (o == this) {
      return true;
    } else if (!(o instanceof Node)) {
      return false;
    } else {
      Node other = (Node)o;
      if (!other.canEqual(this)) {
        return false;
      } else {
        Object this$item1 = this.getItem1();
        Object other$item1 = other.getItem1();
        if (this$item1 == null) {
          if (other$item1 != null) {
            return false;
          }
        } else if (!this$item1.equals(other$item1)) {
          return false;
        }

        Object this$item2 = this.getItem2();
        Object other$item2 = other.getItem2();
        if (this$item2 == null) {
          if (other$item2 != null) {
            return false;
          }
        } else if (!this$item2.equals(other$item2)) {
          return false;
        }

        return true;
      }
    }
  }

  protected boolean canEqual(Object other) {
    return other instanceof Node;
  }

  public int hashCode() {
    int PRIME = true;
    int result = 1;
    Object $item1 = this.getItem1();
    result = result * 59 + ($item1 == null ? 43 : $item1.hashCode());
    Object $item2 = this.getItem2();
    result = result * 59 + ($item2 == null ? 43 : $item2.hashCode());
    return result;
  }

  public String toString() {
    return "Node(item1=" + this.getItem1() + ", item2=" + this.getItem2() + ")";
  }
}
  • Here are some basic advanced usages:
1
2
3
4
5
6
7
8
@Getter
@Setter
public class Node {
    @Getter(AccessLevel.PRIVATE)
    private Long item1;
    @Setter(AccessLevel.PRIVATE)
    private String item2;
}
  • You can see that setting the corresponding PRIVATE permission methods become private. This helps with centralized permission management.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Node {
    private Long item1;
    private String item2;

    public Node() {
    }

    public String getItem2() {
        return this.item2;
    }

    public void setItem1(Long item1) {
        this.item1 = item1;
    }

    private Long getItem1() {
        return this.item1;
    }

    private void setItem2(String item2) {
        this.item2 = item2;
    }
}

@AllArgsConstructor & @NoArgsConstructor & @RequiredArgsConstructor

1
2
3
4
5
6
@NoArgsConstructor
@AllArgsConstructor
public class Node {
    private Long item1;
    private String item2;
}
  • Obviously, this will generate a no-argument constructor and an all-arguments constructor. The required arguments constructor would conflict with the no-argument constructor, so it’s shown separately.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class Node {
    private Long item1;
    private String item2;

    public Node() {
    }

    public Node(Long item1, String item2) {
        this.item1 = item1;
        this.item2 = item2;
    }
}
  • Required arguments constructor
1
2
3
4
5
6
@RequiredArgsConstructor
@AllArgsConstructor
public class Node {
    private final Long item1;
    private String item2;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class Node {
    private final Long item1;
    private String item2;

    public Node(Long item1) {
        this.item1 = item1;
    }

    public Node(Long item1, String item2) {
        this.item1 = item1;
        this.item2 = item2;
    }
}
  • Here’s the most classic combination:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@Service
@Slf4j
@RequiredArgsConstructor
public class Node {
    private final Long item1;
    private final Long item2;
    private final Long item3;
    @Autowired
    @Lazy
    private String lazyBean;
}
  • Output:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@Service
public class Node {
    private static final Logger log = LoggerFactory.getLogger(Node.class);
    private final Long item1;
    private final Long item2;
    private final Long item3;
    @Autowired
    @Lazy
    private String lazyBean;

    public Node(Long item1, Long item2, Long item3) {
        this.item1 = item1;
        this.item2 = item2;
        this.item3 = item3;
    }
}
  • You can see that beans that could originally be initialized can still be initialized, and those with conflicts or needing lazy loading will still follow lazy loading.

  • Advanced usage:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@Service
@Slf4j
@RequiredArgsConstructor(onConstructor_ = @JsonCreator)
public class Node {
    private final Long item1;
    private final Long item2;
    private final Long item3;
    @Autowired
    @Lazy
    private String lazyBean;
}
  • At this point, the generated constructor will have the corresponding annotation. This syntax is also Java’s built-in syntax.
  • Another point to mention: JSON serialization and deserialization work by first constructing the object, then setting the object, so you must have a no-argument constructor and setters.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@Service
public class Node {
    private static final Logger log = LoggerFactory.getLogger(Node.class);
    private final Long item1;
    private final Long item2;
    private final Long item3;
    @Autowired
    @Lazy
    private String lazyBean;

    @JsonCreator
    public Node(Long item1, Long item2, Long item3) {
        this.item1 = item1;
        this.item2 = item2;
        this.item3 = item3;
    }
}

@Builder & @SuperBuilder & @Singular

  • As we know, @Builder’s principle is to first store data, then uniformly call the all-arguments constructor. So remember to add the corresponding all-arguments constructor.
1
2
3
4
5
6
7
@AllArgsConstructor
@Builder
public class Node {
    private Long item1;
    private Long item2;
    private Long item3;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class Node {
    private Long item1;
    private Long item2;
    private Long item3;

    public static NodeBuilder builder() {
        return new NodeBuilder();
    }

    public Node(Long item1, Long item2, Long item3) {
        this.item1 = item1;
        this.item2 = item2;
        this.item3 = item3;
    }

    public static class NodeBuilder {
        private Long item1;
        private Long item2;
        private Long item3;

        NodeBuilder() {
        }

        public NodeBuilder item1(Long item1) {
            this.item1 = item1;
            return this;
        }

        public NodeBuilder item2(Long item2) {
            this.item2 = item2;
            return this;
        }

        public NodeBuilder item3(Long item3) {
            this.item3 = item3;
            return this;
        }

        public Node build() {
            return new Node(this.item1, this.item2, this.item3);
        }

        public String toString() {
            return "Node.NodeBuilder(item1=" + this.item1 + ", item2=" + this.item2 + ", item3=" + this.item3 + ")";
        }
    }
}
  • Let’s show some advanced features:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@ToString
@AllArgsConstructor
@Builder(toBuilder = true)
public class Node {
    private Long item1;
    @Singular
    private Map<Long, Long> item2s;
    @Singular
    private List<Long> item3s;
}
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
public class Node {
    private Long item1;
    private Map<Long, Long> item2s;
    private List<Long> item3s;

    public static NodeBuilder builder() {
        return new NodeBuilder();
    }

    public NodeBuilder toBuilder() {
        NodeBuilder builder = (new NodeBuilder()).item1(this.item1);
        if (this.item2s != null) {
            builder.item2s(this.item2s);
        }

        if (this.item3s != null) {
            builder.item3s(this.item3s);
        }

        return builder;
    }

    public String toString() {
        return "Node(item1=" + this.item1 + ", item2s=" + this.item2s + ", item3s=" + this.item3s + ")";
    }

    public Node(Long item1, Map<Long, Long> item2s, List<Long> item3s) {
        this.item1 = item1;
        this.item2s = item2s;
        this.item3s = item3s;
    }

    public static class NodeBuilder {
        private Long item1;
        private ArrayList<Long> item2s$key;
        private ArrayList<Long> item2s$value;
        private ArrayList<Long> item3s;

        NodeBuilder() {
        }

        public NodeBuilder item1(Long item1) {
            this.item1 = item1;
            return this;
        }

        public NodeBuilder item2(Long item2Key, Long item2Value) {
            if (this.item2s$key == null) {
                this.item2s$key = new ArrayList();
                this.item2s$value = new ArrayList();
            }

            this.item2s$key.add(item2Key);
            this.item2s$value.add(item2Value);
            return this;
        }

        public NodeBuilder item2s(Map<? extends Long, ? extends Long> item2s) {
            if (item2s == null) {
                throw new NullPointerException("item2s cannot be null");
            } else {
                if (this.item2s$key == null) {
                    this.item2s$key = new ArrayList();
                    this.item2s$value = new ArrayList();
                }

                Iterator var2 = item2s.entrySet().iterator();

                while(var2.hasNext()) {
                    Map.Entry<? extends Long, ? extends Long> $lombokEntry = (Map.Entry)var2.next();
                    this.item2s$key.add($lombokEntry.getKey());
                    this.item2s$value.add($lombokEntry.getValue());
                }

                return this;
            }
        }

        public NodeBuilder clearItem2s() {
            if (this.item2s$key != null) {
                this.item2s$key.clear();
                this.item2s$value.clear();
            }

            return this;
        }

        public NodeBuilder item3(Long item3) {
            if (this.item3s == null) {
                this.item3s = new ArrayList();
            }

            this.item3s.add(item3);
            return this;
        }

        public NodeBuilder item3s(Collection<? extends Long> item3s) {
            if (item3s == null) {
                throw new NullPointerException("item3s cannot be null");
            } else {
                if (this.item3s == null) {
                    this.item3s = new ArrayList();
                }

                this.item3s.addAll(item3s);
                return this;
            }
        }

        public NodeBuilder clearItem3s() {
            if (this.item3s != null) {
                this.item3s.clear();
            }

            return this;
        }

        public Node build() {
            Map item2s;
            switch (this.item2s$key == null ? 0 : this.item2s$key.size()) {
                case 0:
                    item2s = Collections.emptyMap();
                    break;
                case 1:
                    item2s = Collections.singletonMap(this.item2s$key.get(0), this.item2s$value.get(0));
                    break;
                default:
                    Map<Long, Long> item2s = new LinkedHashMap(this.item2s$key.size() < 1073741824 ? 1 + this.item2s$key.size() + (this.item2s$key.size() - 3) / 3 : Integer.MAX_VALUE);

                    for(int $i = 0; $i < this.item2s$key.size(); ++$i) {
                        item2s.put(this.item2s$key.get($i), (Long)this.item2s$value.get($i));
                    }

                    item2s = Collections.unmodifiableMap(item2s);
            }

            List item3s;
            switch (this.item3s == null ? 0 : this.item3s.size()) {
                case 0:
                    item3s = Collections.emptyList();
                    break;
                case 1:
                    item3s = Collections.singletonList(this.item3s.get(0));
                    break;
                default:
                    item3s = Collections.unmodifiableList(new ArrayList(this.item3s));
            }

            return new Node(this.item1, item2s, item3s);
        }

        public String toString() {
            return "Node.NodeBuilder(item1=" + this.item1 + ", item2s$key=" + this.item2s$key + ", item2s$value=" + this.item2s$value + ", item3s=" + this.item3s + ")";
        }
    }
}
  • The usage is as follows. Here toBuilder().build() is a deep copy more efficient than BeanUtils.copy. However, internal objects are not deep copied.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public static void main(String[] args) {
    Node.builder()
        .item1(1L)
        .item2(2L, 3L)
        .item2(3L, 4L)
        .item3(5L)
        .item3(6L)
        .build()
        .toBuilder()
        .build();
}
  • In comparison, the builder pattern is clearer and more readable. The only downside is needing to construct a lightweight Builder object.
  • Overall, I recommend using Builder to create objects. The core is to use toBuilder and Singular to ensure the original object doesn’t change.

@Slf4j

  • Nothing much to say, everyone knows this.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@Slf4j
public class Node {
  private Long item1;
  private Long item2;
  private Long item3;
}


public class Node {
    private static final Logger log = LoggerFactory.getLogger(Node.class);
    private Long item1;
    private Long item2;
    private Long item3;

    public Node() {
    }
}

How to Customize

  • Lombok’s principle is to use Java’s built-in compilation API for enhancement, so we can follow this approach for our own enhancements.
  • The core is to inherit and implement the javax.annotation.processing.AbstractProcessor class.
  • Then through this class’s process method, modify the internal JavacTrees related variables.
  • I’ve written a common base class for graphs, let me briefly introduce it:
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
public abstract class BaseProcessor<T extends Annotation> extends AbstractProcessor {

    /** annotation type */
    protected Class<T> clazz;
    /** javac trees */
    protected JavacTrees trees;
    /** AST */
    protected TreeMaker treeMaker;
    /** mark name */
    protected Names names;
    /** log */
    protected Messager messager;
    /** filer */
    protected Filer filer;
    /** the jcTrees generated by annotation to add */
    protected List<JCTree> annotationJCTrees;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        // transfer type T to Class
        final Type superclass = getClass().getGenericSuperclass();
        if (superclass instanceof ParameterizedType) {
            this.clazz = (Class<T>) ((ParameterizedType) superclass).getActualTypeArguments()[0];
        } else {
            this.clazz = null;
        }
        this.trees = JavacTrees.instance(processingEnv);
        this.messager = processingEnv.getMessager();
        this.filer = processingEnv.getFiler();
        final Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
        this.treeMaker = TreeMaker.instance(context);
        this.names = Names.instance(context);
        // init list
        annotationJCTrees = List.nil();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        roundEnv.getElementsAnnotatedWith(this.clazz)
                .stream()
                .map(element -> trees.getTree(element))
                // NOTE(goody): 2022/5/5
                // tree is the class input. Modify the `JCTree` to modify the method or argus
                // `visitClassDef` runs after than `visitAnnotation`, so method `visitAnnotation` can add `annotationJCTrees` to
                // `annotationJCTrees`. `visitClassDef` will prepend all
                .forEach(tree -> tree.accept(new TreeTranslator() {
                    @Override
                    public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
                        // NOTE(goody): 2022/5/4 https://stackoverflow.com/questions/46874126/java-lang-assertionerror-thrown-by-compiler-when-adding-generated-method-with-pa
                        // setMethod var is a new Object from jcVariable, the pos should be reset to jcClass
                        treeMaker.at(jcClassDecl.pos);

                        // generate the new method or variable or something else
                        final List<JCTree> jcTrees = generate(jcClassDecl);
                        jcClassDecl.defs = jcClassDecl.defs.prependList(jcTrees);

                        // add all elements in `annotationJCTrees`
                        jcClassDecl.defs = jcClassDecl.defs.prependList(annotationJCTrees);

                        super.visitClassDef(jcClassDecl);
                    }

                    @Override
                    public void visitMethodDef(JCTree.JCMethodDecl jcMethodDecl) {
                        if (isModify(jcMethodDecl)) {
                            super.visitMethodDef(modifyDecl(jcMethodDecl));
                        } else {
                            super.visitMethodDef(jcMethodDecl);
                        }
                    }

                    @Override
                    public void visitVarDef(JCTree.JCVariableDecl jcVariableDecl) {
                        if (isModify(jcVariableDecl)) {
                            super.visitVarDef(modifyDecl(jcVariableDecl));
                        } else {
                            super.visitVarDef(jcVariableDecl);
                        }
                    }

                    @Override
                    public void visitAnnotation(JCTree.JCAnnotation jcAnnotation) {
                        super.visitAnnotation(jcAnnotation);

                        final JCTree.JCAssign[] jcAssigns = jcAnnotation.getArguments()
                                .stream()
                                .filter(argu -> argu.getKind().equals(Tree.Kind.ASSIGNMENT))
                                .map(argu -> (JCTree.JCAssign) argu)
                                .toArray(JCTree.JCAssign[]::new);

                        if (jcAssigns.length > 0) {
                            annotationGenerateJCTree(handleJCAssign(List.from(jcAssigns)));
                        }
                    }
                }));
        return true;
    }

    /**
     * subclass should implement this method to add method or variable or others
     *
     * @param jcClassDecl jcClassDecl
     * @return new JCTree list
     */
    private List<JCTree> generate(JCTree.JCClassDecl jcClassDecl) {
        final JCTree[] trees = generate()
                .toArray(JCTree[]::new);

        // method Trees
        final JCTree[] methodTrees = jcClassDecl.defs
                .stream()
                .filter(k -> k.getKind().equals(Tree.Kind.METHOD))
                .map(tree -> (JCTree.JCMethodDecl) tree)
                .flatMap(jcMethodDecl -> handleDecl(jcMethodDecl))
                .toArray(JCTree[]::new);

        // variable trees
        final JCTree[] variableTrees = jcClassDecl.defs
                .stream()
                .filter(k -> k.getKind().equals(Tree.Kind.VARIABLE))
                .map(tree -> (JCTree.JCVariableDecl) tree)
                .flatMap(jcVariable -> handleDecl(jcVariable))
                .toArray(JCTree[]::new);

        return List.from(trees)
                .prependList(List.from(variableTrees))
                .prependList(List.from(methodTrees));
    }

    /**
     * check if the method need to be modified. default false
     *
     * @param jcMethodDecl jcmethodDecl
     * @return true -> need to be modified ; false -> need not to be
     */
    protected boolean isModify(JCTree.JCMethodDecl jcMethodDecl) {
        return false;
    }

    /**
     * modify the jcMethodDecl input.
     *
     * @param jcMethodDecl metaDecl
     * @return point Decl
     */
    protected JCTree.JCMethodDecl modifyDecl(JCTree.JCMethodDecl jcMethodDecl) {
        return jcMethodDecl;
    }

    /**
     * check if the method need to be modified. default false
     *
     * @param jcVariableDecl jcmethodDecl
     * @return true -> need to be modified ; false -> need not to be
     */
    protected boolean isModify(JCTree.JCVariableDecl jcVariableDecl) {
        return false;
    }

    /**
     * modify the jcVariableDecl input.
     *
     * @param jcVariableDecl metaDecl
     * @return point Decl
     */
    protected JCTree.JCVariableDecl modifyDecl(JCTree.JCVariableDecl jcVariableDecl) {
        return jcVariableDecl;
    }

    /**
     * generate with nothing
     *
     * @return Steam of JCTree to add
     */
    protected Stream<JCTree> generate() {
        return Stream.empty();
    }

    /**
     * every jcMethodDecl will input to
     *
     * @param jcMethodDecl jcMethodDecl
     * @return Steam of JCTree to add
     */
    protected Stream<JCTree> handleDecl(JCTree.JCMethodDecl jcMethodDecl) {
        return Stream.empty();
    }

    /**
     * every jcVariableDecl will input to
     *
     * @param jcVariableDecl jcVariableDecl
     * @return Steam of JCTree to add
     */
    protected Stream<JCTree> handleDecl(JCTree.JCVariableDecl jcVariableDecl) {
        return Stream.empty();
    }

    /**
     * every annotation argu will input to
     *
     * @param jcAssign jcAssign
     */
    protected List<JCTree> handleJCAssign(List<JCTree.JCAssign> jcAssign) {
        return List.nil();
    }

    protected final void annotationGenerateJCTree(JCTree jcTree) {
        this.annotationJCTrees = this.annotationJCTrees.prepend(jcTree);
    }

    protected final void annotationGenerateJCTree(List<JCTree> jcTrees) {
        this.annotationJCTrees = this.annotationJCTrees.prependList(jcTrees);
    }
}
  • It’s relatively complex, but you can continue to use it directly.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE})
public @interface LogSelf {
}

@SupportedAnnotationTypes({"com.goody.utils.qianliang.processor.impl.LogSelf"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@AutoService(Processor.class)
public class ZLogSelfProcessor extends BaseProcessor<LogSelf> {

    @Override
    protected Stream<JCTree> generate() {
        try {
            return Stream.of(this.logSelf());
        } catch (Exception e) {
            return Stream.empty();
        }
    }

    /**
     * <pre>
     *  <@javax.annotation.PostConstruct()
     *  public void logSelf() {
     *      log.warn("---------- {}", this);
     *  }
     * </pre>
     */
    private JCTree logSelf() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        JCTree.JCAnnotation jcAnnotation = treeMaker.Annotation(chainDots("javax.annotation.PostConstruct"), List.nil());
        JCModifiers jcModifiers = treeMaker.Modifiers(Flags.PUBLIC, List.of(jcAnnotation));
        Name methodName = getNameFromString("logSelf");
        JCExpression returnType = treeMaker.Type((Type) (Class.forName("com.sun.tools.javac.code.Type$JCVoidType").newInstance()));
        List<JCTypeParameter> typeParameters = List.nil();
        List<JCVariableDecl> parameters = List.nil();
        List<JCExpression> throwsClauses = List.nil();
        JCBlock jcBlock = treeMaker.Block(0, List.of(treeMaker.Exec(
            treeMaker.Apply(List.nil(), chainDots("log.warn"), List.of(treeMaker.Literal("---------- {}"), treeMaker.Ident(getNameFromString("this")))))));
        JCMethodDecl method = treeMaker
            .MethodDef(jcModifiers, methodName, returnType, typeParameters, parameters, throwsClauses, jcBlock, null);
        return method;
    }

    private Name getNameFromString(String s) {
        return names.fromString(s);
    }

    public JCExpression chainDots(String element) {
        JCExpression e = null;
        String[] elems = element.split("\\.");
        for (int i = 0; i < elems.length; i++) {
            e = e == null ? treeMaker.Ident(names.fromString(elems[i]))
                : treeMaker.Select(e, names.fromString(elems[i]));
        }
        return e;
    }
}