├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── innerbuilder.jar
├── pom.xml
├── prepare-build.sh
├── screenshot.png
└── src
└── main
├── java
└── org
│ └── jetbrains
│ └── plugins
│ └── innerbuilder
│ ├── CheckboxSelectorOption.java
│ ├── DropdownListCellRenderer.java
│ ├── DropdownSelectorOption.java
│ ├── DropdownSelectorOptionValue.java
│ ├── InnerBuilderAction.java
│ ├── InnerBuilderCollector.java
│ ├── InnerBuilderGenerator.java
│ ├── InnerBuilderHandler.java
│ ├── InnerBuilderOption.java
│ ├── InnerBuilderOptionSelector.java
│ ├── InnerBuilderUtils.java
│ └── SelectorOption.java
└── resources
└── META-INF
├── plugin.xml
├── pluginIcon.svg
└── pluginIcon_dark.svg
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | *.iml
3 | .idea
4 | out/
5 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 | jdk: openjdk17
3 | before_install:
4 | - chmod +x prepare-build.sh
5 | - ./prepare-build.sh
6 | script: mvn package
7 | after_success:
8 | - cd /tmp
9 | - git clone https://${GH_USER_NAME}:${GH_TOKEN}@github.com/${GH_USER_NAME}/${GH_PROJECT_NAME} innerbuilder
10 | - cd innerbuilder
11 | - cp $TRAVIS_BUILD_DIR/innerbuilder.jar .
12 | - git config --global user.name $GIT_AUTHOR_NAME
13 | - git config --global user.email $GIT_AUTHOR_EMAIL
14 | - git add innerbuilder.jar
15 | - git commit -m "[ci skip] Committed by Travis-CI"
16 | - git push https://${GH_USER_NAME}:${GH_TOKEN}@github.com/${GH_USER_NAME}/${GH_PROJECT_NAME}
17 | env:
18 | global:
19 | - GH_USER_NAME: analytically
20 | - GH_PROJECT_NAME: innerbuilder
21 | - GIT_AUTHOR_NAME: TravisCI
22 | - GIT_AUTHOR_EMAIL: noreply@travis-ci.org
23 | - CI_HOME=`pwd`/$TRAVIS_REPO_SLUG
24 | - secure: LXAjFSAiRS5x2bokW3Z07Xq2iOuwXPdrgPUpGZjKIltrzkPASvCyZZFAnNqnDAEkdfQAxnPGHPory53PkplakwnpVa8z9Ehw33sH5qMBsBxGJAtH+x7VEfpUdtw83hsU/mBD9D5evSyjN87rUABXX5BuM0l6gHprkGuxdNA7CEc=
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | innerbuilder [](https://travis-ci.org/analytically/innerbuilder)
2 | ============
3 |
4 | [IntelliJ IDEA](https://www.jetbrains.com/idea/) [plugin](https://plugins.jetbrains.com/plugin/7354-innerbuilder/) that
5 | adds a 'Builder' action to the Generate menu (Alt+Insert) which generates an inner builder class as described in
6 | the [Effective Java book](https://www.oreilly.com/library/view/effective-java-3rd/9780134686097/). Works with
7 | [IntelliJ IDEA](https://www.jetbrains.com/idea/) 2019 and later.
8 |
9 | 
10 |
11 | ```java
12 | public class JavaBean {
13 | private final String foo;
14 | private String bar;
15 | private int qux;
16 | private Double x,y;
17 |
18 | private JavaBean(Builder builder) {
19 | foo = builder.foo;
20 | bar = builder.bar;
21 | qux = builder.qux;
22 | x = builder.x;
23 | y = builder.y;
24 | }
25 |
26 | public static Builder newJavaBean() {
27 | return new Builder();
28 | }
29 |
30 | public static final class Builder {
31 | private String foo;
32 | private String bar;
33 | private int qux;
34 | private Double x;
35 | private Double y;
36 |
37 | private Builder() {
38 | }
39 |
40 | public Builder foo(String val) {
41 | foo = val;
42 | return this;
43 | }
44 |
45 | public Builder bar(String val) {
46 | bar = val;
47 | return this;
48 | }
49 |
50 | public Builder qux(int val) {
51 | qux = val;
52 | return this;
53 | }
54 |
55 | public Builder x(Double val) {
56 | x = val;
57 | return this;
58 | }
59 |
60 | public Builder y(Double val) {
61 | y = val;
62 | return this;
63 | }
64 |
65 | public JavaBean build() {
66 | return new JavaBean(this);
67 | }
68 | }
69 | }
70 | ```
71 |
72 | ### Installation
73 |
74 | In IntelliJ IDEA 2019 or later, open `Preferences...` > `Plugins`, search for `innerbuilder`. It should show up in
75 | the plugin list, click `INSTALL`.
76 |
77 | #### Manual installation
78 |
79 | Download the plugin jar `innerbuilder.jar` and select `Install Plugin From Disk` in IntelliJ's plugin preferences.
80 |
81 | ### Usage
82 |
83 | Use `Shift+Alt+B` or `Alt+Insert` and select `Builder...`. Choose the fields to be included and press `OK`. When generating a
84 | builder when a builder already exists, the plugin will try to update it. It will add missing fields and builder methods, but
85 | never remove any fields or methods.
86 |
87 | ### Rate
88 |
89 | If you enjoy this plugin, please rate it on its [plugins.jetbrains.com page](https://plugins.jetbrains.com/plugin/7354-innerbuilder/).
90 |
91 | ### Building
92 |
93 | - Run `./prepare-build.sh` to download IntelliJ IDEA Community Edition
94 | - Run `mvn package`
95 |
96 | ### License
97 |
98 | Licensed under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0).
--------------------------------------------------------------------------------
/innerbuilder.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/analytically/innerbuilder/2ec1feacbbe98ebdee02c56022a852d6562d1d7d/innerbuilder.jar
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 | org.jetbrains.plugins
6 | innerbuilder
7 | 1.3.1-SNAPSHOT
8 | jar
9 | InnerBuilder
10 | Adds a Builder action to the Generate menu (Alt+Insert) which generates an inner builder class as described in Effective Java
11 |
12 |
13 | UTF-8
14 |
15 |
16 |
17 | innerbuilder
18 |
19 |
20 | src/main/resources
21 | true
22 |
23 |
24 |
25 |
26 | org.apache.maven.plugins
27 | maven-compiler-plugin
28 | 2.5.1
29 |
30 |
31 | ${project.build.directory}/dependency/intellij-idea/lib/:${project.build.directory}/dependency/intellij-idea/plugins/java/lib/:${project.build.directory}/dependency/intellij-idea/plugins/devkit/lib/
32 |
33 | 1.8
34 | 1.8
35 |
36 |
37 |
38 | org.apache.maven.plugins
39 | maven-jar-plugin
40 | 2.5
41 |
42 | ${basedir}
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | sonatype-public-repository
51 | https://oss.sonatype.org/content/groups/public
52 |
53 | true
54 |
55 |
56 | true
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/prepare-build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | mkdir -p target/dependency/intellij-idea
4 | curl -L https://download.jetbrains.com/idea/ideaIC-2022.1.3.tar.gz | tar xz --strip-components=1 -C target/dependency/intellij-idea
5 |
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/analytically/innerbuilder/2ec1feacbbe98ebdee02c56022a852d6562d1d7d/screenshot.png
--------------------------------------------------------------------------------
/src/main/java/org/jetbrains/plugins/innerbuilder/CheckboxSelectorOption.java:
--------------------------------------------------------------------------------
1 | package org.jetbrains.plugins.innerbuilder;
2 |
3 |
4 | public class CheckboxSelectorOption implements SelectorOption {
5 | private final InnerBuilderOption option;
6 | private final String caption;
7 | private final char mnemonic;
8 | private String toolTip;
9 |
10 | public CheckboxSelectorOption(InnerBuilderOption option, String caption, char mnemonic) {
11 | this.option = option;
12 | this.caption = caption;
13 | this.mnemonic = mnemonic;
14 | }
15 |
16 | public CheckboxSelectorOption(InnerBuilderOption option, String caption, char mnemonic, String toolTip) {
17 | this.option = option;
18 | this.caption = caption;
19 | this.mnemonic = mnemonic;
20 | this.toolTip = toolTip;
21 | }
22 |
23 | @Override
24 | public InnerBuilderOption getOption() {
25 | return option;
26 | }
27 |
28 | @Override
29 | public String getCaption() {
30 | return caption;
31 | }
32 |
33 | public char getMnemonic() {
34 | return mnemonic;
35 | }
36 |
37 | @Override
38 | public String getToolTip() {
39 | return toolTip;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/org/jetbrains/plugins/innerbuilder/DropdownListCellRenderer.java:
--------------------------------------------------------------------------------
1 | package org.jetbrains.plugins.innerbuilder;
2 |
3 | import com.intellij.ui.SimpleListCellRenderer;
4 | import org.jetbrains.annotations.NotNull;
5 |
6 | import javax.swing.*;
7 |
8 | public class DropdownListCellRenderer extends SimpleListCellRenderer {
9 | @Override
10 | public void customize(@NotNull JList extends DropdownSelectorOptionValue> list,
11 | DropdownSelectorOptionValue value,
12 | int index,
13 | boolean selected,
14 | boolean hasFocus) {
15 | setText(value.getCaption());
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/org/jetbrains/plugins/innerbuilder/DropdownSelectorOption.java:
--------------------------------------------------------------------------------
1 | package org.jetbrains.plugins.innerbuilder;
2 |
3 | import java.util.List;
4 |
5 | public class DropdownSelectorOption implements SelectorOption {
6 | private final InnerBuilderOption option;
7 | private final String caption;
8 | private final String toolTip;
9 | private final List values;
10 |
11 | public DropdownSelectorOption(InnerBuilderOption option, String caption, String toolTip, List values) {
12 | this.option = option;
13 | this.caption = caption;
14 | this.toolTip = toolTip;
15 | this.values = values;
16 | }
17 |
18 | @Override
19 | public InnerBuilderOption getOption() {
20 | return option;
21 | }
22 |
23 | @Override
24 | public String getCaption() {
25 | return caption;
26 | }
27 |
28 | @Override
29 | public String getToolTip() {
30 | return toolTip;
31 | }
32 |
33 | public List getValues() {
34 | return values;
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/org/jetbrains/plugins/innerbuilder/DropdownSelectorOptionValue.java:
--------------------------------------------------------------------------------
1 | package org.jetbrains.plugins.innerbuilder;
2 |
3 |
4 | public class DropdownSelectorOptionValue {
5 | InnerBuilderOption option;
6 | String caption;
7 |
8 | private DropdownSelectorOptionValue(Builder builder) {
9 | option = builder.option;
10 | caption = builder.caption;
11 | }
12 |
13 | public InnerBuilderOption getOption() {
14 | return option;
15 | }
16 |
17 | public String getCaption() {
18 | return caption;
19 | }
20 |
21 | public static Builder newBuilder() {
22 | return new Builder();
23 | }
24 |
25 | public static final class Builder {
26 | private InnerBuilderOption option;
27 | private String caption;
28 |
29 | private Builder() {
30 | }
31 |
32 | public Builder withOption(InnerBuilderOption val) {
33 | option = val;
34 | return this;
35 | }
36 |
37 | public Builder withCaption(String val) {
38 | caption = val;
39 | return this;
40 | }
41 |
42 | public DropdownSelectorOptionValue build() {
43 | return new DropdownSelectorOptionValue(this);
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/java/org/jetbrains/plugins/innerbuilder/InnerBuilderAction.java:
--------------------------------------------------------------------------------
1 | package org.jetbrains.plugins.innerbuilder;
2 |
3 | import com.intellij.codeInsight.CodeInsightActionHandler;
4 | import com.intellij.codeInsight.actions.BaseCodeInsightAction;
5 | import com.intellij.openapi.editor.Editor;
6 | import com.intellij.openapi.project.Project;
7 | import com.intellij.psi.PsiFile;
8 | import org.jetbrains.annotations.NotNull;
9 |
10 | /**
11 | * The IntelliJ IDEA action for this plugin, generates an inner builder class as described in Effective Java.
12 | *
13 | * @author Mathias Bogaert
14 | */
15 | public class InnerBuilderAction extends BaseCodeInsightAction {
16 | private final InnerBuilderHandler handler = new InnerBuilderHandler();
17 |
18 | @NotNull
19 | @Override
20 | protected CodeInsightActionHandler getHandler() {
21 | return handler;
22 | }
23 |
24 | @Override
25 | protected boolean isValidForFile(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) {
26 | return handler.isValidFor(editor, file);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/org/jetbrains/plugins/innerbuilder/InnerBuilderCollector.java:
--------------------------------------------------------------------------------
1 | package org.jetbrains.plugins.innerbuilder;
2 |
3 | import com.intellij.codeInsight.generation.PsiFieldMember;
4 | import com.intellij.openapi.editor.Editor;
5 | import com.intellij.psi.JavaPsiFacade;
6 | import com.intellij.psi.PsiClass;
7 | import com.intellij.psi.PsiElement;
8 | import com.intellij.psi.PsiField;
9 | import com.intellij.psi.PsiFile;
10 | import com.intellij.psi.PsiModifier;
11 | import com.intellij.psi.PsiResolveHelper;
12 | import com.intellij.psi.PsiSubstitutor;
13 | import com.intellij.psi.util.PsiTreeUtil;
14 | import com.intellij.psi.util.TypeConversionUtil;
15 | import org.jetbrains.annotations.Nullable;
16 |
17 | import java.util.ArrayList;
18 | import java.util.List;
19 |
20 | import static org.jetbrains.plugins.innerbuilder.InnerBuilderUtils.hasLowerCaseChar;
21 |
22 | public final class InnerBuilderCollector {
23 | private InnerBuilderCollector() { }
24 |
25 | @Nullable
26 | public static List collectFields(final PsiFile file, final Editor editor) {
27 | final int offset = editor.getCaretModel().getOffset();
28 | final PsiElement element = file.findElementAt(offset);
29 | if (element == null) {
30 | return null;
31 | }
32 |
33 | final PsiClass clazz = PsiTreeUtil.getParentOfType(element, PsiClass.class);
34 | if (clazz == null || clazz.hasModifierProperty(PsiModifier.ABSTRACT)) {
35 | return null;
36 | }
37 |
38 | final List allFields = new ArrayList<>();
39 |
40 | PsiClass classToExtractFieldsFrom = clazz;
41 | while (classToExtractFieldsFrom != null) {
42 | final List classFieldMembers = collectFieldsInClass(element, clazz,
43 | classToExtractFieldsFrom);
44 | allFields.addAll(0, classFieldMembers);
45 |
46 | classToExtractFieldsFrom = classToExtractFieldsFrom.getSuperClass();
47 | }
48 |
49 | return allFields;
50 | }
51 |
52 | private static List collectFieldsInClass(final PsiElement element, final PsiClass accessObjectClass,
53 | final PsiClass clazz) {
54 | final List classFieldMembers = new ArrayList<>();
55 | final PsiResolveHelper helper = JavaPsiFacade.getInstance(clazz.getProject()).getResolveHelper();
56 |
57 | for (final PsiField field : clazz.getFields()) {
58 |
59 | // check access to the field from the builder container class (e.g. private superclass fields)
60 | if ((helper.isAccessible(field, clazz, accessObjectClass) || hasSetter(clazz, field.getName()))
61 | && !PsiTreeUtil.isAncestor(field, element, false)) {
62 |
63 | // skip static fields
64 | if (field.hasModifierProperty(PsiModifier.STATIC)) {
65 | continue;
66 | }
67 |
68 | // skip any uppercase fields
69 | if (!hasLowerCaseChar(field.getName())) {
70 | continue;
71 | }
72 |
73 | // skip eventual logging fields
74 | final String fieldType = field.getType().getCanonicalText();
75 | if ("org.apache.log4j.Logger".equals(fieldType) || "org.apache.logging.log4j.Logger".equals(fieldType)
76 | || "java.util.logging.Logger".equals(fieldType) || "org.slf4j.Logger".equals(fieldType)
77 | || "ch.qos.logback.classic.Logger".equals(fieldType)
78 | || "net.sf.microlog.core.Logger".equals(fieldType)
79 | || "org.apache.commons.logging.Log".equals(fieldType)
80 | || "org.pmw.tinylog.Logger".equals(fieldType) || "org.jboss.logging.Logger".equals(fieldType)
81 | || "jodd.log.Logger".equals(fieldType)) {
82 | continue;
83 | }
84 |
85 | if (field.hasModifierProperty(PsiModifier.FINAL)) {
86 | if (field.getInitializer() != null) {
87 | continue; // skip final fields that are assigned in the declaration
88 | }
89 |
90 | if (!accessObjectClass.isEquivalentTo(clazz)) {
91 | continue; // skip final superclass fields
92 | }
93 | }
94 |
95 | final PsiClass containingClass = field.getContainingClass();
96 | if (containingClass != null) {
97 | classFieldMembers.add(buildFieldMember(field, containingClass, clazz));
98 | }
99 | }
100 | }
101 |
102 | return classFieldMembers;
103 | }
104 |
105 | private static boolean hasSetter(PsiClass clazz, String name) {
106 | for (int i = 0; i < clazz.getAllMethods().length; i++) {
107 | if (clazz.getAllMethods()[i].getName().equals(String.format("set%s", InnerBuilderUtils.capitalize(name)))) {
108 | return true;
109 | }
110 | }
111 |
112 | return false;
113 | }
114 |
115 | private static PsiFieldMember buildFieldMember(final PsiField field, final PsiClass containingClass,
116 | final PsiClass clazz) {
117 | return new PsiFieldMember(field,
118 | TypeConversionUtil.getSuperClassSubstitutor(containingClass, clazz, PsiSubstitutor.EMPTY));
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/src/main/java/org/jetbrains/plugins/innerbuilder/InnerBuilderGenerator.java:
--------------------------------------------------------------------------------
1 | package org.jetbrains.plugins.innerbuilder;
2 |
3 | import com.intellij.codeInsight.generation.PsiFieldMember;
4 | import com.intellij.ide.util.PropertiesComponent;
5 | import com.intellij.openapi.application.ApplicationManager;
6 | import com.intellij.openapi.editor.Editor;
7 | import com.intellij.openapi.project.Project;
8 | import com.intellij.psi.JavaPsiFacade;
9 | import com.intellij.psi.PsiClass;
10 | import com.intellij.psi.PsiCodeBlock;
11 | import com.intellij.psi.PsiComment;
12 | import com.intellij.psi.PsiElement;
13 | import com.intellij.psi.PsiElementFactory;
14 | import com.intellij.psi.PsiField;
15 | import com.intellij.psi.PsiFile;
16 | import com.intellij.psi.PsiMethod;
17 | import com.intellij.psi.PsiModifier;
18 | import com.intellij.psi.PsiModifierList;
19 | import com.intellij.psi.PsiParameter;
20 | import com.intellij.psi.PsiPrimitiveType;
21 | import com.intellij.psi.PsiStatement;
22 | import com.intellij.psi.PsiType;
23 | import com.intellij.psi.codeStyle.CodeStyleManager;
24 | import com.intellij.psi.codeStyle.JavaCodeStyleManager;
25 | import com.intellij.psi.javadoc.PsiDocComment;
26 | import com.intellij.psi.util.PropertyUtil;
27 | import com.intellij.psi.util.PsiUtil;
28 | import org.jetbrains.annotations.NonNls;
29 | import org.jetbrains.annotations.NotNull;
30 | import org.jetbrains.annotations.Nullable;
31 |
32 | import java.util.ArrayList;
33 | import java.util.Collection;
34 | import java.util.EnumSet;
35 | import java.util.List;
36 | import java.util.Set;
37 |
38 | public class InnerBuilderGenerator implements Runnable {
39 |
40 | @NonNls
41 | private static final String BUILDER_CLASS_NAME = "Builder";
42 | @NonNls
43 | private static final String BUILDER_SETTER_DEFAULT_PARAMETER_NAME = "val";
44 | @NonNls
45 | private static final String BUILDER_SETTER_ALTERNATIVE_PARAMETER_NAME = "value";
46 | @NonNls
47 | private static final String JSR305_NONNULL = "javax.annotation.Nonnull";
48 | @NonNls
49 | private static final String DEFAULT_BUILDER_METHOD_NAME = "newBuilder";
50 | @NonNls
51 | private static final String BUILDER_METHOD_NAME = "builder";
52 |
53 | private final Project project;
54 | private final PsiFile file;
55 | private final Editor editor;
56 | private final List selectedFields;
57 | private final PsiElementFactory psiElementFactory;
58 |
59 | public static void generate(final Project project, final Editor editor, final PsiFile file,
60 | final List selectedFields) {
61 | final Runnable builderGenerator = new InnerBuilderGenerator(project, file, editor, selectedFields);
62 | ApplicationManager.getApplication().runWriteAction(builderGenerator);
63 | }
64 |
65 | private InnerBuilderGenerator(final Project project, final PsiFile file, final Editor editor,
66 | final List selectedFields) {
67 | this.project = project;
68 | this.file = file;
69 | this.editor = editor;
70 | this.selectedFields = selectedFields;
71 | psiElementFactory = JavaPsiFacade.getInstance(project).getElementFactory();
72 | }
73 |
74 | @Override
75 | public void run() {
76 | final PsiClass targetClass = InnerBuilderUtils.getStaticOrTopLevelClass(file, editor);
77 | if (targetClass == null) {
78 | return;
79 | }
80 | final Set options = currentOptions();
81 | final PsiClass builderClass = findOrCreateBuilderClass(targetClass);
82 | final PsiType builderType = psiElementFactory.createTypeFromText(BUILDER_CLASS_NAME, null);
83 | final PsiMethod constructor = generateConstructor(targetClass, builderType);
84 |
85 | addMethod(targetClass, null, constructor, true);
86 | final Collection finalFields = new ArrayList<>();
87 | final Collection nonFinalFields = new ArrayList<>();
88 |
89 | PsiElement lastAddedField = null;
90 | for (final PsiFieldMember fieldMember : selectedFields) {
91 | lastAddedField = findOrCreateField(builderClass, fieldMember, lastAddedField);
92 | if (fieldMember.getElement().hasModifierProperty(PsiModifier.FINAL)
93 | && !options.contains(InnerBuilderOption.FINAL_SETTERS)) {
94 | finalFields.add(fieldMember);
95 | PsiUtil.setModifierProperty((PsiField) lastAddedField, PsiModifier.FINAL, true);
96 | } else {
97 | nonFinalFields.add(fieldMember);
98 | }
99 | }
100 | if (options.contains(InnerBuilderOption.NEW_BUILDER_METHOD)) {
101 | final PsiMethod newBuilderMethod = generateNewBuilderMethod(builderType, targetClass, finalFields, options);
102 |
103 | final PsiClass builderTarget = options.contains(InnerBuilderOption.BUILDER_METHOD_IN_PARENT_CLASS) ?
104 | targetClass :
105 | builderClass;
106 |
107 | addMethod(builderTarget, null, newBuilderMethod, false);
108 | }
109 |
110 | // builder constructor, accepting the final fields
111 | if(finalFields.size() == 0 && !options.contains(InnerBuilderOption.NEW_BUILDER_METHOD)) {
112 | final PsiMethod builderConstructorMethod = generateBuilderConstructor(builderClass, finalFields, options);
113 | addMethod(builderClass, null, builderConstructorMethod, false);
114 | }
115 |
116 | // builder copy constructor or static copy method
117 | if (options.contains(InnerBuilderOption.COPY_CONSTRUCTOR)) {
118 | if (options.contains(InnerBuilderOption.NEW_BUILDER_METHOD)) {
119 | final PsiMethod copyBuilderMethod = generateCopyBuilderMethod(targetClass, builderType,
120 | nonFinalFields, options);
121 | addMethod(targetClass, null, copyBuilderMethod, true);
122 | } else {
123 | final PsiMethod copyConstructorBuilderMethod = generateCopyConstructor(targetClass, builderType,
124 | selectedFields, options);
125 | addMethod(builderClass, null, copyConstructorBuilderMethod, true);
126 | }
127 | }
128 |
129 | // builder methods
130 | PsiElement lastAddedElement = null;
131 | for (final PsiFieldMember member : nonFinalFields) {
132 | final PsiMethod setterMethod = generateBuilderSetter(builderType, member, options);
133 | lastAddedElement = addMethod(builderClass, lastAddedElement, setterMethod, false);
134 | }
135 |
136 | // builder.build() method
137 | final PsiMethod buildMethod = generateBuildMethod(targetClass, options);
138 | addMethod(builderClass, lastAddedElement, buildMethod, false);
139 |
140 | JavaCodeStyleManager.getInstance(project).shortenClassReferences(file);
141 | CodeStyleManager.getInstance(project).reformat(builderClass);
142 | }
143 |
144 | private PsiMethod generateCopyBuilderMethod(final PsiClass targetClass, final PsiType builderType,
145 | final Collection fields,
146 | final Set options) {
147 | final PsiMethod copyBuilderMethod = psiElementFactory.createMethod(getBuilderMethodName(targetClass, options), builderType);
148 | PsiUtil.setModifierProperty(copyBuilderMethod, PsiModifier.STATIC, true);
149 | PsiUtil.setModifierProperty(copyBuilderMethod, PsiModifier.PUBLIC, true);
150 |
151 | final PsiType targetClassType = psiElementFactory.createType(targetClass);
152 | final PsiParameter parameter = psiElementFactory.createParameter("copy", targetClassType);
153 | final PsiModifierList parameterModifierList = parameter.getModifierList();
154 |
155 | if (parameterModifierList != null) {
156 | if (options.contains(InnerBuilderOption.JSR305_ANNOTATIONS)) {
157 | parameterModifierList.addAnnotation(JSR305_NONNULL);
158 | }
159 | }
160 | copyBuilderMethod.getParameterList().add(parameter);
161 | final PsiCodeBlock copyBuilderBody = copyBuilderMethod.getBody();
162 | if (copyBuilderBody != null) {
163 | final StringBuilder copyBuilderParameters = new StringBuilder();
164 | for (final PsiFieldMember fieldMember : selectedFields) {
165 | if (fieldMember.getElement().hasModifierProperty(PsiModifier.FINAL)
166 | && !options.contains(InnerBuilderOption.FINAL_SETTERS)) {
167 |
168 | if (copyBuilderParameters.length() > 0) {
169 | copyBuilderParameters.append(", ");
170 | }
171 |
172 | copyBuilderParameters.append(String.format("copy.%s", fieldMember.getElement().getName()));
173 | }
174 | }
175 | if (options.contains(InnerBuilderOption.NEW_BUILDER_METHOD)) {
176 | final PsiStatement newBuilderStatement = psiElementFactory.createStatementFromText(String.format(
177 | "%s builder = new %s(%s);", builderType.getPresentableText(),
178 | builderType.getPresentableText(), copyBuilderParameters),
179 | copyBuilderMethod);
180 | copyBuilderBody.add(newBuilderStatement);
181 |
182 | addCopyBody(fields, copyBuilderMethod, "builder.");
183 | copyBuilderBody.add(psiElementFactory.createStatementFromText("return builder;", copyBuilderMethod));
184 | } else {
185 | final PsiStatement newBuilderStatement = psiElementFactory.createStatementFromText(String.format(
186 | "return new %s(%s);", builderType.getPresentableText(),
187 | copyBuilderParameters),
188 | copyBuilderMethod);
189 | copyBuilderBody.add(newBuilderStatement);
190 | }
191 | }
192 | return copyBuilderMethod;
193 | }
194 |
195 | private String getBuilderMethodName(final PsiClass psiClass, final Set options) {
196 | if (options.contains(InnerBuilderOption.STATIC_BUILDER_NEW_BUILDER_NAME)) {
197 | return DEFAULT_BUILDER_METHOD_NAME;
198 | }
199 |
200 | if (options.contains(InnerBuilderOption.STATIC_BUILDER_BUILDER_NAME)) {
201 | return BUILDER_METHOD_NAME;
202 | }
203 |
204 | if (options.contains(InnerBuilderOption.STATIC_BUILDER_NEW_CLASS_NAME)) {
205 | return "new" + psiClass.getName();
206 | }
207 |
208 | if(options.contains(InnerBuilderOption.STATIC_BUILDER_NEW_CLASS_NAME_BUILDER)) {
209 | return "new" + psiClass.getName() + "Builder";
210 | }
211 |
212 | return DEFAULT_BUILDER_METHOD_NAME;
213 | }
214 |
215 | private PsiMethod generateCopyConstructor(final PsiClass targetClass, final PsiType builderType,
216 | final Collection nonFinalFields,
217 | final Set options) {
218 |
219 | final PsiMethod copyConstructor = psiElementFactory.createConstructor(builderType.getPresentableText());
220 | PsiUtil.setModifierProperty(copyConstructor, PsiModifier.PUBLIC, true);
221 |
222 | final PsiType targetClassType = psiElementFactory.createType(targetClass);
223 | final PsiParameter constructorParameter = psiElementFactory.createParameter("copy", targetClassType);
224 | final PsiModifierList parameterModifierList = constructorParameter.getModifierList();
225 |
226 | if (parameterModifierList != null) {
227 | if (options.contains(InnerBuilderOption.JSR305_ANNOTATIONS))
228 | parameterModifierList.addAnnotation(JSR305_NONNULL);
229 | }
230 | copyConstructor.getParameterList().add(constructorParameter);
231 | addCopyBody(nonFinalFields, copyConstructor, "this.");
232 | return copyConstructor;
233 | }
234 |
235 | private void addCopyBody(final Collection fields, final PsiMethod method, final String qName) {
236 | final PsiCodeBlock methodBody = method.getBody();
237 | if (methodBody == null) {
238 | return;
239 | }
240 | for (final PsiFieldMember member : fields) {
241 | final PsiField field = member.getElement();
242 | final PsiStatement assignStatement = psiElementFactory.createStatementFromText(String.format(
243 | "%s%2$s = copy.%3$s;", qName, field.getName(), field.getName()), method);
244 | methodBody.add(assignStatement);
245 | }
246 | }
247 |
248 | private PsiMethod generateBuilderConstructor(final PsiClass builderClass,
249 | final Collection finalFields,
250 | final Set options) {
251 |
252 | final PsiMethod builderConstructor = psiElementFactory.createConstructor(builderClass.getName());
253 | if (options.contains(InnerBuilderOption.NEW_BUILDER_METHOD)) {
254 | PsiUtil.setModifierProperty(builderConstructor, PsiModifier.PRIVATE, true);
255 | } else {
256 | PsiUtil.setModifierProperty(builderConstructor, PsiModifier.PUBLIC, true);
257 | }
258 | final PsiCodeBlock builderConstructorBody = builderConstructor.getBody();
259 | if (builderConstructorBody != null) {
260 | for (final PsiFieldMember member : finalFields) {
261 | final PsiField field = member.getElement();
262 | final PsiType fieldType = field.getType();
263 | final String fieldName = field.getName();
264 |
265 | final PsiParameter parameter = psiElementFactory.createParameter(fieldName, fieldType);
266 | final PsiModifierList parameterModifierList = parameter.getModifierList();
267 | final boolean useJsr305 = options.contains(InnerBuilderOption.JSR305_ANNOTATIONS);
268 |
269 | if (!InnerBuilderUtils.isPrimitive(field) && parameterModifierList != null) {
270 | if (useJsr305) parameterModifierList.addAnnotation(JSR305_NONNULL);
271 | }
272 |
273 | builderConstructor.getParameterList().add(parameter);
274 | final PsiStatement assignStatement = psiElementFactory.createStatementFromText(String.format(
275 | "this.%1$s = %1$s;", fieldName), builderConstructor);
276 | builderConstructorBody.add(assignStatement);
277 | }
278 | }
279 |
280 | return builderConstructor;
281 | }
282 |
283 | private PsiMethod generateNewBuilderMethod(final PsiType builderType, PsiClass targetClass, final Collection finalFields,
284 | final Set options) {
285 | final PsiMethod newBuilderMethod = psiElementFactory.createMethod(getBuilderMethodName(targetClass, options), builderType);
286 | PsiUtil.setModifierProperty(newBuilderMethod, PsiModifier.STATIC, true);
287 | PsiUtil.setModifierProperty(newBuilderMethod, PsiModifier.PUBLIC, true);
288 |
289 | final StringBuilder fieldList = new StringBuilder();
290 | if (!finalFields.isEmpty()) {
291 | for (final PsiFieldMember member : finalFields) {
292 | final PsiField field = member.getElement();
293 | final PsiType fieldType = field.getType();
294 | final String fieldName = field.getName();
295 |
296 | final PsiParameter parameter = psiElementFactory.createParameter(fieldName, fieldType);
297 | final PsiModifierList parameterModifierList = parameter.getModifierList();
298 | if (parameterModifierList != null) {
299 | if (!InnerBuilderUtils.isPrimitive(field)) {
300 | if (options.contains(InnerBuilderOption.JSR305_ANNOTATIONS))
301 | parameterModifierList.addAnnotation(JSR305_NONNULL);
302 | }
303 | }
304 | newBuilderMethod.getParameterList().add(parameter);
305 | if (fieldList.length() > 0) {
306 | fieldList.append(", ");
307 | }
308 | fieldList.append(fieldName);
309 | }
310 | }
311 | final PsiCodeBlock newBuilderMethodBody = newBuilderMethod.getBody();
312 | if (newBuilderMethodBody != null) {
313 | final PsiStatement newStatement = psiElementFactory.createStatementFromText(String.format(
314 | "return new %s(%s);", builderType.getPresentableText(), fieldList),
315 | newBuilderMethod);
316 | newBuilderMethodBody.add(newStatement);
317 | }
318 | return newBuilderMethod;
319 | }
320 |
321 | private PsiMethod generateBuilderSetter(final PsiType builderType, final PsiFieldMember member,
322 | final Set options) {
323 |
324 | final PsiField field = member.getElement();
325 | final PsiType fieldType = field.getType();
326 | final String fieldName = InnerBuilderUtils.hasOneLetterPrefix(field.getName()) ?
327 | Character.toLowerCase(field.getName().charAt(1)) + field.getName().substring(2) : field.getName();
328 |
329 | final String methodName;
330 | if (options.contains(InnerBuilderOption.WITH_NOTATION)) {
331 | methodName = String.format("with%s", InnerBuilderUtils.capitalize(fieldName));
332 | } else if (options.contains(InnerBuilderOption.SET_NOTATION)) {
333 | methodName = String.format("set%s", InnerBuilderUtils.capitalize(fieldName));
334 | } else {
335 | methodName = fieldName;
336 | }
337 |
338 | final String parameterName = options.contains(InnerBuilderOption.FIELD_NAMES) ?
339 | fieldName :
340 | !BUILDER_SETTER_DEFAULT_PARAMETER_NAME.equals(fieldName) ?
341 | BUILDER_SETTER_DEFAULT_PARAMETER_NAME :
342 | BUILDER_SETTER_ALTERNATIVE_PARAMETER_NAME;
343 | final PsiMethod setterMethod = psiElementFactory.createMethod(methodName, builderType);
344 | final boolean useJsr305 = options.contains(InnerBuilderOption.JSR305_ANNOTATIONS);
345 |
346 | if (useJsr305) setterMethod.getModifierList().addAnnotation(JSR305_NONNULL);
347 |
348 | setterMethod.getModifierList().setModifierProperty(PsiModifier.PUBLIC, true);
349 | final PsiParameter setterParameter = psiElementFactory.createParameter(parameterName, fieldType);
350 |
351 | if (!(fieldType instanceof PsiPrimitiveType)) {
352 | final PsiModifierList setterParameterModifierList = setterParameter.getModifierList();
353 | if (setterParameterModifierList != null) {
354 | if (useJsr305) setterParameterModifierList.addAnnotation(JSR305_NONNULL);
355 | }
356 | }
357 | setterMethod.getParameterList().add(setterParameter);
358 | final PsiCodeBlock setterMethodBody = setterMethod.getBody();
359 | if (setterMethodBody != null) {
360 | final String actualFieldName = options.contains(InnerBuilderOption.FIELD_NAMES) ?
361 | "this." + fieldName :
362 | fieldName;
363 | final PsiStatement assignStatement = psiElementFactory.createStatementFromText(String.format(
364 | "%s = %s;", actualFieldName, parameterName), setterMethod);
365 | setterMethodBody.add(assignStatement);
366 | setterMethodBody.add(InnerBuilderUtils.createReturnThis(psiElementFactory, setterMethod));
367 | }
368 | setSetterComment(setterMethod, fieldName, parameterName);
369 | return setterMethod;
370 | }
371 |
372 |
373 | private PsiMethod generateConstructor(final PsiClass targetClass, final PsiType builderType) {
374 | final PsiMethod constructor = psiElementFactory.createConstructor(targetClass.getName());
375 | constructor.getModifierList().setModifierProperty(PsiModifier.PRIVATE, true);
376 |
377 | final PsiParameter builderParameter = psiElementFactory.createParameter("builder", builderType);
378 | constructor.getParameterList().add(builderParameter);
379 |
380 | final PsiCodeBlock constructorBody = constructor.getBody();
381 | if (constructorBody != null) {
382 | for (final PsiFieldMember member : selectedFields) {
383 | final PsiField field = member.getElement();
384 |
385 | final PsiMethod setterPrototype = PropertyUtil.generateSetterPrototype(field);
386 | final PsiMethod setter = targetClass.findMethodBySignature(setterPrototype, true);
387 |
388 | final String fieldName = field.getName();
389 | boolean isFinal = false;
390 | final PsiModifierList modifierList = field.getModifierList();
391 | if (modifierList != null) {
392 | isFinal = modifierList.hasModifierProperty(PsiModifier.FINAL);
393 | }
394 |
395 | final String assignText;
396 | if (setter == null || isFinal) {
397 | assignText = String.format("%1$s = builder.%1$s;", fieldName);
398 | } else {
399 | assignText = String.format("%s(builder.%s);", setter.getName(), fieldName);
400 | }
401 |
402 | final PsiStatement assignStatement = psiElementFactory.createStatementFromText(assignText, null);
403 | constructorBody.add(assignStatement);
404 | }
405 | }
406 |
407 | return constructor;
408 | }
409 |
410 | private PsiMethod generateBuildMethod(final PsiClass targetClass, final Set options) {
411 | final PsiType targetClassType = psiElementFactory.createType(targetClass);
412 | final PsiMethod buildMethod = psiElementFactory.createMethod("build", targetClassType);
413 |
414 | final boolean useJsr305 = options.contains(InnerBuilderOption.JSR305_ANNOTATIONS);
415 | if (useJsr305)
416 | buildMethod.getModifierList().addAnnotation(JSR305_NONNULL);
417 |
418 | buildMethod.getModifierList().setModifierProperty(PsiModifier.PUBLIC, true);
419 |
420 | final PsiCodeBlock buildMethodBody = buildMethod.getBody();
421 | if (buildMethodBody != null) {
422 | final PsiStatement returnStatement = psiElementFactory.createStatementFromText(String.format(
423 | "return new %s(this);", targetClass.getName()), buildMethod);
424 | buildMethodBody.add(returnStatement);
425 | }
426 | setBuildMethodComment(buildMethod, targetClass);
427 | return buildMethod;
428 | }
429 |
430 | @NotNull
431 | private PsiClass findOrCreateBuilderClass(final PsiClass targetClass) {
432 | final PsiClass builderClass = targetClass.findInnerClassByName(BUILDER_CLASS_NAME, false);
433 | if (builderClass == null) {
434 | return createBuilderClass(targetClass);
435 | }
436 |
437 | return builderClass;
438 | }
439 |
440 | @NotNull
441 | private PsiClass createBuilderClass(final PsiClass targetClass) {
442 | final PsiClass builderClass = (PsiClass) targetClass.add(psiElementFactory.createClass(BUILDER_CLASS_NAME));
443 | PsiUtil.setModifierProperty(builderClass, PsiModifier.STATIC, true);
444 | PsiUtil.setModifierProperty(builderClass, PsiModifier.FINAL, true);
445 | setBuilderComment(builderClass, targetClass);
446 | setBuilderAnnotation(builderClass);
447 | return builderClass;
448 | }
449 |
450 | private PsiElement findOrCreateField(final PsiClass builderClass, final PsiFieldMember member,
451 | @Nullable final PsiElement last) {
452 | final PsiField field = member.getElement();
453 | final String fieldName = field.getName();
454 | final PsiType fieldType = field.getType();
455 | final PsiField existingField = builderClass.findFieldByName(fieldName, false);
456 | if (existingField == null || !InnerBuilderUtils.areTypesPresentableEqual(existingField.getType(), fieldType)) {
457 | if (existingField != null) {
458 | existingField.delete();
459 | }
460 | final PsiField newField = psiElementFactory.createField(fieldName, fieldType);
461 | if (last != null) {
462 | return builderClass.addAfter(newField, last);
463 | } else {
464 | return builderClass.add(newField);
465 | }
466 | }
467 | return existingField;
468 | }
469 |
470 | private PsiElement addMethod(@NotNull final PsiClass target, @Nullable final PsiElement after,
471 | @NotNull final PsiMethod newMethod, final boolean replace) {
472 | PsiMethod existingMethod = target.findMethodBySignature(newMethod, false);
473 | if (existingMethod == null && newMethod.isConstructor()) {
474 | for (final PsiMethod constructor : target.getConstructors()) {
475 | if (InnerBuilderUtils.areParameterListsEqual(constructor.getParameterList(),
476 | newMethod.getParameterList())) {
477 | existingMethod = constructor;
478 | break;
479 | }
480 | }
481 | }
482 | if (existingMethod == null) {
483 | if (after != null) {
484 | return target.addAfter(newMethod, after);
485 | } else {
486 | return target.add(newMethod);
487 | }
488 | } else if (replace) {
489 | existingMethod.replace(newMethod);
490 | }
491 | return existingMethod;
492 | }
493 |
494 | private static EnumSet currentOptions() {
495 | final EnumSet options = EnumSet.noneOf(InnerBuilderOption.class);
496 | final PropertiesComponent propertiesComponent = PropertiesComponent.getInstance();
497 | for (final InnerBuilderOption option : InnerBuilderOption.values()) {
498 |
499 | if (option.isBooleanProperty()) {
500 | final boolean currentSetting = propertiesComponent.getBoolean(option.getProperty(), false);
501 | if (currentSetting) {
502 | options.add(option);
503 | }
504 | } else {
505 | String currentValue = String.valueOf(propertiesComponent.getValue(option.getProperty()));
506 |
507 | if (currentValue != null) {
508 | InnerBuilderOption.findValue(currentValue)
509 | .ifPresent(options::add);
510 | }
511 | }
512 | }
513 | return options;
514 | }
515 |
516 | private void setBuilderComment(final PsiClass clazz, final PsiClass targetClass) {
517 | if (currentOptions().contains(InnerBuilderOption.WITH_JAVADOC)) {
518 | StringBuilder str = new StringBuilder("/**\n").append("* {@code ");
519 | str.append(targetClass.getName()).append("} builder static inner class.\n");
520 | str.append("*/");
521 | setStringComment(clazz, str.toString());
522 | }
523 | }
524 |
525 | private void setBuilderAnnotation(final PsiClass clazz) {
526 | if (currentOptions().contains(InnerBuilderOption.PMD_AVOID_FIELD_NAME_MATCHING_METHOD_NAME_ANNOTATION)) {
527 | clazz.getModifierList().addAnnotation("SuppressWarnings(\"PMD.AvoidFieldNameMatchingMethodName\")");
528 | }
529 | }
530 |
531 | private void setSetterComment(final PsiMethod method, final String fieldName, final String parameterName) {
532 | if (currentOptions().contains(InnerBuilderOption.WITH_JAVADOC)) {
533 | StringBuilder str = new StringBuilder("/**\n").append("* Sets the {@code ").append(fieldName);
534 | str.append("} and returns a reference to this Builder enabling method chaining.\n");
535 | str.append("* @param ").append(parameterName).append(" the {@code ");
536 | str.append(fieldName).append("} to set\n");
537 | str.append("* @return a reference to this Builder\n*/");
538 | setStringComment(method, str.toString());
539 | }
540 | }
541 |
542 | private void setBuildMethodComment(final PsiMethod method, final PsiClass targetClass) {
543 | if (currentOptions().contains(InnerBuilderOption.WITH_JAVADOC)) {
544 | StringBuilder str = new StringBuilder("/**\n");
545 | str.append("* Returns a {@code ").append(targetClass.getName()).append("} built ");
546 | str.append("from the parameters previously set.\n*\n");
547 | str.append("* @return a {@code ").append(targetClass.getName()).append("} ");
548 | str.append("built with parameters of this {@code ").append(targetClass.getName()).append(".Builder}\n*/");
549 | setStringComment(method, str.toString());
550 | }
551 | }
552 |
553 | private void setStringComment(final PsiMethod method, final String strComment) {
554 | PsiComment comment = psiElementFactory.createCommentFromText(strComment, null);
555 | PsiDocComment doc = method.getDocComment();
556 | if (doc != null) {
557 | doc.replace(comment);
558 | } else {
559 | method.addBefore(comment, method.getFirstChild());
560 | }
561 | }
562 |
563 | private void setStringComment(final PsiClass clazz, final String strComment) {
564 | PsiComment comment = psiElementFactory.createCommentFromText(strComment, null);
565 | PsiDocComment doc = clazz.getDocComment();
566 | if (doc != null) {
567 | doc.replace(comment);
568 | } else {
569 | clazz.addBefore(comment, clazz.getFirstChild());
570 | }
571 | }
572 | }
573 |
--------------------------------------------------------------------------------
/src/main/java/org/jetbrains/plugins/innerbuilder/InnerBuilderHandler.java:
--------------------------------------------------------------------------------
1 | package org.jetbrains.plugins.innerbuilder;
2 |
3 | import com.intellij.codeInsight.generation.PsiFieldMember;
4 | import com.intellij.lang.LanguageCodeInsightActionHandler;
5 | import com.intellij.openapi.editor.Document;
6 | import com.intellij.openapi.editor.Editor;
7 | import com.intellij.openapi.editor.EditorModificationUtil;
8 | import com.intellij.openapi.fileEditor.FileDocumentManager;
9 | import com.intellij.openapi.project.Project;
10 | import com.intellij.psi.PsiDocumentManager;
11 | import com.intellij.psi.PsiFile;
12 | import com.intellij.psi.PsiJavaFile;
13 | import org.jetbrains.annotations.NotNull;
14 |
15 | import java.util.List;
16 |
17 | import static org.jetbrains.plugins.innerbuilder.InnerBuilderCollector.collectFields;
18 | import static org.jetbrains.plugins.innerbuilder.InnerBuilderOptionSelector.selectFieldsAndOptions;
19 |
20 | public class InnerBuilderHandler implements LanguageCodeInsightActionHandler {
21 |
22 | @Override
23 | public boolean isValidFor(final Editor editor, final PsiFile file) {
24 | if (!(file instanceof PsiJavaFile)) {
25 | return false;
26 | }
27 |
28 | final Project project = editor.getProject();
29 | if (project == null) {
30 | return false;
31 | }
32 |
33 | return InnerBuilderUtils.getStaticOrTopLevelClass(file, editor) != null && isApplicable(file, editor);
34 | }
35 |
36 | @Override
37 | public boolean startInWriteAction() {
38 | return false;
39 | }
40 |
41 | private static boolean isApplicable(final PsiFile file, final Editor editor) {
42 | final List targetElements = collectFields(file, editor);
43 | return targetElements != null && !targetElements.isEmpty();
44 | }
45 |
46 | @Override
47 | public void invoke(@NotNull final Project project, @NotNull final Editor editor, @NotNull final PsiFile file) {
48 | final PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(project);
49 | final Document currentDocument = psiDocumentManager.getDocument(file);
50 | if (currentDocument == null) {
51 | return;
52 | }
53 |
54 | psiDocumentManager.commitDocument(currentDocument);
55 |
56 | if (!EditorModificationUtil.checkModificationAllowed(editor)) {
57 | return;
58 | }
59 |
60 | if (!FileDocumentManager.getInstance().requestWriting(editor.getDocument(), project)) {
61 | return;
62 | }
63 |
64 | final List existingFields = collectFields(file, editor);
65 | if (existingFields != null) {
66 | final List selectedFields = selectFieldsAndOptions(existingFields, project);
67 |
68 | if (selectedFields == null || selectedFields.isEmpty()) {
69 | return;
70 | }
71 |
72 | InnerBuilderGenerator.generate(project, editor, file, selectedFields);
73 | }
74 | }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/src/main/java/org/jetbrains/plugins/innerbuilder/InnerBuilderOption.java:
--------------------------------------------------------------------------------
1 | package org.jetbrains.plugins.innerbuilder;
2 |
3 | import java.util.Arrays;
4 | import java.util.Objects;
5 | import java.util.Optional;
6 |
7 | public enum InnerBuilderOption {
8 |
9 | FINAL_SETTERS("finalSetters"),
10 | NEW_BUILDER_METHOD("newBuilderMethod"),
11 |
12 | STATIC_BUILDER_DROPDOWN("staticBuilderDropdown", false),
13 | STATIC_BUILDER_NEW_BUILDER_NAME("staticBuilderNewBuilderName", false),
14 | STATIC_BUILDER_BUILDER_NAME("staticBuilderBuilderName", false),
15 | STATIC_BUILDER_NEW_CLASS_NAME("staticBuilderNewClassName", false),
16 | STATIC_BUILDER_NEW_CLASS_NAME_BUILDER("staticBuilderNewClassNameBuilder", false),
17 |
18 | BUILDER_METHOD_LOCATION_DROPDOWN("builderMethodDropdownLocation", false),
19 | BUILDER_METHOD_IN_PARENT_CLASS("builderMethodInParentClass", false),
20 | BUILDER_METHOD_IN_BUILDER("builderMethodInBuilder", false),
21 |
22 | COPY_CONSTRUCTOR("copyConstructor"),
23 | WITH_NOTATION("withNotation"),
24 | SET_NOTATION("setNotation"),
25 | JSR305_ANNOTATIONS("useJSR305Annotations"),
26 | PMD_AVOID_FIELD_NAME_MATCHING_METHOD_NAME_ANNOTATION("suppressAvoidFieldNameMatchingMethodName"),
27 | WITH_JAVADOC("withJavadoc"),
28 | FIELD_NAMES("fieldNames");
29 |
30 | private final String property;
31 | private final Boolean booleanProperty;
32 |
33 | InnerBuilderOption(final String property) {
34 | this(property, true);
35 | }
36 |
37 | InnerBuilderOption(final String property, final Boolean booleanProperty) {
38 | this.property = String.format("GenerateInnerBuilder.%s", property);
39 | this.booleanProperty = booleanProperty;
40 | }
41 |
42 | public String getProperty() {
43 | return property;
44 | }
45 |
46 | public Boolean isBooleanProperty() {
47 | return booleanProperty;
48 | }
49 |
50 | public static Optional findValue(String value) {
51 | return Arrays.stream(values())
52 | .filter(it -> Objects.equals(it.getProperty(), value))
53 | .findFirst();
54 | }
55 | }
--------------------------------------------------------------------------------
/src/main/java/org/jetbrains/plugins/innerbuilder/InnerBuilderOptionSelector.java:
--------------------------------------------------------------------------------
1 | package org.jetbrains.plugins.innerbuilder;
2 |
3 | import com.intellij.codeInsight.generation.PsiFieldMember;
4 | import com.intellij.ide.util.MemberChooser;
5 | import com.intellij.ide.util.PropertiesComponent;
6 | import com.intellij.openapi.application.ApplicationManager;
7 | import com.intellij.openapi.project.Project;
8 | import com.intellij.openapi.ui.ComboBox;
9 | import com.intellij.openapi.ui.LabeledComponent;
10 | import com.intellij.ui.NonFocusableCheckBox;
11 | import org.jetbrains.annotations.Nullable;
12 |
13 | import javax.swing.*;
14 | import java.awt.event.ItemEvent;
15 | import java.util.ArrayList;
16 | import java.util.List;
17 | import java.util.Objects;
18 |
19 | public final class InnerBuilderOptionSelector {
20 | private static final DropdownListCellRenderer RENDERER = new DropdownListCellRenderer();
21 | private static final List OPTIONS = createGeneratorOptions();
22 |
23 | private static List createGeneratorOptions() {
24 | final List options = new ArrayList<>();
25 |
26 | options.add(new CheckboxSelectorOption(
27 | InnerBuilderOption.FINAL_SETTERS,
28 | "Generate builder methods for final fields",
29 | 'f')
30 | );
31 |
32 | options.add(new CheckboxSelectorOption(
33 | InnerBuilderOption.NEW_BUILDER_METHOD,
34 | "Generate static builder method",
35 | 'n')
36 | );
37 |
38 | options.add(new DropdownSelectorOption(
39 | InnerBuilderOption.STATIC_BUILDER_DROPDOWN,
40 | "Static builder naming",
41 | "Select what the static builder method should look like.",
42 | List.of(
43 | DropdownSelectorOptionValue.newBuilder()
44 | .withCaption("newBuilder()")
45 | .withOption(InnerBuilderOption.STATIC_BUILDER_NEW_BUILDER_NAME)
46 | .build(),
47 | DropdownSelectorOptionValue.newBuilder()
48 | .withCaption("builder()")
49 | .withOption(InnerBuilderOption.STATIC_BUILDER_BUILDER_NAME)
50 | .build(),
51 | DropdownSelectorOptionValue.newBuilder()
52 | .withCaption("new[ClassName]()")
53 | .withOption(InnerBuilderOption.STATIC_BUILDER_NEW_CLASS_NAME)
54 | .build(),
55 | DropdownSelectorOptionValue.newBuilder()
56 | .withCaption("new[ClassName]Builder()")
57 | .withOption(InnerBuilderOption.STATIC_BUILDER_NEW_CLASS_NAME_BUILDER)
58 | .build()
59 | )
60 | ));
61 |
62 | options.add(new DropdownSelectorOption(
63 | InnerBuilderOption.BUILDER_METHOD_LOCATION_DROPDOWN,
64 | "Builder method location",
65 | "Select where the builder method should be located.",
66 | List.of(
67 | DropdownSelectorOptionValue.newBuilder()
68 | .withCaption("Inside parent class")
69 | .withOption(InnerBuilderOption.BUILDER_METHOD_IN_PARENT_CLASS)
70 | .build(),
71 | DropdownSelectorOptionValue.newBuilder()
72 | .withCaption("Inside generated Builder class")
73 | .withOption(InnerBuilderOption.BUILDER_METHOD_IN_BUILDER)
74 | .build()
75 | )
76 | ));
77 |
78 | options.add(new CheckboxSelectorOption(
79 | InnerBuilderOption.COPY_CONSTRUCTOR,
80 | "Generate builder copy constructor",
81 | 'o'
82 | ));
83 | options.add(new CheckboxSelectorOption(
84 | InnerBuilderOption.WITH_NOTATION,
85 | "Use 'with...' notation",
86 | 'w',
87 | "Generate builder methods that start with 'with', for example: "
88 | + "builder.withName(String name)")
89 | );
90 |
91 | options.add(new CheckboxSelectorOption(
92 | InnerBuilderOption.SET_NOTATION,
93 | "Use 'set...' notation",
94 | 't',
95 |
96 | "Generate builder methods that start with 'set', for example: "
97 | + "builder.setName(String name)")
98 | );
99 |
100 | options.add(new CheckboxSelectorOption(
101 | InnerBuilderOption.JSR305_ANNOTATIONS,
102 | "Add JSR-305 @Nonnull annotation",
103 | 'j',
104 | "Add @Nonnull annotations to generated methods and parameters, for example: "
105 | + "@Nonnull public Builder withName(@Nonnull String name) { ... }")
106 | );
107 |
108 | options.add(new CheckboxSelectorOption(
109 | InnerBuilderOption.PMD_AVOID_FIELD_NAME_MATCHING_METHOD_NAME_ANNOTATION,
110 | "Add @SuppressWarnings(\"PMD.AvoidFieldNameMatchingMethodName\") annotation",
111 | 'p',
112 | "Add @SuppressWarnings(\"PMD.AvoidFieldNameMatchingMethodName\") annotation to the generated Builder class")
113 | );
114 |
115 | options.add(new CheckboxSelectorOption(
116 | InnerBuilderOption.WITH_JAVADOC,
117 | "Add Javadoc",
118 | 'c',
119 | "Add Javadoc to generated builder class and methods")
120 | );
121 |
122 | options.add(new CheckboxSelectorOption(
123 | InnerBuilderOption.FIELD_NAMES,
124 | "Use field names in setter",
125 | 's',
126 | "Generate builder methods that has the same parameter names in setter methods as field names, for example: builder.withName(String fieldName)")
127 | );
128 |
129 | return options;
130 | }
131 |
132 | private InnerBuilderOptionSelector() {
133 | }
134 |
135 | @Nullable
136 | public static List selectFieldsAndOptions(final List members,
137 | final Project project) {
138 | if (members == null || members.isEmpty()) {
139 | return null;
140 | }
141 |
142 | if (ApplicationManager.getApplication().isUnitTestMode()) {
143 | return members;
144 | }
145 |
146 | final JComponent[] optionCheckBoxes = buildOptions();
147 |
148 | final PsiFieldMember[] memberArray = members.toArray(new PsiFieldMember[0]);
149 |
150 | final MemberChooser chooser = new MemberChooser<>(memberArray,
151 | false, // allowEmptySelection
152 | true, // allowMultiSelection
153 | project, null, optionCheckBoxes);
154 |
155 | chooser.setTitle("Select Fields and Options for the Builder");
156 | chooser.selectElements(memberArray);
157 | if (chooser.showAndGet()) {
158 | return chooser.getSelectedElements();
159 | }
160 |
161 | return null;
162 | }
163 |
164 | private static JComponent[] buildOptions() {
165 | final PropertiesComponent propertiesComponent = PropertiesComponent.getInstance();
166 | final int optionCount = OPTIONS.size();
167 | final JComponent[] checkBoxesArray = new JComponent[optionCount];
168 | for (int i = 0; i < optionCount; i++) {
169 | checkBoxesArray[i] = buildOptions(propertiesComponent, OPTIONS.get(i));
170 | }
171 |
172 | return checkBoxesArray;
173 | }
174 |
175 | private static JComponent buildOptions(final PropertiesComponent propertiesComponent,
176 | final SelectorOption selectorOption) {
177 |
178 | if (selectorOption instanceof CheckboxSelectorOption) {
179 | return buildCheckbox(propertiesComponent, (CheckboxSelectorOption) selectorOption);
180 | }
181 |
182 | return buildDropdown(propertiesComponent, (DropdownSelectorOption) selectorOption);
183 | }
184 |
185 | private static JComponent buildCheckbox(PropertiesComponent propertiesComponent, CheckboxSelectorOption selectorOption) {
186 | final JCheckBox optionCheckBox = new NonFocusableCheckBox(selectorOption.getCaption());
187 | optionCheckBox.setMnemonic(selectorOption.getMnemonic());
188 | optionCheckBox.setToolTipText(selectorOption.getToolTip());
189 |
190 | final String optionProperty = selectorOption.getOption().getProperty();
191 | optionCheckBox.setSelected(propertiesComponent.isTrueValue(optionProperty));
192 | optionCheckBox.addItemListener(event -> propertiesComponent.setValue(optionProperty, Boolean.toString(optionCheckBox.isSelected())));
193 | return optionCheckBox;
194 | }
195 |
196 | private static JComponent buildDropdown(PropertiesComponent propertiesComponent, DropdownSelectorOption selectorOption) {
197 | final ComboBox comboBox = new ComboBox();
198 | comboBox.setEditable(false);
199 | comboBox.setRenderer(RENDERER);
200 | selectorOption.getValues().forEach(comboBox::addItem);
201 |
202 | comboBox.setSelectedItem(setSelectedComboBoxItem(propertiesComponent, selectorOption));
203 | comboBox.addItemListener(event -> setPropertiesComponentValue(propertiesComponent, selectorOption, event));
204 |
205 | LabeledComponent> labeledComponent = LabeledComponent.create(comboBox, selectorOption.getCaption());
206 | labeledComponent.setToolTipText(selectorOption.getToolTip());
207 |
208 | return labeledComponent;
209 | }
210 |
211 | private static void setPropertiesComponentValue(PropertiesComponent propertiesComponent, DropdownSelectorOption selectorOption, ItemEvent itemEvent) {
212 | DropdownSelectorOptionValue value = (DropdownSelectorOptionValue) itemEvent.getItem();
213 | propertiesComponent.setValue(selectorOption.getOption().getProperty(), value.getOption().getProperty());
214 | }
215 |
216 | private static DropdownSelectorOptionValue setSelectedComboBoxItem(PropertiesComponent propertiesComponent, DropdownSelectorOption selectorOption) {
217 | String selectedValue = propertiesComponent.getValue(selectorOption.getOption().getProperty());
218 | return selectorOption.getValues()
219 | .stream()
220 | .filter(it -> Objects.equals(it.getOption().getProperty(), selectedValue))
221 | .findFirst()
222 | .orElse(selectorOption.getValues().get(0));
223 | }
224 | }
225 |
226 |
--------------------------------------------------------------------------------
/src/main/java/org/jetbrains/plugins/innerbuilder/InnerBuilderUtils.java:
--------------------------------------------------------------------------------
1 | package org.jetbrains.plugins.innerbuilder;
2 |
3 | import com.intellij.openapi.editor.Editor;
4 | import com.intellij.psi.PsiClass;
5 | import com.intellij.psi.PsiElement;
6 | import com.intellij.psi.PsiElementFactory;
7 | import com.intellij.psi.PsiField;
8 | import com.intellij.psi.PsiFile;
9 | import com.intellij.psi.PsiModifier;
10 | import com.intellij.psi.PsiParameter;
11 | import com.intellij.psi.PsiParameterList;
12 | import com.intellij.psi.PsiPrimitiveType;
13 | import com.intellij.psi.PsiStatement;
14 | import com.intellij.psi.PsiType;
15 | import com.intellij.psi.util.PsiTreeUtil;
16 | import com.intellij.psi.util.PsiUtil;
17 | import org.jetbrains.annotations.NonNls;
18 | import org.jetbrains.annotations.NotNull;
19 | import org.jetbrains.annotations.Nullable;
20 |
21 | public final class InnerBuilderUtils {
22 | @NonNls
23 | static final String JAVA_DOT_LANG = "java.lang.";
24 |
25 | private InnerBuilderUtils() { }
26 |
27 | /**
28 | * Does the string have a lowercase character?
29 | *
30 | * @param str the string to test.
31 | * @return true if the string has a lowercase character, false if not.
32 | */
33 | public static boolean hasLowerCaseChar(String str) {
34 | for (int i = 0; i < str.length(); i++) {
35 | if (Character.isLowerCase(str.charAt(i))) {
36 | return true;
37 | }
38 | }
39 |
40 | return false;
41 | }
42 |
43 | public static String capitalize(String str) {
44 | return hasOneLetterPrefix(str) ?
45 | Character.toUpperCase(str.charAt(1)) + str.substring(2) :
46 | Character.toUpperCase(str.charAt(0)) + str.substring(1);
47 | }
48 |
49 | public static boolean hasOneLetterPrefix(String str) {
50 | if (str.length() == 1) return false;
51 | return Character.isLowerCase(str.charAt(0)) && Character.isUpperCase(str.charAt(1));
52 | }
53 |
54 | static String stripJavaLang(String typeString) {
55 | return typeString.startsWith(JAVA_DOT_LANG) ? typeString.substring(JAVA_DOT_LANG.length()) : typeString;
56 | }
57 |
58 | static boolean areParameterListsEqual(PsiParameterList paramList1, PsiParameterList paramList2) {
59 | if (paramList1.getParametersCount() != paramList2.getParametersCount()) {
60 | return false;
61 | }
62 |
63 | final PsiParameter[] param1Params = paramList1.getParameters();
64 | final PsiParameter[] param2Params = paramList2.getParameters();
65 | for (int i = 0; i < param1Params.length; i++) {
66 | final PsiParameter param1Param = param1Params[i];
67 | final PsiParameter param2Param = param2Params[i];
68 |
69 | if (!areTypesPresentableEqual(param1Param.getType(), param2Param.getType())) {
70 | return false;
71 | }
72 | }
73 |
74 | return true;
75 | }
76 |
77 | static boolean areTypesPresentableEqual(PsiType type1, PsiType type2) {
78 | if (type1 != null && type2 != null) {
79 | final String type1Canonical = stripJavaLang(type1.getPresentableText());
80 | final String type2Canonical = stripJavaLang(type2.getPresentableText());
81 | return type1Canonical.equals(type2Canonical);
82 | }
83 |
84 | return false;
85 | }
86 |
87 | /**
88 | * @param file psi file
89 | * @param editor editor
90 | * @return psiClass if class is static or top level. Otherwise returns {@code null}
91 | */
92 | @Nullable
93 | public static PsiClass getStaticOrTopLevelClass(PsiFile file, Editor editor) {
94 | final int offset = editor.getCaretModel().getOffset();
95 | final PsiElement element = file.findElementAt(offset);
96 | if (element == null) {
97 | return null;
98 | }
99 |
100 | PsiClass topLevelClass = PsiUtil.getTopLevelClass(element);
101 | PsiClass psiClass = PsiTreeUtil.getParentOfType(element, PsiClass.class);
102 | if (psiClass != null && (psiClass.hasModifierProperty(PsiModifier.STATIC) ||
103 | psiClass.getManager().areElementsEquivalent(psiClass, topLevelClass)))
104 | return psiClass;
105 | else
106 | return null;
107 | }
108 |
109 | public static boolean isPrimitive(PsiField psiField) {
110 | return (psiField.getType() instanceof PsiPrimitiveType);
111 | }
112 |
113 | static PsiStatement createReturnThis(@NotNull PsiElementFactory psiElementFactory, @Nullable PsiElement context) {
114 | return psiElementFactory.createStatementFromText("return this;", context);
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/src/main/java/org/jetbrains/plugins/innerbuilder/SelectorOption.java:
--------------------------------------------------------------------------------
1 | package org.jetbrains.plugins.innerbuilder;
2 |
3 |
4 | public interface SelectorOption {
5 | InnerBuilderOption getOption();
6 | String getCaption();
7 | String getToolTip();
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/plugin.xml:
--------------------------------------------------------------------------------
1 |
2 | InnerBuilder
3 | InnerBuilder
4 | 1.3.1-SNAPSHOT
5 |
6 | Adds a Builder action to the Generate menu which generates an inner builder class as described in the Effective Java book
7 |
8 | com.intellij.modules.lang
9 | com.intellij.modules.java
10 | com.intellij.modules.vcs
11 |
12 |
14 | 04.07.2022 - 1.2.0 - Added a new option to rename the generated `newBuilder` to `builder`.
15 | 08.12.2021 - 1.1.9 - Remove FindBugs, project is dead.
16 | 08.12.2021 - 1.1.8 - Bugfixes.
17 | 24.11.2021 - 1.1.7 - Work with latest Intellij IDEA and target JDK 17.
18 | 22.12.2020 - 1.1.6 - Work with latest Intellij IDEA.
19 | 14.09.2017 - 1.1.5 - Added 'set' notation.
20 | 09.06.2017 - 1.1.4 - Added a new option to add @SuppressWarnings("PMD.AvoidFieldNameMatchingMethodName") to the generated builder.
21 | 04.06.2016 - 1.1.3 - Added a new option to use field name for setters.
22 | 30.04.2015 - 1.1.2 - Added more options to use JSR-305/Findbugs @Nonnull annotations and generate Javadoc.
23 | 13.06.2014 - 1.1.1 - Bugfix for final fields in copy builder, thanks to neilg.
24 | 27.02.2014 - 1.1.0 - Added checkboxes to customize the builder (new builder method, copy constructor and 'with' notation).
25 | 16.02.2014 - 1.0.8 - Don't include private superclass fields and allow fields with no access modifier.
26 | 01.02.2014 - 1.0.7 - Ignore tinylog and JBoss logging fields.
27 | 14.12.2013 - 1.0.6 - Rewrites existing Builder classes, field ordering in chooser is now superclass-first.
28 | 06.12.2013 - 1.0.5 - Fixed bug with primitive types.
29 | 06.12.2013 - 1.0.4 - Fixed generated field order.
30 | 03.12.2013 - 1.0.3 - Now includes parent class fields in the chooser.
31 | 29.11.2013 - 1.0.1 - Small fixup release.
32 | 22.11.2013 - 1.0.0 - First release, based on an old codebase.
33 | ]]>
34 |
35 |
36 | Mathias Bogaert
37 |
38 |
39 |
40 |
41 |
42 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/pluginIcon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
126 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/pluginIcon_dark.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
144 |
--------------------------------------------------------------------------------