├── 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 | ![screenshot](screenshot_1.png) 11 | 12 | ![screenshot](screenshot_2.png) 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 | --------------------------------------------------------------------------------