From 32ff35c92861b5402e75ed7d07a47d7f9b665095 Mon Sep 17 00:00:00 2001 From: "Jindong.Tian" Date: Sun, 8 Jun 2025 00:36:51 +0800 Subject: [PATCH 1/4] =?UTF-8?q?1.4.0=E7=89=88=E6=9C=AC=EF=BC=9A=20?= =?UTF-8?q?=E5=AE=8C=E6=88=90Object=20Copy=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- build.gradle | 6 +- .../action/ClassToJsonAction.java | 8 +- .../objecthelper/action/ClassToXMLAction.java | 8 +- .../objecthelper/action/ObjectCopyAction.java | 117 ++++++++ .../{WhetherEnum.java => EnableEnum.java} | 12 +- .../objecthelper/common/util/PsiUtils.java | 70 ++++- .../config/PluginConfigModel.java | 8 +- .../generator/AbstractMethodGenerator.java | 65 +++- .../copy/AbstractObjectCopyStrategy.java | 17 +- .../copy/SmartObjectCopyGenerator.java | 44 +++ .../strategy/BuilderObjectCopyStrategy.java | 30 +- .../copy/strategy/SetObjectCopyStrategy.java | 18 +- .../method/ObjectCopyMethodGenerator.java | 83 ++---- .../objecthelper/ui/ClassSearchDialog.java | 277 ++++++++++++++++++ .../plugin/objecthelper/ui/ConfigPage.form | 20 +- .../plugin/objecthelper/ui/ConfigPage.java | 12 +- src/main/resources/META-INF/plugin.xml | 8 + 18 files changed, 649 insertions(+), 156 deletions(-) create mode 100644 src/main/java/cn/bigcoder/plugin/objecthelper/action/ObjectCopyAction.java rename src/main/java/cn/bigcoder/plugin/objecthelper/common/enums/{WhetherEnum.java => EnableEnum.java} (66%) create mode 100644 src/main/java/cn/bigcoder/plugin/objecthelper/generator/copy/SmartObjectCopyGenerator.java create mode 100644 src/main/java/cn/bigcoder/plugin/objecthelper/ui/ClassSearchDialog.java diff --git a/README.md b/README.md index 2e927f6..1d7a602 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ File->Settings->Tools->Object Helper 即可进入插件的配置页面 - `generate field mode = target` 代表以方法返回类型的字段为基础生成对象拷贝; `generate field mode = source` 代表以方法入参类型的字段为基础生成对象拷贝。 -- `non-existent field generate annotation = yes` 代表当目标字段在源对象中不存在时,是否以注释的形式生成代码,如果为 `no`,则代表不生成这一个字段拷贝代码。 +- `non-existent field generate annotation = enable` 代表当目标字段在源对象中不存在时,是否以注释的形式生成代码,如果为 `disable`,则代表不生成这一个字段拷贝代码。 ## 未来功能支持计划 diff --git a/build.gradle b/build.gradle index 196c339..731142b 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ plugins { } group 'cn.bigcoder.plugin' -version '1.3.3' +version '1.4.0' repositories { mavenCentral() @@ -29,9 +29,9 @@ intellij { } patchPluginXml { sinceBuild = '211' - untilBuild = '281' + untilBuild = '301.*' changeNotes = """ - 1. new feature: Object Copy Method support builder mode. + 1. new feature: Object Copy support lambda and common scene. """ } test { diff --git a/src/main/java/cn/bigcoder/plugin/objecthelper/action/ClassToJsonAction.java b/src/main/java/cn/bigcoder/plugin/objecthelper/action/ClassToJsonAction.java index 8e96e8c..7341f6b 100644 --- a/src/main/java/cn/bigcoder/plugin/objecthelper/action/ClassToJsonAction.java +++ b/src/main/java/cn/bigcoder/plugin/objecthelper/action/ClassToJsonAction.java @@ -1,5 +1,7 @@ package cn.bigcoder.plugin.objecthelper.action; +import static cn.bigcoder.plugin.objecthelper.common.util.PsiUtils.getOperatePsiClass; + import cn.bigcoder.plugin.objecthelper.common.enums.FunctionSwitchEnum; import cn.bigcoder.plugin.objecthelper.common.util.NotificationUtils; import cn.bigcoder.plugin.objecthelper.config.PluginConfigState; @@ -10,15 +12,13 @@ import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.ide.CopyPasteManager; import com.intellij.psi.PsiClass; - import java.awt.datatransfer.StringSelection; - -import static cn.bigcoder.plugin.objecthelper.common.util.PsiUtils.getOperatePsiClass; +import org.jetbrains.annotations.NotNull; public class ClassToJsonAction extends AbstractClassAnAction { @Override - public void actionPerformed(AnActionEvent anAction) { + public void actionPerformed(@NotNull AnActionEvent anAction) { PsiClass psiClass = getOperatePsiClass(anAction); if (psiClass == null) { return; diff --git a/src/main/java/cn/bigcoder/plugin/objecthelper/action/ClassToXMLAction.java b/src/main/java/cn/bigcoder/plugin/objecthelper/action/ClassToXMLAction.java index 82cb64c..14cc32a 100644 --- a/src/main/java/cn/bigcoder/plugin/objecthelper/action/ClassToXMLAction.java +++ b/src/main/java/cn/bigcoder/plugin/objecthelper/action/ClassToXMLAction.java @@ -1,5 +1,7 @@ package cn.bigcoder.plugin.objecthelper.action; +import static cn.bigcoder.plugin.objecthelper.common.util.PsiUtils.getOperatePsiClass; + import cn.bigcoder.plugin.objecthelper.common.enums.FunctionSwitchEnum; import cn.bigcoder.plugin.objecthelper.common.util.NotificationUtils; import cn.bigcoder.plugin.objecthelper.config.PluginConfigState; @@ -8,17 +10,13 @@ import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.ide.CopyPasteManager; import com.intellij.psi.PsiClass; - -import groovy.json.StringEscapeUtils; import java.awt.datatransfer.StringSelection; import org.jetbrains.annotations.NotNull; -import static cn.bigcoder.plugin.objecthelper.common.util.PsiUtils.getOperatePsiClass; - public class ClassToXMLAction extends AbstractClassAnAction { @Override - public void actionPerformed(AnActionEvent anAction) { + public void actionPerformed(@NotNull AnActionEvent anAction) { PsiClass psiClass = getOperatePsiClass(anAction); if (psiClass == null) { return; diff --git a/src/main/java/cn/bigcoder/plugin/objecthelper/action/ObjectCopyAction.java b/src/main/java/cn/bigcoder/plugin/objecthelper/action/ObjectCopyAction.java new file mode 100644 index 0000000..55f8358 --- /dev/null +++ b/src/main/java/cn/bigcoder/plugin/objecthelper/action/ObjectCopyAction.java @@ -0,0 +1,117 @@ +package cn.bigcoder.plugin.objecthelper.action; + +import static cn.bigcoder.plugin.objecthelper.common.util.PsiUtils.getOperateFieldName; +import static cn.bigcoder.plugin.objecthelper.common.util.PsiUtils.getOperatePsiClass; +import static cn.bigcoder.plugin.objecthelper.common.util.PsiUtils.getPsiClassName; + +import cn.bigcoder.plugin.objecthelper.common.util.NotificationUtils; +import cn.bigcoder.plugin.objecthelper.common.util.StringUtils; +import cn.bigcoder.plugin.objecthelper.generator.Generator; +import cn.bigcoder.plugin.objecthelper.generator.copy.SmartObjectCopyGenerator; +import cn.bigcoder.plugin.objecthelper.ui.ClassSearchDialog; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.editor.CaretModel; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiClass; +import java.util.Objects; +import org.jetbrains.annotations.NotNull; + +public class ObjectCopyAction extends AbstractClassAnAction { + + @Override + public void actionPerformed(@NotNull AnActionEvent anAction) { + PsiClass sourcePsiClass = getOperatePsiClass(anAction); + String operateFieldName = getOperateFieldName(anAction); + if (sourcePsiClass == null || StringUtils.isEmpty(operateFieldName)) { + NotificationUtils.notifyInfo(anAction.getProject(), "The source object type was not obtained"); + return; + } + Project project = anAction.getProject(); + PsiClass targetPsiClass = null; + if (project != null) { + ClassSearchDialog dialog = new ClassSearchDialog(project, "Search target object class"); + if (dialog.showAndGet()) { + targetPsiClass = dialog.getSelectedClass(); + } + } + if (targetPsiClass == null) { + NotificationUtils.notifyInfo(anAction.getProject(), "The target object type was not obtained"); + return; + } + String targetParameterName = StringUtils.firstLowerCase( + Objects.requireNonNull(getPsiClassName(targetPsiClass))); + // 防止方法入参和返回参数名称一致 + if (operateFieldName.equals(targetParameterName)) { + targetParameterName = targetParameterName + "Res"; + } + // 代码生成器 + Generator generator = new SmartObjectCopyGenerator(sourcePsiClass, targetPsiClass, operateFieldName, + targetParameterName); + + insertCode(project, generator.generate()); + } + + /** + * 将代码插入光标处 + * + * @param project + * @param copyCodeStr + */ + private void insertCode(Project project, String copyCodeStr) { + Editor editor = FileEditorManager.getInstance(project).getSelectedTextEditor(); + if (editor == null) { + return; + } + CaretModel caretModel = editor.getCaretModel(); + int offset = caretModel.getOffset(); + Document document = editor.getDocument(); + // 删除光标所在的变量 + int startOffset = findVariableStartOffset(document, offset); + int endOffset = findVariableEndOffset(document, offset); + if (startOffset >= 0 && endOffset >= 0) { + WriteCommandAction.runWriteCommandAction(project, () -> { + document.deleteString(startOffset, endOffset); + }); + } + + WriteCommandAction.runWriteCommandAction(project, () -> { + document.insertString(offset, copyCodeStr); + }); + } + + private int findVariableStartOffset(Document document, int offset) { + int start = offset; + while (start > 0) { + char c = document.getCharsSequence().charAt(start - 1); + if (!Character.isJavaIdentifierPart(c)) { + break; + } + start--; + } + return start; + } + + private int findVariableEndOffset(Document document, int offset) { + int end = offset; + int length = document.getTextLength(); + while (end < length) { + char c = document.getCharsSequence().charAt(end); + if (!Character.isJavaIdentifierPart(c)) { + break; + } + end++; + } + return end; + } + + @Override + public boolean actionShow(AnActionEvent anActionEvent) { + PsiClass sourcePsiClass = getOperatePsiClass(anActionEvent); + String operateFieldName = getOperateFieldName(anActionEvent); + return sourcePsiClass != null && !StringUtils.isEmpty(operateFieldName); + } +} \ No newline at end of file diff --git a/src/main/java/cn/bigcoder/plugin/objecthelper/common/enums/WhetherEnum.java b/src/main/java/cn/bigcoder/plugin/objecthelper/common/enums/EnableEnum.java similarity index 66% rename from src/main/java/cn/bigcoder/plugin/objecthelper/common/enums/WhetherEnum.java rename to src/main/java/cn/bigcoder/plugin/objecthelper/common/enums/EnableEnum.java index 918c484..52067fa 100644 --- a/src/main/java/cn/bigcoder/plugin/objecthelper/common/enums/WhetherEnum.java +++ b/src/main/java/cn/bigcoder/plugin/objecthelper/common/enums/EnableEnum.java @@ -4,13 +4,13 @@ * @author: Jindong.Tian * @date: 2023-12-24 **/ -public enum WhetherEnum implements CommonEnum { - YES("yes"), - NO("no"), +public enum EnableEnum implements CommonEnum { + ENABLE("enable"), + DISABLE("disable"), ; private String code; - WhetherEnum(String code) { + EnableEnum(String code) { this.code = code; } @@ -18,11 +18,11 @@ public String getCode() { return code; } - public static WhetherEnum nameOf(String modify) { + public static EnableEnum nameOf(String modify) { if (modify == null) { return null; } - for (WhetherEnum item : values()) { + for (EnableEnum item : values()) { if (modify.equals(item.getCode())) { return item; } diff --git a/src/main/java/cn/bigcoder/plugin/objecthelper/common/util/PsiUtils.java b/src/main/java/cn/bigcoder/plugin/objecthelper/common/util/PsiUtils.java index c0cc060..686ba38 100644 --- a/src/main/java/cn/bigcoder/plugin/objecthelper/common/util/PsiUtils.java +++ b/src/main/java/cn/bigcoder/plugin/objecthelper/common/util/PsiUtils.java @@ -9,6 +9,7 @@ import com.intellij.openapi.project.Project; import com.intellij.psi.JavaPsiFacade; import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiClassType; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiField; import com.intellij.psi.PsiFile; @@ -18,7 +19,9 @@ import com.intellij.psi.PsiModifierList; import com.intellij.psi.PsiParameter; import com.intellij.psi.PsiType; +import com.intellij.psi.PsiVariable; import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.search.PsiShortNamesCache; import com.intellij.psi.util.PsiTreeUtil; import org.apache.commons.compress.utils.Lists; import org.jetbrains.annotations.NotNull; @@ -58,6 +61,25 @@ public static PsiClass getOperatePsiClass(AnActionEvent actionEvent) { PsiElement psiElement = getOperatePsiElement(actionEvent); if (psiElement instanceof PsiClass) { return (PsiClass) psiElement; + } else if (psiElement instanceof PsiVariable) { + PsiType type = ((PsiVariable) psiElement).getType(); + if (type instanceof PsiClassType) { + return ((PsiClassType) type).resolve(); + } + } + return null; + } + + /** + * 获取当前操作下的 {@code PsiClass} + * + * @param actionEvent + * @return + */ + public static String getOperateFieldName(AnActionEvent actionEvent) { + PsiElement psiElement = getOperatePsiElement(actionEvent); + if (psiElement instanceof PsiVariable) { + return ((PsiVariable) psiElement).getName(); } return null; } @@ -228,14 +250,54 @@ public static List getAllPsiFields(PsiClass psiClass) { public static boolean isMemberField(PsiField psiField) { PsiModifierList modifierList = psiField.getModifierList(); if (modifierList == null || - modifierList.hasModifierProperty(PsiModifier.STATIC) || - modifierList.hasModifierProperty(PsiModifier.FINAL) || - modifierList.hasModifierProperty(PsiModifier.SYNCHRONIZED)) { + modifierList.hasModifierProperty(PsiModifier.STATIC) || + modifierList.hasModifierProperty(PsiModifier.FINAL) || + modifierList.hasModifierProperty(PsiModifier.SYNCHRONIZED)) { return false; } return true; } + /** + * 根据类名称模糊匹配项目中的PsiClass + * + * @param project + * @param classNameStr 模糊匹配的类名称(大小写不敏感) + * @return + */ + public static @NotNull List matchPsiClassByName(Project project, String classNameStr) { + JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project); + PsiShortNamesCache shortNamesCache = PsiShortNamesCache.getInstance(project); + List exactMatches = new ArrayList<>(); + List fuzzyMatches = new ArrayList<>(); + + // 处理带包名的情况 + if (classNameStr.contains(".")) { + PsiClass cls = psiFacade.findClass(classNameStr, GlobalSearchScope.allScope(project)); + if (cls != null) { + exactMatches.add(cls); + } + } + + // 获取所有类名 + String[] allClassNames = shortNamesCache.getAllClassNames(); + for (String className : allClassNames) { + if (className.equalsIgnoreCase(classNameStr)) { + // 完全匹配 + PsiClass[] classes = shortNamesCache.getClassesByName(className, GlobalSearchScope.allScope(project)); + exactMatches.addAll(Arrays.asList(classes)); + } else if (className.toLowerCase().contains(classNameStr.toLowerCase())) { + // 模糊匹配 + PsiClass[] classes = shortNamesCache.getClassesByName(className, GlobalSearchScope.allScope(project)); + fuzzyMatches.addAll(Arrays.asList(classes)); + } + } + + // 合并结果,优先展示完全匹配的类 + exactMatches.addAll(fuzzyMatches); + return exactMatches; + } + /** * 递归获取类中所有字段 * @@ -249,4 +311,4 @@ private static void recursiveAllFields(PsiClass psiClass, List psiFiel psiFields.addAll(Arrays.asList(psiClass.getFields())); recursiveAllFields(psiClass.getSuperClass(), psiFields); } -} +} \ No newline at end of file diff --git a/src/main/java/cn/bigcoder/plugin/objecthelper/config/PluginConfigModel.java b/src/main/java/cn/bigcoder/plugin/objecthelper/config/PluginConfigModel.java index 66fc4b6..e136cbb 100644 --- a/src/main/java/cn/bigcoder/plugin/objecthelper/config/PluginConfigModel.java +++ b/src/main/java/cn/bigcoder/plugin/objecthelper/config/PluginConfigModel.java @@ -2,7 +2,7 @@ import cn.bigcoder.plugin.objecthelper.common.enums.FieldGenerateModeEnum; import cn.bigcoder.plugin.objecthelper.common.enums.FunctionSwitchEnum; -import cn.bigcoder.plugin.objecthelper.common.enums.WhetherEnum; +import cn.bigcoder.plugin.objecthelper.common.enums.EnableEnum; import java.util.Objects; /** @@ -35,7 +35,7 @@ public class PluginConfigModel { /** * Object Copy Method 功能中,Source 和 Target 对象之间差异的字段,是否以代码注释的形式生成代码 */ - private WhetherEnum objectCopyMethodFieldGenerateAnnotation = WhetherEnum.YES; + private EnableEnum objectCopyMethodFieldGenerateAnnotation = EnableEnum.ENABLE; /** * Object Copy Method 功能中,使用builder模式生成拷贝代码时的判断依据,当目标对象类中包含正则所指定的方法,则默认按照builder模式生成,否则使用set模式生成 @@ -83,12 +83,12 @@ public void setObjectCopyMethodFieldGenerateMode( this.objectCopyMethodFieldGenerateMode = objectCopyMethodFieldGenerateMode; } - public WhetherEnum getObjectCopyMethodFieldGenerateAnnotation() { + public EnableEnum getObjectCopyMethodFieldGenerateAnnotation() { return objectCopyMethodFieldGenerateAnnotation; } public void setObjectCopyMethodFieldGenerateAnnotation( - WhetherEnum objectCopyMethodFieldGenerateAnnotation) { + EnableEnum objectCopyMethodFieldGenerateAnnotation) { this.objectCopyMethodFieldGenerateAnnotation = objectCopyMethodFieldGenerateAnnotation; } diff --git a/src/main/java/cn/bigcoder/plugin/objecthelper/generator/AbstractMethodGenerator.java b/src/main/java/cn/bigcoder/plugin/objecthelper/generator/AbstractMethodGenerator.java index c3912c2..7ecc61e 100644 --- a/src/main/java/cn/bigcoder/plugin/objecthelper/generator/AbstractMethodGenerator.java +++ b/src/main/java/cn/bigcoder/plugin/objecthelper/generator/AbstractMethodGenerator.java @@ -1,18 +1,27 @@ package cn.bigcoder.plugin.objecthelper.generator; +import static cn.bigcoder.plugin.objecthelper.common.constant.JavaKeyWord.BLANK_SEPARATOR; +import static cn.bigcoder.plugin.objecthelper.common.constant.JavaKeyWord.BRACE_OPEN; +import static cn.bigcoder.plugin.objecthelper.common.constant.JavaKeyWord.COMMA_SEPARATOR; +import static cn.bigcoder.plugin.objecthelper.common.constant.JavaKeyWord.PARENTHESIS_CLOSE; +import static cn.bigcoder.plugin.objecthelper.common.constant.JavaKeyWord.PARENTHESIS_OPEN; +import static cn.bigcoder.plugin.objecthelper.common.constant.JavaKeyWord.VOID; +import static cn.bigcoder.plugin.objecthelper.common.util.PsiUtils.getMethodName; +import static cn.bigcoder.plugin.objecthelper.common.util.PsiUtils.getMethodReturnClassName; +import static cn.bigcoder.plugin.objecthelper.common.util.PsiUtils.getPsiClass; +import static cn.bigcoder.plugin.objecthelper.common.util.PsiUtils.getPsiClassName; +import static cn.bigcoder.plugin.objecthelper.common.util.PsiUtils.getPsiParameters; + import cn.bigcoder.plugin.objecthelper.common.util.PsiUtils; import cn.bigcoder.plugin.objecthelper.common.util.StringUtils; -import cn.bigcoder.plugin.objecthelper.generator.Generator; import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiClass; import com.intellij.psi.PsiMethod; import com.intellij.psi.PsiParameter; import com.intellij.psi.PsiType; -import org.apache.commons.collections.CollectionUtils; - import java.util.List; - -import static cn.bigcoder.plugin.objecthelper.common.constant.JavaKeyWord.*; -import static cn.bigcoder.plugin.objecthelper.common.util.PsiUtils.*; +import java.util.Objects; +import org.apache.commons.collections.CollectionUtils; /** * @author: Jindong.Tian @@ -25,6 +34,42 @@ public abstract class AbstractMethodGenerator implements Generator { protected Project project; protected PsiMethod psiMethod; + /** + * 方法第一个参数名称 + */ + protected String firstParameterName; + /** + * 方法返回参数名称 + */ + protected String targetParameterName; + /** + * 方法第一个参数类型 + */ + protected PsiClass firstParameterClass; + /** + * 方法返回参数类型 + */ + protected PsiClass targetClass; + + public AbstractMethodGenerator(PsiMethod psiMethod) { + if (psiMethod == null) { + return; + } + this.project = psiMethod.getProject(); + this.psiMethod = psiMethod; + // 获取参数列表第一个参数 + PsiParameter firstParameter = getParameters().get(FIRST_INDEX); + this.firstParameterClass = getPsiClass(firstParameter.getType(), project); + // 获取返回值类型 + this.targetClass = getPsiClass(getReturnType(), project); + this.firstParameterName = firstParameter.getName(); + this.targetParameterName = StringUtils.firstLowerCase(Objects.requireNonNull(getPsiClassName(targetClass))); + // 防止方法入参和返回参数名称一致 + if (firstParameterName.equals(this.targetParameterName)) { + this.targetParameterName = this.targetParameterName + "Res"; + } + } + public String generate() { if (!check()) { return null; @@ -37,9 +82,9 @@ public String generate() { protected StringBuilder generateMethodFirstLine() { StringBuilder builder = new StringBuilder(); - PsiUtils.getMethodModifies(psiMethod.getModifierList()).forEach(e -> builder.append(e.getName()).append(BLANK_SEPARATOR)); - builder.append(getMethodReturnClassName(psiMethod) + BLANK_SEPARATOR) - .append(getMethodName(psiMethod)) + PsiUtils.getMethodModifies(psiMethod.getModifierList()) + .forEach(e -> builder.append(e.getName()).append(BLANK_SEPARATOR)); + builder.append(getMethodReturnClassName(psiMethod)).append(BLANK_SEPARATOR).append(getMethodName(psiMethod)) .append(PARENTHESIS_OPEN) .append(StringUtils.join(getParameters(), PsiParameter::getText, COMMA_SEPARATOR)) .append(PARENTHESIS_CLOSE + BRACE_OPEN); @@ -48,10 +93,12 @@ protected StringBuilder generateMethodFirstLine() { /** * 生成方法体 + * * @return */ protected abstract String generateMethodBody(); + /** * 检查是否具备生成方法所需要的环境 * diff --git a/src/main/java/cn/bigcoder/plugin/objecthelper/generator/copy/AbstractObjectCopyStrategy.java b/src/main/java/cn/bigcoder/plugin/objecthelper/generator/copy/AbstractObjectCopyStrategy.java index 5e27888..5a63fb3 100644 --- a/src/main/java/cn/bigcoder/plugin/objecthelper/generator/copy/AbstractObjectCopyStrategy.java +++ b/src/main/java/cn/bigcoder/plugin/objecthelper/generator/copy/AbstractObjectCopyStrategy.java @@ -1,18 +1,14 @@ package cn.bigcoder.plugin.objecthelper.generator.copy; -import static cn.bigcoder.plugin.objecthelper.common.util.PsiUtils.getPsiClassName; - +import cn.bigcoder.plugin.objecthelper.common.enums.EnableEnum; import cn.bigcoder.plugin.objecthelper.common.enums.FieldGenerateModeEnum; -import cn.bigcoder.plugin.objecthelper.common.enums.WhetherEnum; import cn.bigcoder.plugin.objecthelper.common.util.PsiUtils; -import cn.bigcoder.plugin.objecthelper.common.util.StringUtils; import cn.bigcoder.plugin.objecthelper.config.PluginConfigState; import cn.bigcoder.plugin.objecthelper.generator.Generator; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiField; import java.util.LinkedList; import java.util.List; -import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; @@ -39,16 +35,13 @@ public abstract class AbstractObjectCopyStrategy implements Generator { */ protected String targetParamName; - public AbstractObjectCopyStrategy(PsiClass sourceClass, PsiClass targetClass, String sourceParamName) { + public AbstractObjectCopyStrategy(PsiClass sourceClass, PsiClass targetClass, String sourceParamName, + String targetParamName) { this.sourceClass = sourceClass; this.targetClass = targetClass; this.sourceParamName = sourceParamName; // 生成参数名 - this.targetParamName = StringUtils.firstLowerCase(Objects.requireNonNull(getPsiClassName(targetClass))); - // 防止方法入参和返回参数名称一致 - if (sourceParamName.equals(this.targetParamName)) { - this.targetParamName = this.targetParamName + "Res"; - } + this.targetParamName = targetParamName; } @Override @@ -80,7 +73,7 @@ public String generate() { if (secondFieldNames.contains(field.getName())) { result.append(generateFiledCopy(field)); } else if (PluginConfigState.getInstance().getObjectCopyMethodFieldGenerateAnnotation() - == WhetherEnum.YES) { + == EnableEnum.ENABLE) { // 如果源对象没有该字段,且开启了以注释模式生成代码的开关,则生成注释 annotationLine.add("// " + generateFiledCopy(field)); } diff --git a/src/main/java/cn/bigcoder/plugin/objecthelper/generator/copy/SmartObjectCopyGenerator.java b/src/main/java/cn/bigcoder/plugin/objecthelper/generator/copy/SmartObjectCopyGenerator.java new file mode 100644 index 0000000..a78fe0f --- /dev/null +++ b/src/main/java/cn/bigcoder/plugin/objecthelper/generator/copy/SmartObjectCopyGenerator.java @@ -0,0 +1,44 @@ +package cn.bigcoder.plugin.objecthelper.generator.copy; + +import cn.bigcoder.plugin.objecthelper.config.PluginConfigState; +import cn.bigcoder.plugin.objecthelper.generator.Generator; +import cn.bigcoder.plugin.objecthelper.generator.copy.strategy.BuilderObjectCopyStrategy; +import cn.bigcoder.plugin.objecthelper.generator.copy.strategy.SetObjectCopyStrategy; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiMethod; +import java.util.regex.Pattern; + +/** + * 根据 sourceClass 和 targetClass 生成字段拷贝代码 + * + * @author: bigcoder84 + * @date: 2025-06-08 + **/ +public class SmartObjectCopyGenerator implements Generator { + + private AbstractObjectCopyStrategy objectCopyGenerator; + + + public SmartObjectCopyGenerator(PsiClass sourceClass, PsiClass targetClass, String sourceParameterName, String targetParameterName) { + // mainClass 代表以哪个类字段为基础生成字段拷贝代码 + String builderRegex = PluginConfigState.getInstance().getBuilderInstanceMethodName(); + + // 判断目标类是否有 builder 方法 + Pattern pattern = Pattern.compile(builderRegex); + for (PsiMethod method : targetClass.getMethods()) { + if (pattern.matcher(method.getName()).matches()) { + // 有 builder 方法,使用 BuilderObjectCopyStrategy + objectCopyGenerator = new BuilderObjectCopyStrategy(sourceClass, targetClass, sourceParameterName, + targetParameterName); + return; + } + } + objectCopyGenerator = new SetObjectCopyStrategy(sourceClass, targetClass, sourceParameterName, + targetParameterName); + } + + @Override + public String generate() { + return objectCopyGenerator.generate(); + } +} diff --git a/src/main/java/cn/bigcoder/plugin/objecthelper/generator/copy/strategy/BuilderObjectCopyStrategy.java b/src/main/java/cn/bigcoder/plugin/objecthelper/generator/copy/strategy/BuilderObjectCopyStrategy.java index fb72f61..2960c5c 100644 --- a/src/main/java/cn/bigcoder/plugin/objecthelper/generator/copy/strategy/BuilderObjectCopyStrategy.java +++ b/src/main/java/cn/bigcoder/plugin/objecthelper/generator/copy/strategy/BuilderObjectCopyStrategy.java @@ -15,27 +15,18 @@ **/ public class BuilderObjectCopyStrategy extends AbstractObjectCopyStrategy { - public BuilderObjectCopyStrategy(PsiClass sourceClass, PsiClass targetClass, String sourceParamName) { - super(sourceClass, targetClass, sourceParamName); + public BuilderObjectCopyStrategy(PsiClass sourceClass, PsiClass targetClass, String sourceParamName, + String targetParamName) { + super(sourceClass, targetClass, sourceParamName, targetParamName); } - - /** - * 生成类似如下代码: - * - * if (user == null) { - * return null; - * } - * return UserDto.builder() - * - * @return - */ @Override protected String generatePrefix() { - return generateNullCheck(sourceParamName) + LINE_SEPARATOR + - "return" + BLANK_SEPARATOR + getPsiClassName(targetClass) + ".builder()" + LINE_SEPARATOR; + String className = getPsiClassName(targetClass); + return className + BLANK_SEPARATOR + super.targetParamName + " = " + className + ".builder()" + LINE_SEPARATOR; } + @Override protected String generateFiledCopy(PsiField field) { return "." + field.getName() + "(" @@ -48,13 +39,4 @@ protected String generateFiledCopy(PsiField field) { protected String generateSuffix() { return ".build();" + LINE_SEPARATOR; } - - /** - * 生成示例:{@code if (user == null) {return null;}} - * - * @return - */ - private String generateNullCheck(String sourceParamName) { - return "if(" + sourceParamName + "==null){return null;}"; - } } diff --git a/src/main/java/cn/bigcoder/plugin/objecthelper/generator/copy/strategy/SetObjectCopyStrategy.java b/src/main/java/cn/bigcoder/plugin/objecthelper/generator/copy/strategy/SetObjectCopyStrategy.java index 48dc32a..f58c2a2 100644 --- a/src/main/java/cn/bigcoder/plugin/objecthelper/generator/copy/strategy/SetObjectCopyStrategy.java +++ b/src/main/java/cn/bigcoder/plugin/objecthelper/generator/copy/strategy/SetObjectCopyStrategy.java @@ -16,8 +16,8 @@ **/ public class SetObjectCopyStrategy extends AbstractObjectCopyStrategy { - public SetObjectCopyStrategy(PsiClass sourceClass, PsiClass targetClass, String sourceParamName) { - super(sourceClass, targetClass, sourceParamName); + public SetObjectCopyStrategy(PsiClass sourceClass, PsiClass targetClass, String sourceParamName, String targetParamName) { + super(sourceClass, targetClass, sourceParamName, targetParamName); } /** @@ -33,8 +33,7 @@ public SetObjectCopyStrategy(PsiClass sourceClass, PsiClass targetClass, String @Override protected String generatePrefix() { String psiClassName = getPsiClassName(targetClass); - return generateNullCheck(sourceParamName) + LINE_SEPARATOR + - psiClassName + BLANK_SEPARATOR + targetParamName + "= new " + psiClassName + "();" + LINE_SEPARATOR; + return psiClassName + BLANK_SEPARATOR + targetParamName + " = new " + psiClassName + "();" + LINE_SEPARATOR; } @Override @@ -45,15 +44,6 @@ protected String generateFiledCopy(PsiField field) { @Override protected String generateSuffix() { - return "return " + targetParamName + SEMICOLON_SEPARATOR; - } - - /** - * 生成示例:{@code if (user == null) {return null;}} - * - * @return - */ - private String generateNullCheck(String sourceParamName) { - return "if(" + sourceParamName + "==null){return null;}"; + return ""; } } diff --git a/src/main/java/cn/bigcoder/plugin/objecthelper/generator/method/ObjectCopyMethodGenerator.java b/src/main/java/cn/bigcoder/plugin/objecthelper/generator/method/ObjectCopyMethodGenerator.java index 63a4b78..57e8334 100644 --- a/src/main/java/cn/bigcoder/plugin/objecthelper/generator/method/ObjectCopyMethodGenerator.java +++ b/src/main/java/cn/bigcoder/plugin/objecthelper/generator/method/ObjectCopyMethodGenerator.java @@ -1,16 +1,11 @@ package cn.bigcoder.plugin.objecthelper.generator.method; -import static cn.bigcoder.plugin.objecthelper.common.util.PsiUtils.getPsiClass; +import static cn.bigcoder.plugin.objecthelper.common.constant.JavaKeyWord.SEMICOLON_SEPARATOR; -import cn.bigcoder.plugin.objecthelper.config.PluginConfigState; import cn.bigcoder.plugin.objecthelper.generator.AbstractMethodGenerator; -import cn.bigcoder.plugin.objecthelper.generator.copy.AbstractObjectCopyStrategy; -import cn.bigcoder.plugin.objecthelper.generator.copy.strategy.BuilderObjectCopyStrategy; -import cn.bigcoder.plugin.objecthelper.generator.copy.strategy.SetObjectCopyStrategy; -import com.intellij.psi.PsiClass; +import cn.bigcoder.plugin.objecthelper.generator.Generator; +import cn.bigcoder.plugin.objecthelper.generator.copy.SmartObjectCopyGenerator; import com.intellij.psi.PsiMethod; -import com.intellij.psi.PsiParameter; -import java.util.regex.Pattern; /** * @author: Jindong.Tian @@ -18,72 +13,50 @@ **/ public class ObjectCopyMethodGenerator extends AbstractMethodGenerator { - /** - * 方法第一个参数名称 - */ - private String firstParameterName; - - private void init(PsiMethod psiMethod) { - if (psiMethod == null) { - return; - } - super.project = psiMethod.getProject(); - super.psiMethod = psiMethod; - this.firstParameterName = getFirstParameter().getName(); + public ObjectCopyMethodGenerator(PsiMethod psiMethod) { + super(psiMethod); } public static ObjectCopyMethodGenerator getInstance(PsiMethod psiMethod) { - ObjectCopyMethodGenerator objectCopyMethodGenerator = new ObjectCopyMethodGenerator(); - objectCopyMethodGenerator.init(psiMethod); - return objectCopyMethodGenerator; + return new ObjectCopyMethodGenerator(psiMethod); } /** + * 生成对象拷贝方法提 * 此方法中不应该存在判空的操作,依赖环境的建议重写父类的check方法,在生成早期拦截异常情况 * * @return */ @Override protected String generateMethodBody() { - AbstractObjectCopyStrategy copyStrategy = getCopyStrategy(); - return copyStrategy.generate(); + StringBuilder stringBuilder = new StringBuilder(); + // check null line + String nullCheckStr = generateNullCheck(); + stringBuilder.append(nullCheckStr); + + // object copy block + Generator generator = new SmartObjectCopyGenerator(firstParameterClass, targetClass, firstParameterName, + targetParameterName); + stringBuilder.append(generator.generate()); + + // return line + stringBuilder.append("return ") + .append(targetParameterName) + .append(SEMICOLON_SEPARATOR); + return stringBuilder.toString(); } - private AbstractObjectCopyStrategy getCopyStrategy() { - // mainClass 代表以哪个类字段为基础生成字段拷贝代码 - PsiClass returnClass = getReturnClass(); - PsiClass sourceClass = getFirstParameterClass(); - String builderRegex = PluginConfigState.getInstance().getBuilderInstanceMethodName(); - - Pattern pattern = Pattern.compile(builderRegex); - for (PsiMethod method : returnClass.getMethods()) { - if (pattern.matcher(method.getName()).matches()) { - return new BuilderObjectCopyStrategy(sourceClass, returnClass, this.firstParameterName); - } - } - return new SetObjectCopyStrategy(sourceClass, returnClass, this.firstParameterName); - } /** - * 获取参数列表第一个参数的{@code PsiParameter} + * 生成类似如下代码: * - * @return - */ - private PsiParameter getFirstParameter() { - return getParameters().get(FIRST_INDEX); - } - - - /** - * 获取参数列表第一个参数的{@code PsiClass} + * if (user == null) { + * return null; + * } * * @return */ - private PsiClass getFirstParameterClass() { - return getPsiClass(getFirstParameter().getType(), project); - } - - private PsiClass getReturnClass() { - return getPsiClass(getReturnType(), project); + private String generateNullCheck() { + return "if(" + super.firstParameterName + "==null){return null;}"; } } \ No newline at end of file diff --git a/src/main/java/cn/bigcoder/plugin/objecthelper/ui/ClassSearchDialog.java b/src/main/java/cn/bigcoder/plugin/objecthelper/ui/ClassSearchDialog.java new file mode 100644 index 0000000..7e49766 --- /dev/null +++ b/src/main/java/cn/bigcoder/plugin/objecthelper/ui/ClassSearchDialog.java @@ -0,0 +1,277 @@ +package cn.bigcoder.plugin.objecthelper.ui; + +import cn.bigcoder.plugin.objecthelper.common.util.PsiUtils; +import com.intellij.icons.AllIcons; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiClass; +import com.intellij.ui.components.JBList; +import com.intellij.ui.components.JBScrollPane; +import com.intellij.ui.components.JBTextField; +import com.intellij.util.ArrayUtil; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; +import javax.swing.DefaultListCellRenderer; +import javax.swing.JComponent; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.SwingUtilities; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import org.jetbrains.annotations.Nullable; + +public class ClassSearchDialog extends DialogWrapper { + + private final Project project; + private JBTextField classNameField; + private JBList classList; + private PsiClass selectedClass; + private Timer timer; + private static final int DEBOUNCE_DELAY = 500; // 防抖延迟时间,单位:毫秒 + // 定义默认宽度,可按需调整 + private static final int DEFAULT_WIDTH = 700; + // 定义默认高度,可按需调整 + private static final int DEFAULT_HEIGHT = 600; + + public ClassSearchDialog(@Nullable Project project, String title) { + super(project); + this.project = project; + this.setTitle(title); + this.setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); + this.init(); + } + + @Override + protected void init() { + super.init(); + // 确保对话框显示后输入框获得焦点 + SwingUtilities.invokeLater(() -> classNameField.requestFocusInWindow()); + } + + + @Nullable + @Override + protected JComponent createCenterPanel() { + JPanel panel = new JPanel(new BorderLayout()); + + // 输入框 + classNameField = new JBTextField(); + panel.add(classNameField, BorderLayout.NORTH); + + // 类列表 + classList = new JBList<>(); + classList.setCellRenderer(new ClassListCellRenderer()); + JBScrollPane scrollPane = new JBScrollPane(classList); + panel.add(scrollPane, BorderLayout.CENTER); + + // 输入框文本变化监听,添加防抖机制 + classNameField.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void insertUpdate(DocumentEvent e) { + debounceUpdateClassList(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + debounceUpdateClassList(); + } + + @Override + public void changedUpdate(DocumentEvent e) { + debounceUpdateClassList(); + } + }); + + // 给输入框添加键盘事件监听,处理方向下键 + classNameField.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_DOWN) { + // 当按下方向下键时,将焦点转移到类列表 + classList.requestFocusInWindow(); + if (classList.getModel().getSize() > 0) { + // 选中列表第一项 + classList.setSelectedIndex(0); + } + } + } + }); + + // 双击列表项确认选择 + classList.addListSelectionListener(e -> { + if (!e.getValueIsAdjusting()) { + selectedClass = classList.getSelectedValue(); + } + }); + + // 添加双击事件监听 + classList.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { // 判断是否为双击事件 + if (classList.getSelectedValue() != null) { + selectedClass = classList.getSelectedValue(); + doOKAction(); // 调用确认操作 + } + } + } + }); + + // 监听候选列表的键盘事件,当有按键输入时,将焦点移回输入框 + classList.addKeyListener(new KeyAdapter() { + @Override + public void keyTyped(KeyEvent e) { + if (Character.isLetterOrDigit(e.getKeyChar()) || Character.isWhitespace(e.getKeyChar())) { + classNameField.requestFocusInWindow(); + // 将按下的字符插入到输入框中 + classNameField.setText(classNameField.getText() + e.getKeyChar()); + } + } + }); + + return panel; + } + + + private void debounceUpdateClassList() { + if (timer != null) { + timer.cancel(); + } + timer = new Timer(); + timer.schedule(new TimerTask() { + @Override + public void run() { + SwingUtilities.invokeLater(() -> updateClassList()); + } + }, DEBOUNCE_DELAY); + } + + private void updateClassList() { + String query = classNameField.getText().trim(); + if (!query.isEmpty()) { + List matchedClasses = PsiUtils.matchPsiClassByName(this.project, query); + // 按优先级排序 + matchedClasses.sort(this::compareClasses); + classList.setListData(ArrayUtil.toObjectArray(matchedClasses, PsiClass.class)); + } else { + classList.setListData(new PsiClass[0]); + } + } + + private int compareClasses(PsiClass c1, PsiClass c2) { + int priority1 = getClassPriority(c1); + int priority2 = getClassPriority(c2); + return Integer.compare(priority1, priority2); + } + + private int getClassPriority(PsiClass cls) { + if (isProjectClass(cls)) { + return 1; // 项目内自定义的类 + } else if (isJDKClass(cls)) { + return 2; // Java JDK 自带的类 + } else { + return 3; // 其它类 + } + } + + private boolean isProjectClass(PsiClass cls) { + if (project == null || cls.getContainingFile() == null) { + return false; + } + VirtualFile virtualFile = cls.getContainingFile().getVirtualFile(); + return virtualFile != null && project.getBasePath() != null && virtualFile.getPath() + .startsWith(project.getBasePath()); + } + + private boolean isJDKClass(PsiClass cls) { + String qualifiedName = cls.getQualifiedName(); + return qualifiedName != null && qualifiedName.startsWith("java."); + } + + @Override + protected void doOKAction() { + if (selectedClass == null && classList.getModel().getSize() > 0) { + selectedClass = classList.getModel().getElementAt(0); + } + super.doOKAction(); + } + + public PsiClass getSelectedClass() { + return selectedClass; + } + + // 自定义 ListCellRenderer + private class ClassListCellRenderer extends DefaultListCellRenderer { + + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, + boolean cellHasFocus) { + Component c = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + if (value instanceof PsiClass) { + PsiClass psiClass = (PsiClass) value; + String className = psiClass.getName(); + String qualifiedName = psiClass.getQualifiedName(); + String query = classNameField.getText().trim().toLowerCase(); + + if (className != null && qualifiedName != null) { + // 仅高亮显示类名 + String highlightedClassName = highlightMatches(className, query); + + // 使用 HTML 格式设置文本样式 + String text = String.format("%s of %s", highlightedClassName, + qualifiedName); + setText("" + text + ""); + // 设置 IDEA 官方类图标 + setIcon(AllIcons.Nodes.Class); + + // 如果是 JDK 自带的类,背景用浅蓝色展示 + if (isJDKClass(psiClass)) { + setBackground(new Color(61, 50, 35)); + if (isSelected) { + setBackground(new Color(46, 67, 110)); + } + } else if (!isProjectClass(psiClass)) { + // 其他类,背景用浅粉色展示 + setBackground(new Color(61, 50, 35)); + if (isSelected) { + setBackground(new Color(46, 67, 110)); + } + } else { + // 项目内的类,保持原有背景 + setBackground(list.getBackground()); + } + } + } + return c; + } + + private String highlightMatches(String input, String query) { + if (query.isEmpty()) { + return input; + } + StringBuilder result = new StringBuilder(); + int lastIndex = 0; + int index = input.toLowerCase().indexOf(query); + while (index != -1) { + result.append(input, lastIndex, index); + // 使用亮黄色作为高亮颜色 + result.append("") + .append(input, index, index + query.length()).append(""); + lastIndex = index + query.length(); + index = input.toLowerCase().indexOf(query, lastIndex); + } + result.append(input.substring(lastIndex)); + return result.toString(); + } + } + +} \ No newline at end of file diff --git a/src/main/java/cn/bigcoder/plugin/objecthelper/ui/ConfigPage.form b/src/main/java/cn/bigcoder/plugin/objecthelper/ui/ConfigPage.form index ec970d0..f5572f8 100644 --- a/src/main/java/cn/bigcoder/plugin/objecthelper/ui/ConfigPage.form +++ b/src/main/java/cn/bigcoder/plugin/objecthelper/ui/ConfigPage.form @@ -61,8 +61,8 @@ - - + + @@ -74,8 +74,8 @@ - - + + @@ -86,8 +86,8 @@ - - + + @@ -107,8 +107,8 @@ - - + + @@ -168,8 +168,8 @@ - - + + diff --git a/src/main/java/cn/bigcoder/plugin/objecthelper/ui/ConfigPage.java b/src/main/java/cn/bigcoder/plugin/objecthelper/ui/ConfigPage.java index 985abb4..c2b5162 100644 --- a/src/main/java/cn/bigcoder/plugin/objecthelper/ui/ConfigPage.java +++ b/src/main/java/cn/bigcoder/plugin/objecthelper/ui/ConfigPage.java @@ -1,13 +1,15 @@ package cn.bigcoder.plugin.objecthelper.ui; +import cn.bigcoder.plugin.objecthelper.common.enums.EnableEnum; import cn.bigcoder.plugin.objecthelper.common.enums.FieldGenerateModeEnum; import cn.bigcoder.plugin.objecthelper.common.enums.FunctionSwitchEnum; -import cn.bigcoder.plugin.objecthelper.common.enums.WhetherEnum; -import cn.bigcoder.plugin.objecthelper.config.PluginConfigState; import cn.bigcoder.plugin.objecthelper.config.PluginConfigModel; - +import cn.bigcoder.plugin.objecthelper.config.PluginConfigState; import java.util.Optional; -import javax.swing.*; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JPanel; +import javax.swing.JTextField; /** * @author: Jindong.Tian @@ -73,7 +75,7 @@ public PluginConfigModel getCurrentConfigModel() { } ); Optional.ofNullable(this.objectCopyMethodGenerateAnnotation.getSelectedItem()).ifPresent(e -> { - pluginConfigModel.setObjectCopyMethodFieldGenerateAnnotation(WhetherEnum.nameOf(e.toString())); + pluginConfigModel.setObjectCopyMethodFieldGenerateAnnotation(EnableEnum.nameOf(e.toString())); } ); Optional.ofNullable(this.builderInstanceMethodName.getText()).ifPresent( diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 1581439..21b6ba4 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -48,6 +48,9 @@ + + + @@ -65,6 +68,11 @@ description="Converts the Java class to a XML string"> + + + \ No newline at end of file From b1ce3dde926af8e9153f7694c71948fc06d529c4 Mon Sep 17 00:00:00 2001 From: "Jindong.Tian" Date: Sat, 14 Jun 2025 10:29:25 +0800 Subject: [PATCH 2/4] update readme.md --- README.md | 81 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 52 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 1d7a602..4ab3a4e 100644 --- a/README.md +++ b/README.md @@ -5,55 +5,78 @@
- + - - + +
-JetBrains Intellij IDEA ObjectHelper 插件旨在减少开发者重复低效的劳动,使开发者能够更专注于业务逻辑的开发。 +JetBrains Intellij ObjectHelper 插件致力于消除开发者重复性的低效工作,助力开发者将更多精力聚焦于核心业务逻辑开发,显著提升开发效率。 -该插件包含以下功能: +## 功能介绍 -- 对象拷贝 - set模式: +### 生成对象拷贝方法(Object Copy Method) - ![](https://image.bigcoder.cn/7fce876e-fa94-4780-bb14-584068c35963.gif) +- **set 模式**:通过直观的操作流程,快速生成对象拷贝代码。默认使用快捷键 `Alt+Insert` 触发,若快捷键冲突,可在 `settings->keymap` 中搜索 “Generate” 关键字,查看并修改对应的快捷键设置。 - 对象拷贝的快捷键默认是 `Alt+Insert`,如果该快捷键无效,可以在settings->keymap中搜索“Generate”关键字查看具体的快捷键: +![](https://image.bigcoder.cn/7fce876e-fa94-4780-bb14-584068c35963.gif) - ![](https://image.bigcoder.cn/20220916173117.png) +- **builder 模式**:当对象中包含 builder 或者 newBuilder 方法时,插件会智能识别并采用 builder 模式生成代码,实现更优雅的对象构建。 - 当对象中包含`builder` 或者 `newBuilder`方法时,则插件默认会采用 builder 模式生成代码: +![](https://image.bigcoder.cn/20240505142735.gif) - ![](https://image.bigcoder.cn/20240505142735.gif) +> **使用提示**:使用对象拷贝方法时,需先定义好方法签名,插件将自动生成对应的方法体。适用于对象属性较多且需频繁拷贝的场景,如数据传输对象(DTO)的转换等,但在一些对代码灵活性要求较高的场景中,可能需要额外调整。 - 如果你的builder类生成的方法名与插件默认生成的不同,可以在设置中更改: +### 对象拷贝(Object Copy) - ![](https://image.bigcoder.cn/20240505143027.png) +支持在业务代码和 Lambda 表达式中直接进行对象拷贝操作,极大提升了代码编写的便利性和效率。无论是处理复杂业务逻辑,还是简化数据处理流程,都能轻松应对。 +![](https://image.bigcoder.cn/20250614092815.gif) -- Java类转JSON +### Java类转JSON - ![](https://image.bigcoder.cn/20231224171155.gif) +在开发过程中,若需要快速生成一个类的对象的 JSON 示例,只需右键单击目标类名,即可一键生成,方便接口调试、文档编写等场景下快速获取数据结构示例。 -- Java类转Thrift IDL +![](https://image.bigcoder.cn/20231224171155.gif) - ![](https://image.bigcoder.cn/6eee7a02-8e4e-4f11-9b8c-81d661a920c5.gif) +### Java类转 Thrift IDL -- Java类转XML +对于涉及 Thrift 框架的开发场景,通过右键单击目标类名,可快速生成对应的 Thrift IDL 文件,加速服务接口的定义和开发流程。 - ![](https://image.bigcoder.cn/20231224171113.gif) +![](https://image.bigcoder.cn/6eee7a02-8e4e-4f11-9b8c-81d661a920c5.gif) -- 插件配置 +### Java 类转 XML + +![](https://image.bigcoder.cn/20231224171113.gif) + +## 相关配置 + +### 插件配置 File->Settings->Tools->Object Helper 即可进入插件的配置页面 -![](https://image.bigcoder.cn/20231224170305.png) +![](https://image.bigcoder.cn/202506140953046.png) + +- `Class To Json/Thrift IDL/XML`:控制该功能是否启用。 +- `generate field mode`: + - `source`:以源字段类型的字段为基础生成对象拷贝代码。例如在 `Object Copy Method` 功能,方法第一个入参的类型就是源字段类型,方法返回的类型为目标类型字段。 + - `target`:以目标类为基础生成对象拷贝代码。 +- `non-existent field generate annotation`: + - `enable`:当目标字段在源对象中不存在时,以注释的形式生成代码。有助于开发者清晰了解字段缺失情况,方便后续代码维护和修改。 + + - `disable`:不生成不存在字段的拷贝代码,适用于对代码简洁性要求较高,且能确保目标字段在源对象中都存在的场景。 + +> 假设存在两个 Java 类:`ClassA` 与 `ClassB`,`ClassA` 包含 `a`、`b`、`c` 三个字段,`ClassB` 包含 `b`、`c`、`d` 三个字段。当使用 ObjectHelper 插件将 `ClassA` 实例的数据拷贝至 `ClassB` 实例时: +> +> - **`generate field mode=source`**:插件以 `ClassA` 的字段 `a`、`b`、`c` 为基础生成拷贝代码。若 `ClassB` 中不存在对应字段(如 `a`),则根据 `non-existent field generate annotation` 配置决定是否处理该字段。 +> - **`generate field mode=target`**:插件以 `ClassB` 的字段 `b`、`c`、`d` 为基准生成代码。若 `ClassA` 中无对应字段(如 `d`),同样由 `non-existent field generate annotation` 配置来决定是否生成相关代码或添加注释。 + +- `builder instance method`:用于插件判断当前类是否支持builder模式,配置的是一个正则表达式。如果你的builder类生成的方法名与插件默认生成的不同,可以在设置中更改: + +### 快捷键配置 -- `generate field mode = target` 代表以方法返回类型的字段为基础生成对象拷贝; - `generate field mode = source` 代表以方法入参类型的字段为基础生成对象拷贝。 +对象拷贝的快捷键默认是 `Alt+Insert`,如果该快捷键无效,可以在 `settings->keymap` 中搜索 “Generate” 关键字查看具体的快捷键: -- `non-existent field generate annotation = enable` 代表当目标字段在源对象中不存在时,是否以注释的形式生成代码,如果为 `disable`,则代表不生成这一个字段拷贝代码。 +![](https://image.bigcoder.cn/20220916173117.png) ## 未来功能支持计划 @@ -63,8 +86,8 @@ object-helper插件未来功能支持计划: - [x] Class 转 XML(Class To XML) - [x] 个性化配置 - [x] Object Copy Method 功能支持 Builder 模式 -- [ ] Object Copy Method 功能支持 Lambda 表达式 +- [x] Object Copy 功能支持 Lambda 表达式 - [ ] JSON 转 Class(JSON To Class) -- [ ] Class 转 Protobuf IDL(JSON To Class) -- [ ] All Setter -- [ ] 菜单分组显示 +- [ ] Class 转 Protobuf IDL(JSON To Protobuf) +- [ ] Class 转 Baiji(Class To Baiji) +- [ ] 菜单分组显示 \ No newline at end of file From 7dde10fd16d5e6f847c04e9adf2d2574a51d2662 Mon Sep 17 00:00:00 2001 From: "Jindong.Tian" Date: Sat, 14 Jun 2025 11:33:07 +0800 Subject: [PATCH 3/4] fix: plugin config panel --- .../plugin/objecthelper/action/ClassToJsonAction.java | 2 +- .../objecthelper/action/ClassToThriftIDLAction.java | 2 +- .../plugin/objecthelper/action/ClassToXMLAction.java | 2 +- .../objecthelper/action/ObjectCopyMethodAction.java | 2 +- .../objecthelper/common/enums/FunctionSwitchEnum.java | 4 ++-- .../plugin/objecthelper/config/PluginConfigModel.java | 8 ++++---- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/java/cn/bigcoder/plugin/objecthelper/action/ClassToJsonAction.java b/src/main/java/cn/bigcoder/plugin/objecthelper/action/ClassToJsonAction.java index 7341f6b..206af16 100644 --- a/src/main/java/cn/bigcoder/plugin/objecthelper/action/ClassToJsonAction.java +++ b/src/main/java/cn/bigcoder/plugin/objecthelper/action/ClassToJsonAction.java @@ -30,7 +30,7 @@ public void actionPerformed(@NotNull AnActionEvent anAction) { @Override public boolean actionShow(AnActionEvent anActionEvent) { - return PluginConfigState.getInstance().getJsonSwitch() == FunctionSwitchEnum.OPEN + return PluginConfigState.getInstance().getJsonSwitch() == FunctionSwitchEnum.ENABLE && getOperatePsiClass(anActionEvent) != null; } diff --git a/src/main/java/cn/bigcoder/plugin/objecthelper/action/ClassToThriftIDLAction.java b/src/main/java/cn/bigcoder/plugin/objecthelper/action/ClassToThriftIDLAction.java index d25b2bd..24f28e1 100644 --- a/src/main/java/cn/bigcoder/plugin/objecthelper/action/ClassToThriftIDLAction.java +++ b/src/main/java/cn/bigcoder/plugin/objecthelper/action/ClassToThriftIDLAction.java @@ -31,7 +31,7 @@ public void actionPerformed(@NotNull AnActionEvent anActionEvent) { @Override public boolean actionShow(@NotNull AnActionEvent anActionEvent) { - return PluginConfigState.getInstance().getThriftSwitch() == FunctionSwitchEnum.OPEN + return PluginConfigState.getInstance().getThriftSwitch() == FunctionSwitchEnum.ENABLE && getOperatePsiClass(anActionEvent) != null; } } \ No newline at end of file diff --git a/src/main/java/cn/bigcoder/plugin/objecthelper/action/ClassToXMLAction.java b/src/main/java/cn/bigcoder/plugin/objecthelper/action/ClassToXMLAction.java index 14cc32a..16ca662 100644 --- a/src/main/java/cn/bigcoder/plugin/objecthelper/action/ClassToXMLAction.java +++ b/src/main/java/cn/bigcoder/plugin/objecthelper/action/ClassToXMLAction.java @@ -28,7 +28,7 @@ public void actionPerformed(@NotNull AnActionEvent anAction) { @Override public boolean actionShow(@NotNull AnActionEvent anActionEvent) { - return PluginConfigState.getInstance().getXmlSwitch() == FunctionSwitchEnum.OPEN + return PluginConfigState.getInstance().getXmlSwitch() == FunctionSwitchEnum.ENABLE && getOperatePsiClass(anActionEvent) != null; } diff --git a/src/main/java/cn/bigcoder/plugin/objecthelper/action/ObjectCopyMethodAction.java b/src/main/java/cn/bigcoder/plugin/objecthelper/action/ObjectCopyMethodAction.java index 8b8fe9e..00452f8 100644 --- a/src/main/java/cn/bigcoder/plugin/objecthelper/action/ObjectCopyMethodAction.java +++ b/src/main/java/cn/bigcoder/plugin/objecthelper/action/ObjectCopyMethodAction.java @@ -25,7 +25,7 @@ public void actionPerformed(AnActionEvent anActionEvent) { @Override public boolean actionShow(AnActionEvent anActionEvent) { - return PluginConfigState.getInstance().getObjectCopySwitch() == FunctionSwitchEnum.OPEN + return PluginConfigState.getInstance().getObjectCopySwitch() == FunctionSwitchEnum.ENABLE && check(PsiUtils.getCursorPsiMethod(anActionEvent)); } diff --git a/src/main/java/cn/bigcoder/plugin/objecthelper/common/enums/FunctionSwitchEnum.java b/src/main/java/cn/bigcoder/plugin/objecthelper/common/enums/FunctionSwitchEnum.java index 866c40f..f7a6ebe 100644 --- a/src/main/java/cn/bigcoder/plugin/objecthelper/common/enums/FunctionSwitchEnum.java +++ b/src/main/java/cn/bigcoder/plugin/objecthelper/common/enums/FunctionSwitchEnum.java @@ -5,8 +5,8 @@ * @date: 2023-12-24 **/ public enum FunctionSwitchEnum implements CommonEnum { - OPEN("open"), - CLOSE("close"), + ENABLE("enable"), + DISABLE("disable"), ; private String code; diff --git a/src/main/java/cn/bigcoder/plugin/objecthelper/config/PluginConfigModel.java b/src/main/java/cn/bigcoder/plugin/objecthelper/config/PluginConfigModel.java index e136cbb..8dde736 100644 --- a/src/main/java/cn/bigcoder/plugin/objecthelper/config/PluginConfigModel.java +++ b/src/main/java/cn/bigcoder/plugin/objecthelper/config/PluginConfigModel.java @@ -14,19 +14,19 @@ public class PluginConfigModel { /** * 是否开启 Class To Json 功能,默认为开启状态 */ - private FunctionSwitchEnum jsonSwitch = FunctionSwitchEnum.OPEN; + private FunctionSwitchEnum jsonSwitch = FunctionSwitchEnum.ENABLE; /** * 是否开启 Class To Thrift IDL 功能,默认为开启状态 */ - private FunctionSwitchEnum thriftSwitch = FunctionSwitchEnum.OPEN; + private FunctionSwitchEnum thriftSwitch = FunctionSwitchEnum.ENABLE; /** * 是否开启 Class To XML 功能,默认为开启状态 */ - private FunctionSwitchEnum xmlSwitch = FunctionSwitchEnum.OPEN; + private FunctionSwitchEnum xmlSwitch = FunctionSwitchEnum.ENABLE; /** * 是否开启 Object Copy Method 功能,默认为开启状态 */ - private FunctionSwitchEnum objectCopySwitch = FunctionSwitchEnum.OPEN; + private FunctionSwitchEnum objectCopySwitch = FunctionSwitchEnum.ENABLE; /** * Object Copy Method 功能中,以Source/Target对象为基础生成字段拷贝 */ From f8cca7501fb6e2590acdb8b448f4e4906d6cc960 Mon Sep 17 00:00:00 2001 From: "Jindong.Tian" Date: Sat, 21 Jun 2025 22:02:20 +0800 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E4=B8=80=E4=BA=9B?= =?UTF-8?q?=E9=97=AE=E9=A2=98=EF=BC=8C=E6=94=B9=E5=96=84=E6=8F=92=E4=BB=B6?= =?UTF-8?q?=E4=BD=93=E9=AA=8C=201.=20=E4=BF=AE=E5=A4=8D=20Class=20To=20Jso?= =?UTF-8?q?n=20=E5=AF=B9boolean=E7=B1=BB=E5=9E=8B=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E6=94=AF=E6=8C=81=202.=20=E6=96=B0=E5=A2=9E=20Class=20To=20Jso?= =?UTF-8?q?n=20=E5=AF=B9Map=E7=B1=BB=E5=9E=8B=E5=AD=97=E6=AE=B5=E6=94=AF?= =?UTF-8?q?=E6=8C=81=203.=20=E4=BC=98=E5=8C=96=20Object=20Copy=20=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E4=BB=A3=E7=A0=81=E4=BD=93=E9=AA=8C=204.=20=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E6=8F=92=E4=BB=B6=E5=85=BC=E5=AE=B9=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- build.gradle | 9 +- .../action/AbstractClassAnAction.java | 7 + .../objecthelper/action/ObjectCopyAction.java | 124 ++++++++++++---- .../common/constant/JavaClassName.java | 2 + .../common/util/PsiTypeUtils.java | 5 + .../AbstractDataObjectGenerator.java | 134 +++++++++++------- .../objecthelper/ui/ClassSearchDialog.java | 83 ++++++++--- src/main/resources/META-INF/plugin.xml | 4 +- 9 files changed, 272 insertions(+), 100 deletions(-) diff --git a/README.md b/README.md index 4ab3a4e..d4be63d 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,10 @@
- + - +
JetBrains Intellij ObjectHelper 插件致力于消除开发者重复性的低效工作,助力开发者将更多精力聚焦于核心业务逻辑开发,显著提升开发效率。 diff --git a/build.gradle b/build.gradle index 731142b..44ee143 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ plugins { } group 'cn.bigcoder.plugin' -version '1.4.0' +version '1.4.1' repositories { mavenCentral() @@ -28,10 +28,13 @@ intellij { plugins = ['com.intellij.java'] } patchPluginXml { - sinceBuild = '211' + sinceBuild = '231' untilBuild = '301.*' changeNotes = """ - 1. new feature: Object Copy support lambda and common scene. + 1.Fixed the support for boolean type fields in class to json conversion. + 2.Added support for Map type fields in class to json conversion. + 3.Optimized the code experience of the Object Copy function. + 4.Optimized the plugin compatibility. """ } test { diff --git a/src/main/java/cn/bigcoder/plugin/objecthelper/action/AbstractClassAnAction.java b/src/main/java/cn/bigcoder/plugin/objecthelper/action/AbstractClassAnAction.java index 365eeb0..0fe3612 100644 --- a/src/main/java/cn/bigcoder/plugin/objecthelper/action/AbstractClassAnAction.java +++ b/src/main/java/cn/bigcoder/plugin/objecthelper/action/AbstractClassAnAction.java @@ -2,6 +2,7 @@ import static cn.bigcoder.plugin.objecthelper.common.util.PsiUtils.setActionInvisible; +import com.intellij.openapi.actionSystem.ActionUpdateThread; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import org.jetbrains.annotations.NotNull; @@ -25,4 +26,10 @@ public void update(@NotNull AnActionEvent anActionEvent) { } super.update(anActionEvent); } + + @Override + public @NotNull ActionUpdateThread getActionUpdateThread() { + return ActionUpdateThread.BGT; + } + } diff --git a/src/main/java/cn/bigcoder/plugin/objecthelper/action/ObjectCopyAction.java b/src/main/java/cn/bigcoder/plugin/objecthelper/action/ObjectCopyAction.java index 55f8358..6e8f918 100644 --- a/src/main/java/cn/bigcoder/plugin/objecthelper/action/ObjectCopyAction.java +++ b/src/main/java/cn/bigcoder/plugin/objecthelper/action/ObjectCopyAction.java @@ -9,6 +9,7 @@ import cn.bigcoder.plugin.objecthelper.generator.Generator; import cn.bigcoder.plugin.objecthelper.generator.copy.SmartObjectCopyGenerator; import cn.bigcoder.plugin.objecthelper.ui.ClassSearchDialog; +import com.intellij.openapi.actionSystem.ActionUpdateThread; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.editor.CaretModel; @@ -22,6 +23,7 @@ public class ObjectCopyAction extends AbstractClassAnAction { + @Override public void actionPerformed(@NotNull AnActionEvent anAction) { PsiClass sourcePsiClass = getOperatePsiClass(anAction); @@ -62,6 +64,10 @@ public void actionPerformed(@NotNull AnActionEvent anAction) { * @param copyCodeStr */ private void insertCode(Project project, String copyCodeStr) { + // 检查代码末尾是否有换行符,没有则添加 + if (!copyCodeStr.endsWith("\n")) { + copyCodeStr += "\n"; + } Editor editor = FileEditorManager.getInstance(project).getSelectedTextEditor(); if (editor == null) { return; @@ -69,43 +75,113 @@ private void insertCode(Project project, String copyCodeStr) { CaretModel caretModel = editor.getCaretModel(); int offset = caretModel.getOffset(); Document document = editor.getDocument(); - // 删除光标所在的变量 - int startOffset = findVariableStartOffset(document, offset); - int endOffset = findVariableEndOffset(document, offset); - if (startOffset >= 0 && endOffset >= 0) { + + // 获取当前光标所在行号 + int lineNumber = document.getLineNumber(offset); + int lineStartOffset = document.getLineStartOffset(lineNumber); + int lineEndOffset = document.getLineEndOffset(lineNumber); + + // 检查当前行在光标之后是否有非空白字符 + CharSequence lineText = document.getCharsSequence().subSequence(offset, lineEndOffset); + boolean hasNonWhitespaceAfterCursor = !lineText.toString().trim().isEmpty(); + + // 获取当前行的缩进 + String indent = getLineIndent(document, lineNumber); + // 判断当前行是否为方法头 + String fullLineText = document.getCharsSequence().subSequence(lineStartOffset, lineEndOffset).toString(); + if (isMethodHeader(fullLineText)) { + // 若是方法头,缩进增加一格,假设一格为 4 个空格 + indent += " "; + } + + // 为插入的代码添加缩进 + String indentedCopyCodeStr = addIndentToCode(copyCodeStr, indent); + + if (!hasNonWhitespaceAfterCursor) { + // 如果光标之后没有非空白字符,删除当前行 WriteCommandAction.runWriteCommandAction(project, () -> { - document.deleteString(startOffset, endOffset); + document.deleteString(lineStartOffset, lineEndOffset); }); + offset = lineStartOffset; + } else { + // 如果光标之后有非空白字符,将插入位置移动到下一行开头 + int nextLineStart = document.getLineStartOffset(lineNumber + 1); + offset = nextLineStart; } + int finalOffset = offset; WriteCommandAction.runWriteCommandAction(project, () -> { - document.insertString(offset, copyCodeStr); + document.insertString(finalOffset, indentedCopyCodeStr); }); } - private int findVariableStartOffset(Document document, int offset) { - int start = offset; - while (start > 0) { - char c = document.getCharsSequence().charAt(start - 1); - if (!Character.isJavaIdentifierPart(c)) { - break; - } - start--; + /** + * 获取指定行的缩进 + * @param document 文档对象 + * @param lineNumber 行号 + * @return 缩进字符串 + */ + private String getLineIndent(Document document, int lineNumber) { + int lineStartOffset = document.getLineStartOffset(lineNumber); + int lineEndOffset = document.getLineEndOffset(lineNumber); + CharSequence lineText = document.getCharsSequence().subSequence(lineStartOffset, lineEndOffset); + int indentLength = 0; + while (indentLength < lineText.length() && Character.isWhitespace(lineText.charAt(indentLength))) { + indentLength++; + } + return lineText.subSequence(0, indentLength).toString(); + } + + /** + * 判断当前行是否为方法头 + * @param lineText 当前行的文本内容 + * @return 如果是方法头返回 true,否则返回 false + */ + private boolean isMethodHeader(String lineText) { + // 去除注释内容,避免注释中的括号影响判断 + lineText = removeComments(lineText); + // 去除前后空白字符 + lineText = lineText.trim(); + // 简单判断,包含 ( 和 ) 且不包含 ; 认为是方法头 + return lineText.contains("(") && lineText.contains(")") && !lineText.contains(";"); + } + + + /** + * 移除字符串中的注释内容 + * @param lineText 包含注释的字符串 + * @return 移除注释后的字符串 + */ + private String removeComments(String lineText) { + // 移除单行注释 + int singleCommentIndex = lineText.indexOf("//"); + if (singleCommentIndex != -1) { + lineText = lineText.substring(0, singleCommentIndex); } - return start; + // 移除多行注释开始标记之后的内容(简单处理,不处理嵌套情况) + int multiCommentStartIndex = lineText.indexOf("/*"); + if (multiCommentStartIndex != -1) { + lineText = lineText.substring(0, multiCommentStartIndex); + } + return lineText; } - private int findVariableEndOffset(Document document, int offset) { - int end = offset; - int length = document.getTextLength(); - while (end < length) { - char c = document.getCharsSequence().charAt(end); - if (!Character.isJavaIdentifierPart(c)) { - break; + /** + * 为代码的每一行添加缩进 + * @param code 原始代码 + * @param indent 缩进字符串 + * @return 添加缩进后的代码 + */ + private String addIndentToCode(String code, String indent) { + StringBuilder indentedCode = new StringBuilder(); + String[] lines = code.split("\\r?\\n"); + for (int i = 0; i < lines.length; i++) { + indentedCode.append(indent).append(lines[i]); + if (i < lines.length - 1) { + indentedCode.append("\n"); } - end++; } - return end; + return indentedCode.toString(); } @Override diff --git a/src/main/java/cn/bigcoder/plugin/objecthelper/common/constant/JavaClassName.java b/src/main/java/cn/bigcoder/plugin/objecthelper/common/constant/JavaClassName.java index 297cf03..a1d4c22 100644 --- a/src/main/java/cn/bigcoder/plugin/objecthelper/common/constant/JavaClassName.java +++ b/src/main/java/cn/bigcoder/plugin/objecthelper/common/constant/JavaClassName.java @@ -10,6 +10,7 @@ public class JavaClassName { public static final String LONG_TYPE = "java.lang.Long"; public static final String SHORT_TYPE = "java.lang.Short"; public static final String BYTE_TYPE = "java.lang.Byte"; + public static final String BOOLEAN_TYPE = "java.lang.Boolean"; public static final String DOUBLE_TYPE = "java.lang.Double"; public static final String FLOAT_TYPE = "java.lang.Float"; public static final String DATE_TYPE = "java.util.Date"; @@ -22,6 +23,7 @@ public class JavaClassName { public static final String BASE_BYTE_TYPE = "byte"; public static final String BASE_DOUBLE_TYPE = "double"; public static final String BASE_FLOAT_TYPE = "float"; + public static final String BASE_BOOL_TYPE = "boolean"; public static final String COLLECTION_TYPE = "java.util.Collection"; public static final String MAP_TYPE = "java.util.Map"; diff --git a/src/main/java/cn/bigcoder/plugin/objecthelper/common/util/PsiTypeUtils.java b/src/main/java/cn/bigcoder/plugin/objecthelper/common/util/PsiTypeUtils.java index 9dccef7..ea71389 100644 --- a/src/main/java/cn/bigcoder/plugin/objecthelper/common/util/PsiTypeUtils.java +++ b/src/main/java/cn/bigcoder/plugin/objecthelper/common/util/PsiTypeUtils.java @@ -43,6 +43,8 @@ public class PsiTypeUtils { DATA_TYPES.add(LOCAL_DATE_TYPE); DATA_TYPES.add(LOCAL_DATE_TIME_TYPE); DATA_TYPES.add(BIG_DECIMAL); + DATA_TYPES.add(BOOLEAN_TYPE); + DATA_TYPES.add(BASE_BOOL_TYPE); } /** @@ -76,6 +78,9 @@ public static Object getDataTypeDefaultValue(String canonicalText) { case FLOAT_TYPE: case BASE_FLOAT_TYPE: return 1.1f; + case BOOLEAN_TYPE: + case BASE_BOOL_TYPE: + return true; case DATE_TYPE: return DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss"); case LOCAL_DATE_TYPE: diff --git a/src/main/java/cn/bigcoder/plugin/objecthelper/generator/AbstractDataObjectGenerator.java b/src/main/java/cn/bigcoder/plugin/objecthelper/generator/AbstractDataObjectGenerator.java index d490bfe..4d88424 100644 --- a/src/main/java/cn/bigcoder/plugin/objecthelper/generator/AbstractDataObjectGenerator.java +++ b/src/main/java/cn/bigcoder/plugin/objecthelper/generator/AbstractDataObjectGenerator.java @@ -1,5 +1,6 @@ package cn.bigcoder.plugin.objecthelper.generator; +import cn.bigcoder.plugin.objecthelper.common.constant.JavaClassName; import cn.bigcoder.plugin.objecthelper.common.util.PsiTypeUtils; import cn.bigcoder.plugin.objecthelper.common.util.PsiUtils; import com.google.common.collect.Maps; @@ -23,66 +24,91 @@ **/ public abstract class AbstractDataObjectGenerator implements Generator { - private PsiClass psiClass; + private PsiClass psiClass; - public AbstractDataObjectGenerator(PsiClass psiClass) { - this.psiClass = psiClass; - } - - /** - * 保存已经解析过的自定义类型名称,防止出现递归嵌套的情况 - */ - private Set recursiveCache = Sets.newHashSet(); + public AbstractDataObjectGenerator(PsiClass psiClass) { + this.psiClass = psiClass; + } - protected Map processFields() { - return processFields(psiClass); - } + /** + * 保存已经解析过的自定义类型名称,防止出现递归嵌套的情况 + */ + private Set recursiveCache = Sets.newHashSet(); - protected Map processFields(PsiClass psiClass) { - Map result = Maps.newLinkedHashMap(); - // 当前类所有字段 - List allPsiFields = PsiUtils.getAllPsiFields(psiClass); - if (CollectionUtils.isEmpty(allPsiFields)) { - return result; + protected Map processFields() { + return processFields(psiClass); } - for (PsiField psiField : allPsiFields) { - result.put(psiField.getName(), processField(psiField.getType())); + + protected Map processFields(PsiClass psiClass) { + Map result = Maps.newLinkedHashMap(); + // 当前类所有字段 + List allPsiFields = PsiUtils.getAllPsiFields(psiClass); + if (CollectionUtils.isEmpty(allPsiFields)) { + return result; + } + for (PsiField psiField : allPsiFields) { + // 过滤掉静态字段 + if (!psiField.hasModifierProperty(com.intellij.psi.PsiModifier.STATIC)) { + result.put(psiField.getName(), processField(psiField.getType())); + } + } + return result; } - return result; - } - private Object processField(PsiType psiType) { - Object defaultValue = null; - if (PsiTypeUtils.isDataType(psiType)) { - //如果是数据类型 - defaultValue = PsiTypeUtils.getDataTypeDefaultValue(psiType.getCanonicalText()); - } else if (PsiTypeUtils.isArrayType(psiType)) { - //如果是数组类型 - List list = Lists.newArrayList(); - PsiClass arrayContentClass = PsiUtils.getPsiClass(((PsiArrayType) psiType).getComponentType(), - psiClass.getProject()); - list.add(PsiTypeUtils.getDataTypeDefaultValue(arrayContentClass.getQualifiedName())); - defaultValue = list; - } else if (PsiTypeUtils.isCollectionType(psiType)) { - defaultValue = Lists.newArrayList(); - //如果是集合类型 - PsiType[] parameters = ((PsiClassReferenceType) psiType).getParameters(); - if (ArrayUtils.isEmpty(parameters)) { + private Object processField(PsiType psiType) { + Object defaultValue = null; + if (PsiTypeUtils.isDataType(psiType)) { + //如果是数据类型 + defaultValue = PsiTypeUtils.getDataTypeDefaultValue(psiType.getCanonicalText()); + } else if (PsiTypeUtils.isArrayType(psiType)) { + //如果是数组类型 + List list = Lists.newArrayList(); + PsiClass arrayContentClass = PsiUtils.getPsiClass(((PsiArrayType) psiType).getComponentType(), + psiClass.getProject()); + if (arrayContentClass == null) { + return list; + } + list.add(PsiTypeUtils.getDataTypeDefaultValue(arrayContentClass.getQualifiedName())); + defaultValue = list; + } else if (PsiTypeUtils.isCollectionType(psiType)) { + defaultValue = Lists.newArrayList(); + //如果是集合类型 + PsiType[] parameters = ((PsiClassReferenceType) psiType).getParameters(); + if (ArrayUtils.isEmpty(parameters)) { + return defaultValue; + } + //获取泛型 + PsiType genericType = parameters[0]; + ((List) defaultValue).add(processField(genericType)); + } else if (PsiTypeUtils.isMapType(psiType)) { + // 如果是 Map 类型 + Map map = Maps.newHashMap(); + PsiType[] parameters = ((PsiClassReferenceType) psiType).getParameters(); + if (ArrayUtils.isNotEmpty(parameters) && parameters.length >= 2) { + // 获取键和值的泛型类型 + PsiType keyType = parameters[0]; + PsiType valueType = parameters[1]; + Object keyDefaultValue; + // 判断 key 类型是否为 String + if (JavaClassName.STRING_TYPE.equals(keyType.getCanonicalText())) { + keyDefaultValue = "key"; + } else { + keyDefaultValue = processField(keyType); + } + Object valueDefaultValue = processField(valueType); + map.put(keyDefaultValue, valueDefaultValue); + } + defaultValue = map; + } else if (!PsiTypeUtils.isJavaOfficialType(psiType)) { + //如果是自定义类型 + if (recursiveCache.contains(psiType.getCanonicalText())) { + //出现递归嵌套 + return null; + } + recursiveCache.add(psiType.getCanonicalText()); + //如果是自定义类 + defaultValue = processFields(PsiUtils.getPsiClass(psiType, psiClass.getProject())); + } return defaultValue; - } - //获取泛型 - PsiType genericType = parameters[0]; - ((List) defaultValue).add(processField(genericType)); - } else if (!PsiTypeUtils.isJavaOfficialType(psiType)) { - //如果是自定义类型 - if (recursiveCache.contains(psiType.getCanonicalText())) { - //出现递归嵌套 - return null; - } - recursiveCache.add(psiType.getCanonicalText()); - //如果是自定义类 - defaultValue = processFields(PsiUtils.getPsiClass(psiType, psiClass.getProject())); } - return defaultValue; - } } diff --git a/src/main/java/cn/bigcoder/plugin/objecthelper/ui/ClassSearchDialog.java b/src/main/java/cn/bigcoder/plugin/objecthelper/ui/ClassSearchDialog.java index 7e49766..94e978a 100644 --- a/src/main/java/cn/bigcoder/plugin/objecthelper/ui/ClassSearchDialog.java +++ b/src/main/java/cn/bigcoder/plugin/objecthelper/ui/ClassSearchDialog.java @@ -10,24 +10,24 @@ import com.intellij.ui.components.JBScrollPane; import com.intellij.ui.components.JBTextField; import com.intellij.util.ArrayUtil; -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Component; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Timer; import java.util.TimerTask; -import javax.swing.DefaultListCellRenderer; -import javax.swing.JComponent; -import javax.swing.JList; -import javax.swing.JPanel; -import javax.swing.SwingUtilities; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; -import org.jetbrains.annotations.Nullable; +import javax.swing.SwingUtilities; +import javax.swing.SwingWorker; public class ClassSearchDialog extends DialogWrapper { @@ -36,11 +36,13 @@ public class ClassSearchDialog extends DialogWrapper { private JBList classList; private PsiClass selectedClass; private Timer timer; - private static final int DEBOUNCE_DELAY = 500; // 防抖延迟时间,单位:毫秒 + private static final int DEBOUNCE_DELAY = 800; // 防抖延迟时间,单位:毫秒 // 定义默认宽度,可按需调整 private static final int DEFAULT_WIDTH = 700; // 定义默认高度,可按需调整 private static final int DEFAULT_HEIGHT = 600; + private JLabel loadingLabel; + private Map> searchCache = new HashMap<>(); public ClassSearchDialog(@Nullable Project project, String title) { super(project); @@ -57,7 +59,6 @@ protected void init() { SwingUtilities.invokeLater(() -> classNameField.requestFocusInWindow()); } - @Nullable @Override protected JComponent createCenterPanel() { @@ -71,9 +72,17 @@ protected JComponent createCenterPanel() { classList = new JBList<>(); classList.setCellRenderer(new ClassListCellRenderer()); JBScrollPane scrollPane = new JBScrollPane(classList); - panel.add(scrollPane, BorderLayout.CENTER); - // 输入框文本变化监听,添加防抖机制 + // 创建加载提示标签 + loadingLabel = new JLabel("Loading...", SwingConstants.CENTER); + loadingLabel.setVisible(false); // 初始状态隐藏 + + JPanel listPanel = new JPanel(new BorderLayout()); + listPanel.add(scrollPane, BorderLayout.CENTER); + listPanel.add(loadingLabel, BorderLayout.SOUTH); + + panel.add(listPanel, BorderLayout.CENTER); + classNameField.getDocument().addDocumentListener(new DocumentListener() { @Override public void insertUpdate(DocumentEvent e) { @@ -141,7 +150,6 @@ public void keyTyped(KeyEvent e) { return panel; } - private void debounceUpdateClassList() { if (timer != null) { timer.cancel(); @@ -150,11 +158,55 @@ private void debounceUpdateClassList() { timer.schedule(new TimerTask() { @Override public void run() { - SwingUtilities.invokeLater(() -> updateClassList()); + // 显示加载提示 + SwingUtilities.invokeLater(() -> loadingLabel.setVisible(true)); + // 异步执行搜索操作 + SwingWorker, Void> worker = new SwingWorker<>() { + @Override + protected List doInBackground() { + return updateClassListInBackground(); + } + + @Override + protected void done() { + try { + List matchedClasses = get(); + SwingUtilities.invokeLater(() -> { + updateClassListUI(matchedClasses); + // 隐藏加载提示 + loadingLabel.setVisible(false); + }); + } catch (Exception e) { + e.printStackTrace(); + // 出现异常也隐藏加载提示 + SwingUtilities.invokeLater(() -> loadingLabel.setVisible(false)); + } + } + }; + worker.execute(); } }, DEBOUNCE_DELAY); } + private List updateClassListInBackground() { + String query = classNameField.getText().trim(); + if (!query.isEmpty()) { + if (searchCache.containsKey(query)) { + return searchCache.get(query); + } + List matchedClasses = PsiUtils.matchPsiClassByName(this.project, query); + // 按优先级排序 + matchedClasses.sort(this::compareClasses); + searchCache.put(query, matchedClasses); + return matchedClasses; + } + return Collections.emptyList(); + } + + private void updateClassListUI(List matchedClasses) { + classList.setListData(ArrayUtil.toObjectArray(matchedClasses, PsiClass.class)); + } + private void updateClassList() { String query = classNameField.getText().trim(); if (!query.isEmpty()) { @@ -273,5 +325,4 @@ private String highlightMatches(String input, String query) { return result.toString(); } } - } \ No newline at end of file diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 21b6ba4..9bdb6dd 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -6,8 +6,10 @@ GitHub -
  • Copy the object
  • +
  • Object Copy Method
  • +
  • Object Copy
  • +
  • Class to JSON
  • Class to Thrift IDL