Skip to content

Commit

Permalink
feat: add ability to generate builder with copy constructor (based on #…
Browse files Browse the repository at this point in the history
  • Loading branch information
mjedynak committed Feb 17, 2024
1 parent d01060d commit 50b248f
Show file tree
Hide file tree
Showing 15 changed files with 240 additions and 703 deletions.
2 changes: 2 additions & 0 deletions README.MD
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
Builder Generator Idea plugin ![Java CI with Gradle](https://github.com/mjedynak/builder-generator-idea-plugin/workflows/Java%20CI%20with%20Gradle/badge.svg?branch=master)
===============
Plugin for IntelliJ IDEA that adds ability to generate builder for a class and switch between them.

Switching between builder and source class is similar to 'Go To Test' action.

Generated builder class does not use reflection, only setter methods or constructor.
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ plugins {
}

group = "pl.mjedynak"
version = "1.3.0"
version = "1.4.0"

repositories {
mavenCentral()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ private void writeBuilderIfNecessary(
List<PsiElementClassMember> selectedElements = memberChooserDialog.getSelectedElements();
PsiFieldsForBuilder psiFieldsForBuilder = psiFieldsForBuilderFactory.createPsiFieldsForBuilder(selectedElements, psiClassFromEditor);
BuilderContext context = new BuilderContext(
project, psiFieldsForBuilder, targetDirectory, className, psiClassFromEditor, methodPrefix, createBuilderDialog.isInnerBuilder(), createBuilderDialog.hasButMethod(), createBuilderDialog.useSingleField());
project, psiFieldsForBuilder, targetDirectory, className, psiClassFromEditor, methodPrefix, createBuilderDialog.isInnerBuilder(), createBuilderDialog.hasButMethod(), createBuilderDialog.useSingleField(), createBuilderDialog.hasAddCopyConstructor());
builderWriter.writeBuilder(context, existingBuilder);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,18 +53,20 @@ public class CreateBuilderDialog extends DialogWrapper {

private static BuilderGeneratorSettingsState defaultStates = BuilderGeneratorSettingsState.getInstance();

private PsiHelper psiHelper;
private GuiHelper guiHelper;
private Project project;
private final PsiHelper psiHelper;
private final GuiHelper guiHelper;
private final Project project;
private final PsiClass sourceClass;
private final JTextField targetClassNameField;
private final JTextField targetMethodPrefix;
private final ReferenceEditorComboWithBrowseButton targetPackageField;
private final PsiClass existingBuilder;
private PsiDirectory targetDirectory;
private PsiClass sourceClass;
private JTextField targetClassNameField;
private JTextField targetMethodPrefix;
private JCheckBox innerBuilder;
private JCheckBox butMethod;
private JCheckBox useSingleField;
private ReferenceEditorComboWithBrowseButton targetPackageField;
private PsiClass existingBuilder;
private JCheckBox copyConstructor;



public CreateBuilderDialog(Project project,
Expand Down Expand Up @@ -211,7 +213,6 @@ public void actionPerformed(ActionEvent e) {
panel.add(innerBuilder, gbConstraints);
// Inner builder


// but method
gbConstraints.insets = new Insets(4, 8, 4, 8);
gbConstraints.gridx = 0;
Expand All @@ -232,7 +233,6 @@ public void actionPerformed(ActionEvent e) {
panel.add(butMethod, gbConstraints);
// but method


// useSingleField
gbConstraints.insets = new Insets(4, 8, 4, 8);
gbConstraints.gridx = 0;
Expand All @@ -253,6 +253,26 @@ public void actionPerformed(ActionEvent e) {
panel.add(useSingleField, gbConstraints);
// useSingleField

// copy constructor
gbConstraints.insets = new Insets(4, 8, 4, 8);
gbConstraints.gridx = 0;
gbConstraints.weightx = 0;
gbConstraints.gridy = 7;
gbConstraints.fill = GridBagConstraints.HORIZONTAL;
gbConstraints.anchor = GridBagConstraints.WEST;
panel.add(new JLabel("Add copy constructor"), gbConstraints);

gbConstraints.insets = new Insets(4, 8, 4, 8);
gbConstraints.gridx = 1;
gbConstraints.weightx = 1;
gbConstraints.gridwidth = 1;
gbConstraints.fill = GridBagConstraints.HORIZONTAL;
gbConstraints.anchor = GridBagConstraints.WEST;
copyConstructor = new JCheckBox();
copyConstructor.setSelected(defaultStates.isAddCopyConstructor);
panel.add(copyConstructor, gbConstraints);
// copy constructor

return panel;
}

Expand Down Expand Up @@ -347,6 +367,10 @@ public boolean useSingleField() {
return useSingleField.isSelected();
}

public boolean hasAddCopyConstructor() {
return copyConstructor.isSelected();
}

public PsiDirectory getTargetDirectory() {
return targetDirectory;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.intellij.psi.PsiElementFactory;
import com.intellij.psi.PsiField;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiModifier;
import com.intellij.psi.PsiModifierList;
import com.intellij.psi.PsiParameter;
import com.intellij.psi.PsiType;
Expand All @@ -24,19 +25,17 @@

public class BuilderPsiClassBuilder {

private static final String PRIVATE_STRING = "private";
private static final String SPACE = " ";
private static final String A_PREFIX = " a";
private static final String AN_PREFIX = " an";
private static final String SEMICOLON = ",";
static final String STATIC_MODIFIER = "static";
static final String FINAL_MODIFIER = "final";

private PsiHelper psiHelper = new PsiHelper();
private PsiFieldsModifier psiFieldsModifier = new PsiFieldsModifier();
private PsiFieldVerifier psiFieldVerifier = new PsiFieldVerifier();
private CodeStyleSettings codeStyleSettings = new CodeStyleSettings();
private final PsiHelper psiHelper = new PsiHelper();
private final PsiFieldsModifier psiFieldsModifier = new PsiFieldsModifier();
private final PsiFieldVerifier psiFieldVerifier = new PsiFieldVerifier();
private final CodeStyleSettings codeStyleSettings = new CodeStyleSettings();
private ButMethodCreator butMethodCreator;
private CopyConstructorCreator copyConstructorCreator;
private MethodCreator methodCreator;

private PsiClass srcClass = null;
Expand All @@ -54,22 +53,23 @@ public class BuilderPsiClassBuilder {

private boolean useSingleField = false;
private boolean isInline = false;
private boolean copyConstructor = false;

public BuilderPsiClassBuilder aBuilder(BuilderContext context) {
initializeFields(context);
JavaDirectoryService javaDirectoryService = psiHelper.getJavaDirectoryService();
builderClass = javaDirectoryService.createClass(context.getTargetDirectory(), builderClassName);
PsiModifierList modifierList = builderClass.getModifierList();
modifierList.setModifierProperty(FINAL_MODIFIER, true);
modifierList.setModifierProperty(PsiModifier.FINAL, true);
return this;
}

public BuilderPsiClassBuilder anInnerBuilder(BuilderContext context) {
initializeFields(context);
builderClass = elementFactory.createClass(builderClassName);
PsiModifierList modifierList = builderClass.getModifierList();
modifierList.setModifierProperty(FINAL_MODIFIER, true);
modifierList.setModifierProperty(STATIC_MODIFIER, true);
modifierList.setModifierProperty(PsiModifier.FINAL, true);
modifierList.setModifierProperty(PsiModifier.STATIC, true);
return this;
}

Expand All @@ -87,7 +87,9 @@ private void initializeFields(BuilderContext context) {
bestConstructor = context.getPsiFieldsForBuilder().getBestConstructor();
methodCreator = new MethodCreator(elementFactory, builderClassName);
butMethodCreator = new ButMethodCreator(elementFactory);
copyConstructorCreator = new CopyConstructorCreator(elementFactory);
isInline = allSelectedPsiFields.size() == psiFieldsForConstructor.size();
copyConstructor = context.hasAddCopyConstructor();
}

public BuilderPsiClassBuilder withFields() {
Expand All @@ -103,14 +105,17 @@ public BuilderPsiClassBuilder withFields() {
return this;
}

public BuilderPsiClassBuilder withPrivateConstructor() {
public BuilderPsiClassBuilder withConstructor() {
PsiMethod constructor;
if (useSingleField) {
constructor = elementFactory.createMethodFromText(builderClassName + "(){ " + srcClassFieldName + " = new " + srcClassName + "(); }", srcClass);
} else {
constructor = elementFactory.createConstructor();
}
constructor.getModifierList().setModifierProperty(PRIVATE_STRING, true);

constructor.getModifierList().setModifierProperty(copyConstructor ? PsiModifier.PUBLIC : PsiModifier.PRIVATE, true);


builderClass.add(constructor);
return this;
}
Expand Down Expand Up @@ -140,7 +145,7 @@ public BuilderPsiClassBuilder withSetMethods(String methodPrefix) {
}

private boolean isInnerBuilder(PsiClass aClass) {
return aClass.hasModifierProperty("static");
return aClass.hasModifierProperty(PsiModifier.STATIC);
}

public BuilderPsiClassBuilder withButMethod() {
Expand All @@ -149,6 +154,12 @@ public BuilderPsiClassBuilder withButMethod() {
return this;
}

public BuilderPsiClassBuilder withCopyConstructor() {
final PsiMethod method = copyConstructorCreator.copyConstructor(builderClass, srcClass, isInnerBuilder(builderClass), useSingleField);
builderClass.add(method);
return this;
}

private void createAndAddMethod(PsiField psiField, String methodPrefix) {
builderClass.add(methodCreator.createMethod(psiField, methodPrefix, srcClassFieldName, useSingleField));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package pl.mjedynak.idea.plugins.builder.psi;

import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElementFactory;
import com.intellij.psi.PsiField;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.util.PropertyUtilBase;
import com.intellij.util.IncorrectOperationException;

import static java.util.Objects.nonNull;

public class CopyConstructorCreator {

private final PsiElementFactory elementFactory;

public CopyConstructorCreator(PsiElementFactory elementFactory) {
this.elementFactory = elementFactory;
}

public PsiMethod copyConstructor(PsiClass builderClass, PsiClass srcClass, boolean isInnerBuilder, boolean useSingleField) {
PsiField[] fields = builderClass.getAllFields();
StringBuilder text = new StringBuilder(
"public " + builderClass.getNameIdentifier().getText() + "(" + srcClass.getQualifiedName() + " other) { ");

for (PsiField field : fields) {
text.append("this.").append(field.getName()).append(" = other");

if (srcClass.isRecord()) {
text.append(".").append(field.getName()).append("();");
} else if (isInnerBuilder) {
if (useSingleField) {
text.append(";");
} else {
text.append(".").append(field.getName()).append(";");
}
} else {
if (useSingleField) {
text.append(";");
} else {
text.append(".").append(findFieldGetter(srcClass, field).getName()).append("();");
}
}
}
text.append(" }");

return elementFactory.createMethodFromText(text.toString(), srcClass);
}

private PsiMethod findFieldGetter(final PsiClass srcClass, final PsiField field) {
PsiMethod method = srcClass.findMethodBySignature(PropertyUtilBase.generateGetterPrototype(field), true);

if (nonNull(method)) {
return method;
}

throw new IncorrectOperationException("Could not create copy constructor as cannot get field getters");
}

}
Loading

0 comments on commit 50b248f

Please sign in to comment.