├── README.md
├── resources
└── META-INF
│ └── plugin.xml
├── screenshot_1.png
├── screenshot_2.png
└── src
└── online
└── devliving
└── stepbuilder
└── generator
├── SelectorOption.java
├── StepBuilderAction.java
├── StepBuilderCollector.java
├── StepBuilderGenerator.java
├── StepBuilderHandler.java
├── StepBuilderOption.java
├── StepBuilderOptionSelector.java
└── StepBuilderUtils.java
/README.md:
--------------------------------------------------------------------------------
1 | StepBuilder Generator
2 | =======================
3 |
4 | [IntelliJ IDEA](http://www.jetbrains.com/idea/)/[Android Studio](http://developer.android.com/tools/studio/index.html)
5 | plugin that adds a 'Step Builder' action to the Generate menu (Alt+Insert)
6 | which generates a Builder class which follows the Step Builder pattern. You can
7 | read about the Step Builder pattern and why it might be a little more effective than
8 | the usual Builder pattern [here](http://devliving.online/stepbuilder-builder-that-guides-you-through-the-steps/).
9 |
10 | 
11 |
12 | 
13 | ```java
14 | public class Server{
15 | final static int DEFAULT_PORT = 8080;
16 |
17 | private String protocol;
18 | private String url;
19 | private String ipAddress;
20 | private int port;
21 | private String description;
22 | private long uptime;
23 |
24 | private Server(Builder builder) {
25 | protocol = builder.protocol;
26 | url = builder.url;
27 | ipAddress = builder.ipAddress;
28 | port = builder.port;
29 | description = builder.description;
30 | uptime = builder.uptime;
31 | }
32 |
33 | public static IProtocol builder() {
34 | return new Builder();
35 | }
36 |
37 |
38 | public interface IBuild {
39 | Server build();
40 | }
41 |
42 | public interface IUptime {
43 | IBuild withUptime(long val);
44 | }
45 |
46 | public interface IDescription {
47 | IUptime withDescription(String val);
48 | }
49 |
50 | public interface IPort {
51 | IDescription withPort(int val);
52 | }
53 |
54 | public interface IIpAddress {
55 | IPort withIpAddress(String val);
56 | }
57 |
58 | public interface IUrl {
59 | IIpAddress withUrl(String val);
60 | }
61 |
62 | public interface IProtocol {
63 | IUrl withProtocol(String val);
64 | }
65 |
66 | public static final class Builder implements IUptime, IDescription, IPort, IIpAddress, IUrl, IProtocol, IBuild {
67 | private long uptime;
68 | private String description;
69 | private int port;
70 | private String ipAddress;
71 | private String url;
72 | private String protocol;
73 |
74 | private Builder() {
75 | }
76 |
77 | @Override
78 | public IBuild withUptime(long val) {
79 | uptime = val;
80 | return this;
81 | }
82 |
83 | @Override
84 | public IUptime withDescription(String val) {
85 | description = val;
86 | return this;
87 | }
88 |
89 | @Override
90 | public IDescription withPort(int val) {
91 | port = val;
92 | return this;
93 | }
94 |
95 | @Override
96 | public IPort withIpAddress(String val) {
97 | ipAddress = val;
98 | return this;
99 | }
100 |
101 | @Override
102 | public IIpAddress withUrl(String val) {
103 | url = val;
104 | return this;
105 | }
106 |
107 | @Override
108 | public IUrl withProtocol(String val) {
109 | protocol = val;
110 | return this;
111 | }
112 |
113 | public Server build() {
114 | return new Server(this);
115 | }
116 | }
117 | }
118 | ```
119 |
120 | ### Installation
121 |
122 | In IntelliJ IDEA 12.x or later, go to `File` > `Settings` > `Plugins` or
123 | `Intellij IDEA` > `Preferences` > `Plugins` on Mac OSx. Click the `Browse repositories` button, in
124 | the search field, type `online.devliving.stepbuilder.generator` or `Step Builder Generator`.
125 | It should show up in the plugin list. Right-click it and select `Download and Install`.
126 |
127 | #### Manual installation
128 |
129 | Download the plugin jar `stepbuilder.jar` and select "Install Plugin From Disk" in IntelliJ's plugin preferences.
130 |
131 | ### Usage
132 |
133 | Use `Shift+Ctrl+S` or `Alt+Insert` and select `Step Builder`. Choose the mandatory fields
134 | (the fields that must be set for an object of this class) and press `OK`.
135 |
136 | ### Rate
137 |
138 | If you enjoy this plugin, please rate it on it's [plugins.jetbrains.com page](http://plugins.jetbrains.com/plugin/8276).
139 |
140 | ### License
141 |
142 | Licensed under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0).
143 |
144 | The source code is based on the code from the [innerbuilder plugin](https://github.com/analytically/innerbuilder).
145 |
146 | © [Mehedi Hasan Khan](http://devliving.online/)
--------------------------------------------------------------------------------
/resources/META-INF/plugin.xml:
--------------------------------------------------------------------------------
1 |
2 | online.devliving.stepbuilder.generator
3 | StepBuilder Generator
4 | 1.0.2
5 | Mehedi Hasan Khan
6 |
7 | This plugin generates a Builder class following the Step Builder pattern so that you can build instances of
9 | your class more easily in a guided manner.
10 |
11 | Here is an example:
12 |
13 |
14 |
15 | public class Server{
16 |
17 | final static int DEFAULT_PORT = 8080;
18 |
19 | private String protocol;
20 | private String url;
21 | private String ipAddress;
22 | private int port;
23 | private String description;
24 | private long uptime;
25 |
26 | private Server(Builder builder) {
27 | protocol = builder.protocol;
28 | url = builder.url;
29 | ipAddress = builder.ipAddress;
30 | port = builder.port;
31 | description = builder.description;
32 | uptime = builder.uptime;
33 | }
34 |
35 | public static IProtocol builder() {
36 | return new Builder();
37 | }
38 |
39 |
40 | public interface IBuild {
41 | Server build();
42 | }
43 |
44 | public interface IUptime {
45 | IBuild withUptime(long val);
46 | }
47 |
48 | public interface IDescription {
49 | IUptime withDescription(String val);
50 | }
51 |
52 | public interface IPort {
53 | IDescription withPort(int val);
54 | }
55 |
56 | public interface IIpAddress {
57 | IPort withIpAddress(String val);
58 | }
59 |
60 | public interface IUrl {
61 | IIpAddress withUrl(String val);
62 | }
63 |
64 | public interface IProtocol {
65 | IUrl withProtocol(String val);
66 | }
67 |
68 | public static final class Builder implements IUptime, IDescription, IPort, IIpAddress, IUrl, IProtocol, IBuild {
69 | private long uptime;
70 | private String description;
71 | private int port;
72 | private String ipAddress;
73 | private String url;
74 | private String protocol;
75 |
76 | private Builder() {
77 | }
78 |
79 | @Override
80 | public IBuild withUptime(long val) {
81 | uptime = val;
82 | return this;
83 | }
84 |
85 | @Override
86 | public IUptime withDescription(String val) {
87 | description = val;
88 | return this;
89 | }
90 |
91 | @Override
92 | public IDescription withPort(int val) {
93 | port = val;
94 | return this;
95 | }
96 |
97 | @Override
98 | public IPort withIpAddress(String val) {
99 | ipAddress = val;
100 | return this;
101 | }
102 |
103 | @Override
104 | public IIpAddress withUrl(String val) {
105 | url = val;
106 | return this;
107 | }
108 |
109 | @Override
110 | public IUrl withProtocol(String val) {
111 | protocol = val;
112 | return this;
113 | }
114 |
115 | public Server build() {
116 | return new Server(this);
117 | }
118 | }
119 | }
120 |
121 |
122 | ]]>
123 |
124 |
126 |
127 | 1.0.0 |
128 | First release |
129 |
130 |
131 |
132 | 1.0.1 |
133 | Generated interfaces aren't public |
134 |
135 |
136 |
137 | 1.0.2 |
138 | Added option so that you can choose whether to generate interfaces as public or not |
139 |
140 |
141 | ]]>
142 |
143 |
144 |
145 |
146 |
147 |
148 |
150 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
163 |
164 |
165 |
166 |
167 |
168 |
--------------------------------------------------------------------------------
/screenshot_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamMehedi/stepbuilder/d0a46a950ad7e7245329012f99e559949b353546/screenshot_1.png
--------------------------------------------------------------------------------
/screenshot_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamMehedi/stepbuilder/d0a46a950ad7e7245329012f99e559949b353546/screenshot_2.png
--------------------------------------------------------------------------------
/src/online/devliving/stepbuilder/generator/SelectorOption.java:
--------------------------------------------------------------------------------
1 | package online.devliving.stepbuilder.generator;
2 |
3 | public class SelectorOption {
4 | private final StepBuilderOption option;
5 | private final String caption;
6 | private final char mnemonic;
7 | private final String toolTip; //optional
8 |
9 | private SelectorOption(final Builder builder) {
10 | option = builder.option;
11 | caption = builder.caption;
12 | mnemonic = builder.mnemonic;
13 | toolTip = builder.toolTip;
14 | }
15 |
16 | public static IOption newBuilder() {
17 | return new Builder();
18 | }
19 |
20 | public StepBuilderOption getOption() {
21 | return option;
22 | }
23 |
24 | public String getCaption() {
25 | return caption;
26 | }
27 |
28 | public char getMnemonic() {
29 | return mnemonic;
30 | }
31 |
32 | public String getToolTip() {
33 | return toolTip;
34 | }
35 |
36 | interface IOption{
37 | ICaption withOption(StepBuilderOption option);
38 | }
39 |
40 | interface ICaption{
41 | IMnemonic withCaption(String caption);
42 | }
43 |
44 | interface IMnemonic{
45 | IBuild withMnemonic(char mnemonic);
46 | }
47 |
48 | interface IBuild{
49 | IBuild withTooltip(String tooltip);
50 | SelectorOption build();
51 | }
52 |
53 | public static final class Builder implements IOption, ICaption, IMnemonic, IBuild{
54 | private StepBuilderOption option;
55 | private String caption;
56 | private char mnemonic;
57 | private String toolTip;
58 |
59 | private Builder() { }
60 |
61 | public ICaption withOption(final StepBuilderOption option) {
62 | this.option = option;
63 | return this;
64 | }
65 |
66 | public IMnemonic withCaption(final String caption) {
67 | this.caption = caption;
68 | return this;
69 | }
70 |
71 | public IBuild withMnemonic(final char mnemonic) {
72 | this.mnemonic = mnemonic;
73 | return this;
74 | }
75 |
76 | public IBuild withTooltip(final String toolTip) {
77 | this.toolTip = toolTip;
78 | return this;
79 | }
80 |
81 | public SelectorOption build() {
82 | return new SelectorOption(this);
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/online/devliving/stepbuilder/generator/StepBuilderAction.java:
--------------------------------------------------------------------------------
1 | package online.devliving.stepbuilder.generator;
2 |
3 | import org.jetbrains.annotations.NotNull;
4 |
5 | import com.intellij.codeInsight.CodeInsightActionHandler;
6 | import com.intellij.codeInsight.actions.BaseCodeInsightAction;
7 |
8 | import com.intellij.openapi.editor.Editor;
9 | import com.intellij.openapi.project.Project;
10 |
11 | import com.intellij.psi.PsiFile;
12 |
13 | /**
14 | * The IntelliJ IDEA action for this plugin, generates an step builder class.
15 | */
16 | public class StepBuilderAction extends BaseCodeInsightAction {
17 | private final StepBuilderHandler handler = new StepBuilderHandler();
18 |
19 | @NotNull
20 | @Override
21 | protected CodeInsightActionHandler getHandler() {
22 | return handler;
23 | }
24 |
25 | @Override
26 | protected boolean isValidForFile(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) {
27 | return handler.isValidFor(editor, file);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/online/devliving/stepbuilder/generator/StepBuilderCollector.java:
--------------------------------------------------------------------------------
1 | package online.devliving.stepbuilder.generator;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | import org.jetbrains.annotations.Nullable;
7 |
8 | import com.intellij.codeInsight.generation.PsiFieldMember;
9 |
10 | import com.intellij.openapi.editor.Editor;
11 |
12 | import com.intellij.psi.JavaPsiFacade;
13 | import com.intellij.psi.PsiClass;
14 | import com.intellij.psi.PsiElement;
15 | import com.intellij.psi.PsiField;
16 | import com.intellij.psi.PsiFile;
17 | import com.intellij.psi.PsiModifier;
18 | import com.intellij.psi.PsiResolveHelper;
19 | import com.intellij.psi.PsiSubstitutor;
20 | import com.intellij.psi.util.PsiTreeUtil;
21 | import com.intellij.psi.util.TypeConversionUtil;
22 |
23 | import static online.devliving.stepbuilder.generator.StepBuilderUtils.hasLowerCaseChar;
24 |
25 | public final class StepBuilderCollector {
26 | private StepBuilderCollector() { }
27 |
28 | @Nullable
29 | public static List collectFields(final PsiFile file, final Editor editor) {
30 | final int offset = editor.getCaretModel().getOffset();
31 | final PsiElement element = file.findElementAt(offset);
32 | if (element == null) {
33 | return null;
34 | }
35 |
36 | final PsiClass clazz = PsiTreeUtil.getParentOfType(element, PsiClass.class);
37 | if (clazz == null || clazz.hasModifierProperty(PsiModifier.ABSTRACT)) {
38 | return null;
39 | }
40 |
41 | final List allFields = new ArrayList();
42 |
43 | PsiClass classToExtractFieldsFrom = clazz;
44 | while (classToExtractFieldsFrom != null) {
45 | if (classToExtractFieldsFrom.hasModifierProperty(PsiModifier.STATIC)) {
46 | break;
47 | }
48 |
49 | final List classFieldMembers = collectFieldsInClass(element, clazz,
50 | classToExtractFieldsFrom);
51 | allFields.addAll(0, classFieldMembers);
52 |
53 | classToExtractFieldsFrom = classToExtractFieldsFrom.getSuperClass();
54 | }
55 |
56 | return allFields;
57 | }
58 |
59 | private static List collectFieldsInClass(final PsiElement element, final PsiClass accessObjectClass,
60 | final PsiClass clazz) {
61 | final List classFieldMembers = new ArrayList();
62 | final PsiResolveHelper helper = JavaPsiFacade.getInstance(clazz.getProject()).getResolveHelper();
63 |
64 | for (final PsiField field : clazz.getFields()) {
65 |
66 | // check access to the field from the builder container class (eg. private superclass fields)
67 | if (helper.isAccessible(field, accessObjectClass, clazz)
68 | && !PsiTreeUtil.isAncestor(field, element, false)) {
69 |
70 | // skip static fields
71 | if (field.hasModifierProperty(PsiModifier.STATIC)) {
72 | continue;
73 | }
74 |
75 | // skip any uppercase fields
76 | if (!hasLowerCaseChar(field.getName())) {
77 | continue;
78 | }
79 |
80 | // skip eventual logging fields
81 | final String fieldType = field.getType().getCanonicalText();
82 | if ("org.apache.log4j.Logger".equals(fieldType) || "org.apache.logging.log4j.Logger".equals(fieldType)
83 | || "java.util.logging.Logger".equals(fieldType) || "org.slf4j.Logger".equals(fieldType)
84 | || "ch.qos.logback.classic.Logger".equals(fieldType)
85 | || "net.sf.microlog.core.Logger".equals(fieldType)
86 | || "org.apache.commons.logging.Log".equals(fieldType)
87 | || "org.pmw.tinylog.Logger".equals(fieldType) || "org.jboss.logging.Logger".equals(fieldType)
88 | || "jodd.log.Logger".equals(fieldType)) {
89 | continue;
90 | }
91 |
92 | if (field.hasModifierProperty(PsiModifier.FINAL)) {
93 | if (field.getInitializer() != null) {
94 | continue; // skip final fields that are assigned in the declaration
95 | }
96 |
97 | if (!accessObjectClass.isEquivalentTo(clazz)) {
98 | continue; // skip final superclass fields
99 | }
100 | }
101 |
102 | final PsiClass containingClass = field.getContainingClass();
103 | if (containingClass != null) {
104 | classFieldMembers.add(buildFieldMember(field, containingClass, clazz));
105 | }
106 | }
107 | }
108 |
109 | return classFieldMembers;
110 | }
111 |
112 | private static PsiFieldMember buildFieldMember(final PsiField field, final PsiClass containingClass,
113 | final PsiClass clazz) {
114 | return new PsiFieldMember(field,
115 | TypeConversionUtil.getSuperClassSubstitutor(containingClass, clazz, PsiSubstitutor.EMPTY));
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/online/devliving/stepbuilder/generator/StepBuilderGenerator.java:
--------------------------------------------------------------------------------
1 | package online.devliving.stepbuilder.generator;
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.*;
9 | import com.intellij.psi.codeStyle.CodeStyleManager;
10 | import com.intellij.psi.codeStyle.JavaCodeStyleManager;
11 | import com.intellij.psi.javadoc.PsiDocComment;
12 | import com.intellij.psi.util.PropertyUtil;
13 | import com.intellij.psi.util.PsiUtil;
14 | import org.jetbrains.annotations.NonNls;
15 | import org.jetbrains.annotations.NotNull;
16 | import org.jetbrains.annotations.Nullable;
17 |
18 | import java.util.*;
19 |
20 | public class StepBuilderGenerator implements Runnable {
21 |
22 | @NonNls
23 | private static final String BUILDER_CLASS_NAME = "Builder";
24 | @NonNls
25 | private static final String BUILD_STEP_INTERFACE_NAME = "Build";
26 | @NonNls
27 | private static final String INTERFACE_NAME_PREFIX = "I";
28 | @NonNls
29 | private static final String BUILDER_SETTER_DEFAULT_PARAMETER_NAME = "val";
30 | @NonNls
31 | private static final String BUILDER_SETTER_ALTERNATIVE_PARAMETER_NAME = "value";
32 | @NonNls
33 | private static final String OVERRIDE_ANNOTATION = "java.lang.Override";
34 |
35 | private final Project project;
36 | private final PsiFile file;
37 | private final Editor editor;
38 | private final List mandatoryFields;
39 | private final List optionalFields;
40 | private final PsiElementFactory psiElementFactory;
41 |
42 | private StepBuilderGenerator(final Project project, final PsiFile file, final Editor editor,
43 | final List mandatoryFields, final List optionalFields) {
44 | this.project = project;
45 | this.file = file;
46 | this.editor = editor;
47 | this.mandatoryFields = mandatoryFields;
48 | this.optionalFields = optionalFields;
49 | psiElementFactory = JavaPsiFacade.getInstance(project).getElementFactory();
50 | }
51 |
52 | public static void generate(final Project project, final Editor editor, final PsiFile file,
53 | final List selectedFields, final List optionalFields) {
54 | final Runnable builderGenerator = new StepBuilderGenerator(project, file, editor, selectedFields, optionalFields);
55 | ApplicationManager.getApplication().runWriteAction(builderGenerator);
56 | }
57 |
58 | private static EnumSet currentOptions() {
59 | final EnumSet options = EnumSet.noneOf(StepBuilderOption.class);
60 | final PropertiesComponent propertiesComponent = PropertiesComponent.getInstance();
61 | for (final StepBuilderOption option : StepBuilderOption.values()) {
62 | final boolean currentSetting = propertiesComponent.getBoolean(option.getProperty(), false);
63 | if (currentSetting) {
64 | options.add(option);
65 | }
66 | }
67 | return options;
68 | }
69 |
70 | @Override
71 | public void run() {
72 | final PsiClass topLevelClass = StepBuilderUtils.getTopLevelClass(project, file, editor);
73 | if (topLevelClass == null) {
74 | return;
75 | }
76 |
77 | final Set options = currentOptions();
78 |
79 | final List finalFields = new ArrayList(); //should't have setters
80 | final List nonFinalFields = new ArrayList(); //should have setters
81 | final List optionalNonfinalFields = new ArrayList();
82 | final List mandatoryNonfinalFields = new ArrayList();
83 |
84 | //generate the interfaces
85 | //generate optional interface
86 | final PsiClass optionalInterface = createBuildStepInterface(options.contains(StepBuilderOption.PUBLIC_INTERFACES));
87 | final PsiClassType optionalInterfaceType = psiElementFactory.createType(optionalInterface);
88 |
89 | if(optionalFields != null && !optionalFields.isEmpty()) {
90 | for (PsiFieldMember fieldMember : optionalFields) {
91 | if (!fieldMember.getElement().hasModifierProperty(PsiModifier.FINAL)
92 | || options.contains(StepBuilderOption.FINAL_SETTERS)) {
93 |
94 | nonFinalFields.add(fieldMember);
95 | optionalNonfinalFields.add(fieldMember);
96 |
97 | String fieldName = fieldMember.getElement().getName();
98 | String methodName = String.format("with%s", StepBuilderUtils.capitalize(fieldName));
99 | String paramName = BUILDER_SETTER_DEFAULT_PARAMETER_NAME.equals(fieldName) ?
100 | BUILDER_SETTER_ALTERNATIVE_PARAMETER_NAME : BUILDER_SETTER_DEFAULT_PARAMETER_NAME;
101 |
102 | PsiMethod methodStatement = psiElementFactory.createMethodFromText(String.format("%s %s(%s %s);",
103 | optionalInterfaceType.getPresentableText(), methodName, fieldMember.getElement().getType().getPresentableText(),
104 | paramName), optionalInterface);
105 |
106 | optionalInterface.add(methodStatement);
107 | } else {
108 | finalFields.add(fieldMember);
109 | }
110 | }
111 | }
112 | //add build method
113 | PsiMethod methodStatement = psiElementFactory.createMethodFromText(String.format("%s %s();",
114 | psiElementFactory.createType(topLevelClass).getPresentableText(), "build"), optionalInterface);
115 |
116 | optionalInterface.add(methodStatement);
117 |
118 | topLevelClass.add(optionalInterface);
119 |
120 | //generate mandatory interfaces
121 | final List mandatoryInterfaceTypes = new ArrayList();
122 | if(mandatoryFields != null && !mandatoryFields.isEmpty()){
123 | PsiClassType returnType = optionalInterfaceType;
124 |
125 | for(int i = mandatoryFields.size() - 1; i >= 0; i--) {
126 | PsiFieldMember fieldMember = mandatoryFields.get(i);
127 | if (!fieldMember.getElement().hasModifierProperty(PsiModifier.FINAL)
128 | || options.contains(StepBuilderOption.FINAL_SETTERS)) {
129 |
130 | nonFinalFields.add(fieldMember);
131 | mandatoryNonfinalFields.add(fieldMember);
132 |
133 | PsiClass mInterface = generateMandatoryInterface(fieldMember, returnType, options.contains(StepBuilderOption.PUBLIC_INTERFACES));
134 | topLevelClass.add(mInterface);
135 |
136 | returnType = psiElementFactory.createType(mInterface);
137 | mandatoryInterfaceTypes.add(returnType);
138 | }
139 | else{
140 | finalFields.add(fieldMember);
141 | }
142 | }
143 | }
144 |
145 | //create builder class
146 | final PsiClass builderClass = findOrCreateBuilderClass(topLevelClass, mandatoryInterfaceTypes, optionalInterfaceType);
147 | final PsiType builderType = psiElementFactory.createTypeFromText(BUILDER_CLASS_NAME, null);
148 | //topLevelClass.addAfter(psiElementFactory.createCommentFromText("//regionend", null), builderClass);
149 | //add a constructor to the class
150 | final PsiMethod constructor = generateConstructor(topLevelClass, builderType);
151 | addMethod(topLevelClass, null, constructor, true);
152 | //topLevelClass.addBefore(psiElementFactory.createCommentFromText("//region Builder", null), constructor);
153 | //add the fields in Builder
154 | PsiElement lastAddedField = null;
155 | for (final PsiFieldMember fieldMember : nonFinalFields) {
156 | lastAddedField = findOrCreateField(builderClass, fieldMember, lastAddedField);
157 | }
158 |
159 | for (final PsiFieldMember fieldMember : finalFields) {
160 | lastAddedField = findOrCreateField(builderClass, fieldMember, lastAddedField);
161 | PsiUtil.setModifierProperty((PsiField) lastAddedField, PsiModifier.FINAL, true);
162 | }
163 |
164 | // builder constructor, accepting the final fields
165 | final PsiMethod builderConstructorMethod = generateBuilderConstructor(builderClass, finalFields, options);
166 | addMethod(builderClass, null, builderConstructorMethod, false);
167 |
168 | // builder copy constructor or static copy method
169 | if (options.contains(StepBuilderOption.COPY_CONSTRUCTOR)) {
170 | final PsiMethod copyBuilderMethod = generateCopyBuilderMethod(topLevelClass, builderType,
171 | finalFields,nonFinalFields, options);
172 | addMethod(topLevelClass, null, copyBuilderMethod, true);
173 | }
174 |
175 | // builder methods
176 | final PsiClassType lastInterfaceType;
177 |
178 | PsiElement lastAddedElement = null;
179 | if(!mandatoryNonfinalFields.isEmpty()) {
180 | PsiClassType returnType = optionalInterfaceType;
181 |
182 | for (int i = 0; i < mandatoryNonfinalFields.size(); i++) {
183 | final PsiFieldMember member = mandatoryNonfinalFields.get(i);
184 | final PsiMethod setterMethod = generateBuilderSetter(returnType, member, options);
185 | lastAddedElement = addMethod(builderClass, lastAddedElement, setterMethod, false);
186 | returnType = mandatoryInterfaceTypes.get(i);
187 | }
188 |
189 | lastInterfaceType = returnType;
190 | }
191 | else{
192 | lastInterfaceType = optionalInterfaceType;
193 | }
194 |
195 | if(!optionalNonfinalFields.isEmpty()) {
196 | for (int i = 0; i < optionalNonfinalFields.size(); i++) {
197 | final PsiFieldMember member = optionalNonfinalFields.get(i);
198 | final PsiMethod setterMethod = generateBuilderSetter(optionalInterfaceType, member, options);
199 | lastAddedElement = addMethod(builderClass, lastAddedElement, setterMethod, false);
200 | }
201 | }
202 |
203 | //generate the static builder method
204 | final PsiMethod newBuilderMethod = generateNewBuilderMethod(builderType, finalFields, options, lastInterfaceType);
205 | addMethod(topLevelClass, null, newBuilderMethod, false);
206 |
207 | // builder.build() method
208 | final PsiMethod buildMethod = generateBuildMethod(topLevelClass, options);
209 | addMethod(builderClass, lastAddedElement, buildMethod, false);
210 |
211 | JavaCodeStyleManager.getInstance(project).shortenClassReferences(file);
212 | CodeStyleManager.getInstance(project).reformat(builderClass);
213 | }
214 |
215 | private PsiClass createBuildStepInterface(boolean isPublic){
216 | PsiClass buildStep = psiElementFactory.createInterface(INTERFACE_NAME_PREFIX + BUILD_STEP_INTERFACE_NAME);
217 | if(buildStep.getModifierList() != null){
218 | buildStep.getModifierList().setModifierProperty(PsiModifier.PUBLIC, isPublic);
219 | }
220 |
221 | return buildStep;
222 | }
223 |
224 | /**
225 | *
226 | * @param forMember
227 | * @param returnType - should be an interface type
228 | * @return
229 | */
230 | private PsiClass generateMandatoryInterface(PsiFieldMember forMember, PsiType returnType, boolean isPublic){
231 | String capitalizedFieldName = StepBuilderUtils.capitalize(forMember.getElement().getName());
232 | String methodName = String.format("with%s", capitalizedFieldName);
233 | String paramName = BUILDER_SETTER_DEFAULT_PARAMETER_NAME.equals(forMember.getElement().getName())?
234 | BUILDER_SETTER_ALTERNATIVE_PARAMETER_NAME:BUILDER_SETTER_DEFAULT_PARAMETER_NAME;
235 |
236 | PsiClass mInterface = psiElementFactory.createInterface(INTERFACE_NAME_PREFIX + capitalizedFieldName);
237 | if(mInterface.getModifierList() != null){
238 | mInterface.getModifierList().setModifierProperty(PsiModifier.PUBLIC, isPublic);
239 | }
240 |
241 | PsiMethod fieldMethod = psiElementFactory.createMethodFromText(String.format("%s %s(%s %s);", returnType.getPresentableText(),
242 | methodName, forMember.getElement().getType().getPresentableText(), paramName), mInterface);
243 |
244 | mInterface.add(fieldMethod);
245 | return mInterface;
246 | }
247 |
248 | private PsiMethod generateCopyBuilderMethod(final PsiClass topLevelClass, final PsiType builderType,
249 | final Collection finalFields,
250 | final Collection nonFinalfields,
251 | final Set options) {
252 | //create the method
253 | final PsiMethod copyBuilderMethod = psiElementFactory.createMethod("newBuilder", builderType);
254 | PsiUtil.setModifierProperty(copyBuilderMethod, PsiModifier.STATIC, true);
255 | PsiUtil.setModifierProperty(copyBuilderMethod, PsiModifier.PUBLIC, true);
256 |
257 | //add method parameter
258 | final PsiType topLevelClassType = psiElementFactory.createType(topLevelClass);
259 | final PsiParameter parameter = psiElementFactory.createParameter("copy", topLevelClassType);
260 | final PsiModifierList parameterModifierList = parameter.getModifierList();
261 |
262 | copyBuilderMethod.getParameterList().add(parameter);
263 |
264 | //add body to method
265 | final PsiCodeBlock copyBuilderBody = copyBuilderMethod.getBody();
266 | if (copyBuilderBody != null) {
267 | final StringBuilder copyBuilderParameters = new StringBuilder();
268 | for (final PsiFieldMember fieldMember : finalFields) {
269 | if (copyBuilderParameters.length() > 0) {
270 | copyBuilderParameters.append(", ");
271 | }
272 |
273 | copyBuilderParameters.append(String.format("copy.%s", fieldMember.getElement().getName()));
274 | }
275 |
276 | final PsiStatement newBuilderStatement = psiElementFactory.createStatementFromText(String.format(
277 | "%s builder = new %s(%s);", builderType.getPresentableText(),
278 | builderType.getPresentableText(), copyBuilderParameters.toString()),
279 | copyBuilderMethod);
280 | copyBuilderBody.add(newBuilderStatement);
281 |
282 | addCopyBody(nonFinalfields, copyBuilderMethod, "builder.");
283 | copyBuilderBody.add(psiElementFactory.createStatementFromText("return builder;", copyBuilderMethod));
284 | }
285 | return copyBuilderMethod;
286 | }
287 |
288 | private PsiMethod generateCopyConstructor(final PsiClass topLevelClass, final PsiType builderType,
289 | final Collection nonFinalFields,
290 | final Set options) {
291 |
292 | final PsiMethod copyConstructor = psiElementFactory.createConstructor(builderType.getPresentableText());
293 | PsiUtil.setModifierProperty(copyConstructor, PsiModifier.PUBLIC, true);
294 |
295 | final PsiType topLevelClassType = psiElementFactory.createType(topLevelClass);
296 | final PsiParameter constructorParameter = psiElementFactory.createParameter("copy", topLevelClassType);
297 | final PsiModifierList parameterModifierList = constructorParameter.getModifierList();
298 |
299 | copyConstructor.getParameterList().add(constructorParameter);
300 | addCopyBody(nonFinalFields, copyConstructor, "this.");
301 | return copyConstructor;
302 | }
303 |
304 | private void addCopyBody(final Collection fields, final PsiMethod method, final String qName) {
305 | final PsiCodeBlock methodBody = method.getBody();
306 | if (methodBody == null) {
307 | return;
308 | }
309 | for (final PsiFieldMember member : fields) {
310 | final PsiField field = member.getElement();
311 | final PsiStatement assignStatement = psiElementFactory.createStatementFromText(String.format(
312 | "%s%2$s = copy.%2$s;", qName, field.getName()), method);
313 | methodBody.add(assignStatement);
314 | }
315 | }
316 |
317 | private PsiMethod generateBuilderConstructor(final PsiClass builderClass,
318 | final Collection finalFields,
319 | final Set options) {
320 |
321 | final PsiMethod builderConstructor = psiElementFactory.createConstructor(builderClass.getName());
322 | PsiUtil.setModifierProperty(builderConstructor, PsiModifier.PRIVATE, true);
323 |
324 | final PsiCodeBlock builderConstructorBody = builderConstructor.getBody();
325 | if (builderConstructorBody != null) {
326 | for (final PsiFieldMember member : finalFields) {
327 | final PsiField field = member.getElement();
328 | final PsiType fieldType = field.getType();
329 | final String fieldName = field.getName();
330 |
331 | final PsiParameter parameter = psiElementFactory.createParameter(fieldName, fieldType);
332 | final PsiModifierList parameterModifierList = parameter.getModifierList();
333 |
334 | builderConstructor.getParameterList().add(parameter);
335 | final PsiStatement assignStatement = psiElementFactory.createStatementFromText(String.format(
336 | "this.%1$s = %1$s;", fieldName), builderConstructor);
337 | builderConstructorBody.add(assignStatement);
338 | }
339 | }
340 |
341 | return builderConstructor;
342 | }
343 |
344 | private PsiMethod generateNewBuilderMethod(final PsiType builderType, final Collection finalFields,
345 | final Set options, final PsiType returnType) {
346 | final PsiMethod newBuilderMethod = psiElementFactory.createMethod("builder", returnType);
347 | PsiUtil.setModifierProperty(newBuilderMethod, PsiModifier.STATIC, true);
348 | PsiUtil.setModifierProperty(newBuilderMethod, PsiModifier.PUBLIC, true);
349 |
350 | final StringBuilder fieldList = new StringBuilder();
351 | if (!finalFields.isEmpty()) {
352 | for (final PsiFieldMember member : finalFields) {
353 | final PsiField field = member.getElement();
354 | final PsiType fieldType = field.getType();
355 | final String fieldName = field.getName();
356 |
357 | final PsiParameter parameter = psiElementFactory.createParameter(fieldName, fieldType);
358 | final PsiModifierList parameterModifierList = parameter.getModifierList();
359 |
360 | newBuilderMethod.getParameterList().add(parameter);
361 | if (fieldList.length() > 0) {
362 | fieldList.append(", ");
363 | }
364 | fieldList.append(fieldName);
365 | }
366 | }
367 | final PsiCodeBlock newBuilderMethodBody = newBuilderMethod.getBody();
368 | if (newBuilderMethodBody != null) {
369 | final PsiStatement newStatement = psiElementFactory.createStatementFromText(String.format(
370 | "return new %s(%s);", builderType.getPresentableText(), fieldList.toString()),
371 | newBuilderMethod);
372 | newBuilderMethodBody.add(newStatement);
373 | }
374 | return newBuilderMethod;
375 | }
376 |
377 | private PsiMethod generateBuilderSetter(final PsiType returnType, final PsiFieldMember member,
378 | final Set options) {
379 |
380 | final PsiField field = member.getElement();
381 | final PsiType fieldType = field.getType();
382 | final String fieldName = field.getName();
383 |
384 | final String methodName = String.format("with%s", StepBuilderUtils.capitalize(fieldName));
385 |
386 | final String parameterName = !BUILDER_SETTER_DEFAULT_PARAMETER_NAME.equals(fieldName) ?
387 | BUILDER_SETTER_DEFAULT_PARAMETER_NAME :
388 | BUILDER_SETTER_ALTERNATIVE_PARAMETER_NAME;
389 | final PsiMethod setterMethod = psiElementFactory.createMethod(methodName, returnType);
390 |
391 | setterMethod.getModifierList().addAnnotation(OVERRIDE_ANNOTATION);
392 |
393 | setterMethod.getModifierList().setModifierProperty(PsiModifier.PUBLIC, true);
394 | final PsiParameter setterParameter = psiElementFactory.createParameter(parameterName, fieldType);
395 |
396 | if (!(fieldType instanceof PsiPrimitiveType)) {
397 | final PsiModifierList setterParameterModifierList = setterParameter.getModifierList();
398 | }
399 | setterMethod.getParameterList().add(setterParameter);
400 | final PsiCodeBlock setterMethodBody = setterMethod.getBody();
401 | if (setterMethodBody != null) {
402 | final PsiStatement assignStatement = psiElementFactory.createStatementFromText(String.format(
403 | "%s = %s;", fieldName, parameterName), setterMethod);
404 | setterMethodBody.add(assignStatement);
405 | setterMethodBody.add(StepBuilderUtils.createReturnThis(psiElementFactory, setterMethod));
406 | }
407 | setSetterComment(setterMethod, fieldName, parameterName, returnType);
408 | return setterMethod;
409 | }
410 |
411 | private PsiMethod generateConstructor(final PsiClass topLevelClass, final PsiType builderType) {
412 | final PsiMethod constructor = psiElementFactory.createConstructor(topLevelClass.getName());
413 | constructor.getModifierList().setModifierProperty(PsiModifier.PRIVATE, true);
414 |
415 | final PsiParameter builderParameter = psiElementFactory.createParameter("builder", builderType);
416 | constructor.getParameterList().add(builderParameter);
417 |
418 | final PsiCodeBlock constructorBody = constructor.getBody();
419 | if (constructorBody != null) {
420 | for (final PsiFieldMember member : mandatoryFields) {
421 | final PsiField field = member.getElement();
422 |
423 | final PsiMethod setterPrototype = PropertyUtil.generateSetterPrototype(field);
424 | final PsiMethod setter = topLevelClass.findMethodBySignature(setterPrototype, true);
425 |
426 | final String fieldName = field.getName();
427 | boolean isFinal = false;
428 | final PsiModifierList modifierList = field.getModifierList();
429 | if (modifierList != null) {
430 | isFinal = modifierList.hasModifierProperty(PsiModifier.FINAL);
431 | }
432 |
433 | final String assignText;
434 | if (setter == null || isFinal) {
435 | assignText = String.format("%1$s = builder.%1$s;", fieldName);
436 | } else {
437 | assignText = String.format("%s(builder.%s);", setter.getName(), fieldName);
438 | }
439 |
440 | final PsiStatement assignStatement = psiElementFactory.createStatementFromText(assignText, null);
441 | constructorBody.add(assignStatement);
442 | }
443 |
444 | for (final PsiFieldMember member : optionalFields) {
445 | final PsiField field = member.getElement();
446 |
447 | final PsiMethod setterPrototype = PropertyUtil.generateSetterPrototype(field);
448 | final PsiMethod setter = topLevelClass.findMethodBySignature(setterPrototype, true);
449 |
450 | final String fieldName = field.getName();
451 | boolean isFinal = false;
452 | final PsiModifierList modifierList = field.getModifierList();
453 | if (modifierList != null) {
454 | isFinal = modifierList.hasModifierProperty(PsiModifier.FINAL);
455 | }
456 |
457 | final String assignText;
458 | if (setter == null || isFinal) {
459 | assignText = String.format("%1$s = builder.%1$s;", fieldName);
460 | } else {
461 | assignText = String.format("%s(builder.%s);", setter.getName(), fieldName);
462 | }
463 |
464 | final PsiStatement assignStatement = psiElementFactory.createStatementFromText(assignText, null);
465 | constructorBody.add(assignStatement);
466 | }
467 | }
468 |
469 | return constructor;
470 | }
471 |
472 | private PsiMethod generateBuildMethod(final PsiClass topLevelClass, final Set options) {
473 | final PsiType topLevelClassType = psiElementFactory.createType(topLevelClass);
474 | final PsiMethod buildMethod = psiElementFactory.createMethod("build", topLevelClassType);
475 |
476 | buildMethod.getModifierList().setModifierProperty(PsiModifier.PUBLIC, true);
477 |
478 | final PsiCodeBlock buildMethodBody = buildMethod.getBody();
479 | if (buildMethodBody != null) {
480 | final PsiStatement returnStatement = psiElementFactory.createStatementFromText(String.format(
481 | "return new %s(this);", topLevelClass.getName()), buildMethod);
482 | buildMethodBody.add(returnStatement);
483 | }
484 | setBuildMethodComment(buildMethod, topLevelClass);
485 | return buildMethod;
486 | }
487 |
488 | @NotNull
489 | private PsiClass findOrCreateBuilderClass(final PsiClass topLevelClass, Collection interfaces, PsiClassType optionalInterface) {
490 | final PsiClass builderClass = topLevelClass.findInnerClassByName(BUILDER_CLASS_NAME, false);
491 | if (builderClass == null) {
492 | List types = new ArrayList(interfaces);
493 | types.add(optionalInterface);
494 |
495 | return createBuilderClass(topLevelClass, types);
496 | }
497 |
498 | return builderClass;
499 | }
500 |
501 | @NotNull
502 | private PsiClass createBuilderClass(final PsiClass topLevelClass, List implementedTypes) {
503 | final PsiClass builderClass = (PsiClass) topLevelClass.add(psiElementFactory.createClass(BUILDER_CLASS_NAME));
504 | PsiUtil.setModifierProperty(builderClass, PsiModifier.STATIC, true);
505 | PsiUtil.setModifierProperty(builderClass, PsiModifier.FINAL, true);
506 |
507 | if(builderClass.getImplementsList() != null) {
508 | for (PsiClassType type : implementedTypes) {
509 | builderClass.getImplementsList().add(psiElementFactory.createReferenceElementByType(type));
510 | }
511 | }
512 |
513 | setBuilderComment(builderClass, topLevelClass);
514 | return builderClass;
515 | }
516 |
517 | private PsiElement findOrCreateField(final PsiClass builderClass, final PsiFieldMember member,
518 | @Nullable final PsiElement last) {
519 | final PsiField field = member.getElement();
520 | final String fieldName = field.getName();
521 | final PsiType fieldType = field.getType();
522 | final PsiField existingField = builderClass.findFieldByName(fieldName, false);
523 | if (existingField == null || !StepBuilderUtils.areTypesPresentableEqual(existingField.getType(), fieldType)) {
524 | if (existingField != null) {
525 | existingField.delete();
526 | }
527 | final PsiField newField = psiElementFactory.createField(fieldName, fieldType);
528 | if (last != null) {
529 | return builderClass.addAfter(newField, last);
530 | } else {
531 | return builderClass.add(newField);
532 | }
533 | }
534 | return existingField;
535 | }
536 |
537 | private PsiElement addMethod(@NotNull final PsiClass target, @Nullable final PsiElement after,
538 | @NotNull final PsiMethod newMethod, final boolean replace) {
539 | PsiMethod existingMethod = target.findMethodBySignature(newMethod, false);
540 | if (existingMethod == null && newMethod.isConstructor()) {
541 | for (final PsiMethod constructor : target.getConstructors()) {
542 | if (StepBuilderUtils.areParameterListsEqual(constructor.getParameterList(),
543 | newMethod.getParameterList())) {
544 | existingMethod = constructor;
545 | break;
546 | }
547 | }
548 | }
549 | if (existingMethod == null) {
550 | if (after != null) {
551 | return target.addAfter(newMethod, after);
552 | } else {
553 | return target.add(newMethod);
554 | }
555 | } else if (replace) {
556 | existingMethod.replace(newMethod);
557 | }
558 | return existingMethod;
559 | }
560 |
561 | private void setBuilderComment(final PsiClass clazz, final PsiClass topLevelClass) {
562 | if (currentOptions().contains(StepBuilderOption.WITH_JAVADOC)) {
563 | StringBuilder str = new StringBuilder("/**\n").append("* {@code ");
564 | str.append(topLevelClass.getName()).append("} builder static inner class.\n");
565 | str.append("*/");
566 | setStringComment(clazz, str.toString());
567 | }
568 | }
569 |
570 | private void setSetterComment(final PsiMethod method, final String fieldName, final String parameterName, final PsiType returnType) {
571 | if (currentOptions().contains(StepBuilderOption.WITH_JAVADOC)) {
572 | StringBuilder str = new StringBuilder("/**\n").append("* Sets the {@code ").append(fieldName);
573 | str.append("} and returns a reference to {@code ").append(returnType.getPresentableText());
574 | str.append("}\n* @param ").append(parameterName).append(" the {@code ");
575 | str.append(fieldName).append("} to set\n");
576 | str.append("* @return a reference to this Builder\n*/");
577 | setStringComment(method, str.toString());
578 | }
579 | }
580 |
581 | private void setBuildMethodComment(final PsiMethod method, final PsiClass topLevelClass) {
582 | if (currentOptions().contains(StepBuilderOption.WITH_JAVADOC)) {
583 | StringBuilder str = new StringBuilder("/**\n");
584 | str.append("* Returns a {@code ").append(topLevelClass.getName()).append("} built ");
585 | str.append("from the parameters previously set.\n*\n");
586 | str.append("* @return a {@code ").append(topLevelClass.getName()).append("} ");
587 | str.append("built with parameters of this {@code ").append(topLevelClass.getName()).append(".Builder}\n*/");
588 | setStringComment(method, str.toString());
589 | }
590 | }
591 |
592 | private void setStringComment(final PsiMethod method, final String strComment) {
593 | PsiComment comment = psiElementFactory.createCommentFromText(strComment, null);
594 | PsiDocComment doc = method.getDocComment();
595 | if (doc != null) {
596 | doc.replace(comment);
597 | } else {
598 | method.addBefore(comment, method.getFirstChild());
599 | }
600 | }
601 |
602 | private void setStringComment(final PsiClass clazz, final String strComment) {
603 | PsiComment comment = psiElementFactory.createCommentFromText(strComment, null);
604 | PsiDocComment doc = clazz.getDocComment();
605 | if (doc != null) {
606 | doc.replace(comment);
607 | } else {
608 | clazz.addBefore(comment, clazz.getFirstChild());
609 | }
610 | }
611 | }
612 |
--------------------------------------------------------------------------------
/src/online/devliving/stepbuilder/generator/StepBuilderHandler.java:
--------------------------------------------------------------------------------
1 | package online.devliving.stepbuilder.generator;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | import org.jetbrains.annotations.NotNull;
7 |
8 | import com.intellij.codeInsight.CodeInsightUtilBase;
9 | import com.intellij.codeInsight.generation.PsiFieldMember;
10 |
11 | import com.intellij.lang.LanguageCodeInsightActionHandler;
12 |
13 | import com.intellij.openapi.editor.Document;
14 | import com.intellij.openapi.editor.Editor;
15 | import com.intellij.openapi.fileEditor.FileDocumentManager;
16 | import com.intellij.openapi.project.Project;
17 |
18 | import com.intellij.psi.PsiDocumentManager;
19 | import com.intellij.psi.PsiFile;
20 | import com.intellij.psi.PsiJavaFile;
21 |
22 | import static online.devliving.stepbuilder.generator.StepBuilderCollector.collectFields;
23 | import static online.devliving.stepbuilder.generator.StepBuilderOptionSelector.selectFieldsAndOptions;
24 |
25 | public class StepBuilderHandler implements LanguageCodeInsightActionHandler {
26 |
27 | private static boolean isApplicable(final PsiFile file, final Editor editor) {
28 | final List targetElements = collectFields(file, editor);
29 | return targetElements != null && !targetElements.isEmpty();
30 | }
31 |
32 | @Override
33 | public boolean isValidFor(final Editor editor, final PsiFile file) {
34 | if (!(file instanceof PsiJavaFile)) {
35 | return false;
36 | }
37 |
38 | final Project project = editor.getProject();
39 | if (project == null) {
40 | return false;
41 | }
42 |
43 | return StepBuilderUtils.getTopLevelClass(project, file, editor) != null && isApplicable(file, editor);
44 | }
45 |
46 | @Override
47 | public boolean startInWriteAction() {
48 | return false;
49 | }
50 |
51 | @Override
52 | public void invoke(@NotNull final Project project, @NotNull final Editor editor, @NotNull final PsiFile file) {
53 | final PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(project);
54 | final Document currentDocument = psiDocumentManager.getDocument(file);
55 | if (currentDocument == null) {
56 | return;
57 | }
58 |
59 | psiDocumentManager.commitDocument(currentDocument);
60 |
61 | if (!CodeInsightUtilBase.prepareEditorForWrite(editor)) {
62 | return;
63 | }
64 |
65 | if (!FileDocumentManager.getInstance().requestWriting(editor.getDocument(), project)) {
66 | return;
67 | }
68 |
69 | final List existingFields = collectFields(file, editor);
70 | if (existingFields != null) {
71 | final List selectedFields = selectFieldsAndOptions(existingFields, project);
72 |
73 | if (selectedFields == null) {
74 | return;
75 | }
76 | else{
77 | final List optionalFields = new ArrayList(existingFields);
78 | optionalFields.removeAll(selectedFields);
79 |
80 | StepBuilderGenerator.generate(project, editor, file, selectedFields, optionalFields);
81 | }
82 | }
83 | }
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/src/online/devliving/stepbuilder/generator/StepBuilderOption.java:
--------------------------------------------------------------------------------
1 | package online.devliving.stepbuilder.generator;
2 |
3 | public enum StepBuilderOption {
4 |
5 | FINAL_SETTERS("finalSetters"),
6 | COPY_CONSTRUCTOR("copyConstructor"),
7 | WITH_JAVADOC("withJavadoc"),
8 | PUBLIC_INTERFACES("publicInterface");
9 |
10 | private final String property;
11 |
12 | private StepBuilderOption(final String property) {
13 | this.property = String.format("GenerateStepBuilder.%s", property);
14 | }
15 |
16 | public String getProperty() {
17 | return property;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/online/devliving/stepbuilder/generator/StepBuilderOptionSelector.java:
--------------------------------------------------------------------------------
1 | package online.devliving.stepbuilder.generator;
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.ui.NonFocusableCheckBox;
9 | import org.jetbrains.annotations.Nullable;
10 |
11 | import javax.swing.*;
12 | import java.awt.event.ItemEvent;
13 | import java.awt.event.ItemListener;
14 | import java.util.ArrayList;
15 | import java.util.List;
16 |
17 | public final class StepBuilderOptionSelector {
18 | private static final List OPTIONS = createGeneratorOptions();
19 |
20 | private StepBuilderOptionSelector() {
21 | }
22 |
23 | private static List createGeneratorOptions() {
24 | final List options = new ArrayList(8);
25 |
26 | options.add(
27 | SelectorOption.newBuilder()
28 | .withOption(StepBuilderOption.FINAL_SETTERS)
29 | .withCaption("Generate builder methods for final fields")
30 | .withMnemonic('f')
31 | .build());
32 |
33 | options.add(
34 | SelectorOption.newBuilder()
35 | .withOption(StepBuilderOption.COPY_CONSTRUCTOR)
36 | .withCaption("Generate builder copy constructor")
37 | .withMnemonic('o')
38 | .build());
39 |
40 | options.add(
41 | SelectorOption.newBuilder()
42 | .withOption(StepBuilderOption.WITH_JAVADOC)
43 | .withCaption("Add Javadoc")
44 | .withMnemonic('c')
45 | .withTooltip("Add Javadoc to generated builder class and methods")
46 | .build());
47 |
48 | options.add(
49 | SelectorOption.newBuilder()
50 | .withOption(StepBuilderOption.PUBLIC_INTERFACES)
51 | .withCaption("Make Interfaces public")
52 | .withMnemonic('p')
53 | .withTooltip("Make generated interfaces public")
54 | .build());
55 | return options;
56 | }
57 |
58 | @Nullable
59 | public static List selectFieldsAndOptions(final List members,
60 | final Project project) {
61 | if (members == null || members.isEmpty()) {
62 | return null;
63 | }
64 |
65 | if (ApplicationManager.getApplication().isUnitTestMode()) {
66 | return members;
67 | }
68 |
69 | final JCheckBox[] optionCheckBoxes = buildOptionCheckBoxes();
70 |
71 | final PsiFieldMember[] memberArray = members.toArray(new PsiFieldMember[members.size()]);
72 |
73 | final MemberChooser chooser = new MemberChooser(memberArray,
74 | false, // allowEmptySelection
75 | true, // allowMultiSelection
76 | project, null, optionCheckBoxes);
77 |
78 | chooser.setTitle("Select Mandatory Fields and Options for the Builder");
79 | chooser.selectElements(memberArray);
80 | if (chooser.showAndGet()) {
81 | return chooser.getSelectedElements();
82 | }
83 |
84 | return null;
85 | }
86 |
87 | private static JCheckBox[] buildOptionCheckBoxes() {
88 | final PropertiesComponent propertiesComponent = PropertiesComponent.getInstance();
89 | //propertiesComponent.setValue(StepBuilderOption.NEW_BUILDER_METHOD.getProperty(), Boolean.toString(true));
90 | final int optionCount = OPTIONS.size();
91 | final JCheckBox[] checkBoxesArray = new JCheckBox[optionCount];
92 | for (int i = 0; i < optionCount; i++) {
93 | checkBoxesArray[i] = buildOptionCheckBox(propertiesComponent, OPTIONS.get(i));
94 | }
95 |
96 | return checkBoxesArray;
97 | }
98 |
99 | private static JCheckBox buildOptionCheckBox(final PropertiesComponent propertiesComponent,
100 | final SelectorOption selectorOption) {
101 | final StepBuilderOption option = selectorOption.getOption();
102 |
103 | final JCheckBox optionCheckBox = new NonFocusableCheckBox(selectorOption.getCaption());
104 | optionCheckBox.setMnemonic(selectorOption.getMnemonic());
105 | optionCheckBox.setToolTipText(selectorOption.getToolTip());
106 |
107 | final String optionProperty = option.getProperty();
108 | optionCheckBox.setSelected(propertiesComponent.isTrueValue(optionProperty));
109 | optionCheckBox.addItemListener(new ItemListener() {
110 | @Override
111 | public void itemStateChanged(final ItemEvent event) {
112 | propertiesComponent.setValue(optionProperty, Boolean.toString(optionCheckBox.isSelected()));
113 | }
114 | });
115 | return optionCheckBox;
116 | }
117 | }
118 |
119 |
--------------------------------------------------------------------------------
/src/online/devliving/stepbuilder/generator/StepBuilderUtils.java:
--------------------------------------------------------------------------------
1 | package online.devliving.stepbuilder.generator;
2 |
3 | import org.jetbrains.annotations.NonNls;
4 | import org.jetbrains.annotations.NotNull;
5 | import org.jetbrains.annotations.Nullable;
6 |
7 | import com.intellij.codeInsight.generation.PsiFieldMember;
8 |
9 | import com.intellij.openapi.editor.Editor;
10 | import com.intellij.openapi.project.Project;
11 |
12 | import com.intellij.psi.PsiClass;
13 | import com.intellij.psi.PsiElement;
14 | import com.intellij.psi.PsiElementFactory;
15 | import com.intellij.psi.PsiField;
16 | import com.intellij.psi.PsiFile;
17 | import com.intellij.psi.PsiParameter;
18 | import com.intellij.psi.PsiParameterList;
19 | import com.intellij.psi.PsiPrimitiveType;
20 | import com.intellij.psi.PsiStatement;
21 | import com.intellij.psi.PsiType;
22 | import com.intellij.psi.util.PsiUtil;
23 |
24 | public final class StepBuilderUtils {
25 | @NonNls
26 | static final String JAVA_DOT_LANG = "java.lang.";
27 |
28 | private StepBuilderUtils() { }
29 |
30 | /**
31 | * Does the string have a lowercase character?
32 | *
33 | * @param str the string to test.
34 | * @return true if the string has a lowercase character, false if not.
35 | */
36 | public static boolean hasLowerCaseChar(String str) {
37 | for (int i = 0; i < str.length(); i++) {
38 | if (Character.isLowerCase(str.charAt(i))) {
39 | return true;
40 | }
41 | }
42 |
43 | return false;
44 | }
45 |
46 | public static String capitalize(String str) {
47 | return Character.toUpperCase(str.charAt(0)) + str.substring(1);
48 | }
49 |
50 | static String stripJavaLang(String typeString) {
51 | return typeString.startsWith(JAVA_DOT_LANG) ? typeString.substring(JAVA_DOT_LANG.length()) : typeString;
52 | }
53 |
54 | static boolean areParameterListsEqual(PsiParameterList paramList1, PsiParameterList paramList2) {
55 | if (paramList1.getParametersCount() != paramList2.getParametersCount()) {
56 | return false;
57 | }
58 |
59 | final PsiParameter[] param1Params = paramList1.getParameters();
60 | final PsiParameter[] param2Params = paramList2.getParameters();
61 | for (int i = 0; i < param1Params.length; i++) {
62 | final PsiParameter param1Param = param1Params[i];
63 | final PsiParameter param2Param = param2Params[i];
64 |
65 | if (!areTypesPresentableEqual(param1Param.getType(), param2Param.getType())) {
66 | return false;
67 | }
68 | }
69 |
70 | return true;
71 | }
72 |
73 | static boolean areTypesPresentableEqual(PsiType type1, PsiType type2) {
74 | if (type1 != null && type2 != null) {
75 | final String type1Canonical = stripJavaLang(type1.getPresentableText());
76 | final String type2Canonical = stripJavaLang(type2.getPresentableText());
77 | return type1Canonical.equals(type2Canonical);
78 | }
79 |
80 | return false;
81 | }
82 |
83 | @Nullable
84 | public static PsiClass getTopLevelClass(Project project, PsiFile file, Editor editor) {
85 | final int offset = editor.getCaretModel().getOffset();
86 | final PsiElement element = file.findElementAt(offset);
87 | if (element == null) {
88 | return null;
89 | }
90 |
91 | return PsiUtil.getTopLevelClass(element);
92 | }
93 |
94 | public static boolean isPrimitive(PsiField psiField) {
95 | return (psiField.getType() instanceof PsiPrimitiveType);
96 | }
97 |
98 | static PsiStatement createReturnThis(@NotNull PsiElementFactory psiElementFactory, @Nullable PsiElement context) {
99 | return psiElementFactory.createStatementFromText("return this;", context);
100 | }
101 | }
102 |
--------------------------------------------------------------------------------