├── README.md ├── valuegenerator ├── ByteGenerator.java ├── BooleanGenerator.java ├── StringGenerator.java ├── IntegerGenerator.java ├── LongGenerator.java ├── CharacterGenerator.java ├── DateGenerator.java ├── FloatGenerator.java └── DoubleGenerator.java ├── common ├── MutiValuesWithClass.java ├── MockitoConstants.java └── CodeUtils.java ├── model └── ParentTree.java ├── file ├── FileUtils ├── FileAppendTask.java ├── FileCreateTask.java ├── CodeGenerator.java ├── JsonFileGenerator.java └── MyMethod.java ├── toolwindow ├── MethodSelectDialog.java └── CodeGeneratorDialog.java ├── ClassCodeGenerateAction.java ├── CodeGroupTest.java ├── ModelToJsonAction.java ├── MethodCodeGenerateAction.java ├── ValueContext.java └── LICENSE /README.md: -------------------------------------------------------------------------------- 1 | # 单元测试自动生成插件 2 | 3 | 这个人很懒 4 | -------------------------------------------------------------------------------- /valuegenerator/ByteGenerator.java: -------------------------------------------------------------------------------- 1 | package com.cq.valuegenerator.impl; 2 | 3 | import com.cq.valuegenerator.AbstractJsonValueService; 4 | 5 | /** 6 | * @author 有尘 7 | * @date 2021/9/29 8 | */ 9 | public class ByteGenerator extends AbstractJsonValueService { 10 | 11 | @Override 12 | public Byte defaultValue() { 13 | return 1; 14 | } 15 | 16 | @Override 17 | public Byte randomValue() { 18 | return 0; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /valuegenerator/BooleanGenerator.java: -------------------------------------------------------------------------------- 1 | package com.cq.valuegenerator.impl; 2 | 3 | import com.cq.valuegenerator.AbstractJsonValueService; 4 | 5 | /** 6 | * @author 有尘 7 | * @date 2021/9/29 8 | */ 9 | public class BooleanGenerator extends AbstractJsonValueService { 10 | 11 | @Override 12 | public Boolean defaultValue() { 13 | return true; 14 | } 15 | 16 | @Override 17 | public Boolean randomValue() { 18 | return false; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /valuegenerator/StringGenerator.java: -------------------------------------------------------------------------------- 1 | package com.cq.valuegenerator.impl; 2 | 3 | import com.cq.valuegenerator.AbstractJsonValueService; 4 | 5 | /** 6 | * @author 有尘 7 | * @date 2021/9/29 8 | */ 9 | public class StringGenerator extends AbstractJsonValueService { 10 | 11 | @Override 12 | public String defaultValue() { 13 | return "test"; 14 | } 15 | 16 | @Override 17 | public String randomValue() { 18 | return valueContext.getFaker().name().username(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /valuegenerator/IntegerGenerator.java: -------------------------------------------------------------------------------- 1 | package com.cq.valuegenerator.impl; 2 | 3 | import com.cq.valuegenerator.AbstractJsonValueService; 4 | 5 | /** 6 | * @author 有尘 7 | * @date 2021/9/29 8 | */ 9 | public class IntegerGenerator extends AbstractJsonValueService { 10 | 11 | @Override 12 | public Integer defaultValue() { 13 | return 1; 14 | } 15 | 16 | @Override 17 | public Integer randomValue() { 18 | return valueContext.getFaker().number().randomDigit(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /valuegenerator/LongGenerator.java: -------------------------------------------------------------------------------- 1 | package com.cq.valuegenerator.impl; 2 | 3 | import com.cq.valuegenerator.AbstractJsonValueService; 4 | 5 | /** 6 | * @author 有尘 7 | * @date 2021/9/29 8 | */ 9 | public class LongGenerator extends AbstractJsonValueService { 10 | 11 | @Override 12 | public Long defaultValue() { 13 | return 1L; 14 | } 15 | 16 | @Override 17 | public Long randomValue() { 18 | return Long.valueOf(valueContext.getFaker().number().randomNumber()); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /valuegenerator/CharacterGenerator.java: -------------------------------------------------------------------------------- 1 | package com.cq.valuegenerator.impl; 2 | 3 | import com.cq.valuegenerator.AbstractJsonValueService; 4 | 5 | /** 6 | * @author 有尘 7 | * @date 2021/9/29 8 | */ 9 | public class CharacterGenerator extends AbstractJsonValueService { 10 | 11 | @Override 12 | public Character defaultValue() { 13 | return 'a'; 14 | } 15 | 16 | @Override 17 | public Character randomValue() { 18 | return valueContext.getFaker().lorem().character(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /valuegenerator/DateGenerator.java: -------------------------------------------------------------------------------- 1 | package com.cq.valuegenerator.impl; 2 | 3 | import java.util.Date; 4 | 5 | import com.cq.valuegenerator.AbstractJsonValueService; 6 | 7 | /** 8 | * @author 有尘 9 | * @date 2021/9/29 10 | */ 11 | public class DateGenerator extends AbstractJsonValueService { 12 | 13 | @Override 14 | public Date defaultValue() { 15 | return new Date(); 16 | } 17 | 18 | @Override 19 | public Date randomValue() { 20 | return valueContext.getFaker().date().birthday(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /valuegenerator/FloatGenerator.java: -------------------------------------------------------------------------------- 1 | package com.cq.valuegenerator.impl; 2 | 3 | import com.cq.valuegenerator.AbstractJsonValueService; 4 | 5 | /** 6 | * @author 有尘 7 | * @date 2021/9/29 8 | */ 9 | public class FloatGenerator extends AbstractJsonValueService { 10 | 11 | @Override 12 | public Float defaultValue() { 13 | return Float.valueOf(1.0f); 14 | } 15 | 16 | @Override 17 | public Float randomValue() { 18 | return Float.valueOf(valueContext.getFaker().number().randomNumber(3, false)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /valuegenerator/DoubleGenerator.java: -------------------------------------------------------------------------------- 1 | package com.cq.valuegenerator.impl; 2 | 3 | import com.cq.valuegenerator.AbstractJsonValueService; 4 | 5 | /** 6 | * @author 有尘 7 | * @date 2021/9/29 8 | */ 9 | public class DoubleGenerator extends AbstractJsonValueService { 10 | 11 | @Override 12 | public Double defaultValue() { 13 | return Double.valueOf(1.0f); 14 | } 15 | 16 | @Override 17 | public Double randomValue() { 18 | return Double.valueOf(valueContext.getFaker().number().randomNumber(3, false)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /common/MutiValuesWithClass.java: -------------------------------------------------------------------------------- 1 | package com.cq.common; 2 | 3 | import java.util.HashSet; 4 | import java.util.Set; 5 | 6 | /** 7 | * @author 有尘 8 | * @date 2021/9/30 9 | */ 10 | public class MutiValuesWithClass { 11 | private Object object; 12 | private Set names; 13 | 14 | public MutiValuesWithClass(Object object) { 15 | this.object = object; 16 | this.names = new HashSet<>(); 17 | } 18 | 19 | public void addNames(String name) { 20 | names.add(name); 21 | } 22 | 23 | public Object getObject() { 24 | return object; 25 | } 26 | 27 | public void setObject(Object object) { 28 | this.object = object; 29 | } 30 | 31 | public Set getNames() { 32 | return names; 33 | } 34 | 35 | public void setNames(Set names) { 36 | this.names = names; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /model/ParentTree.java: -------------------------------------------------------------------------------- 1 | package com.cq.model; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.Objects; 6 | 7 | import lombok.Getter; 8 | import lombok.Setter; 9 | 10 | /** 11 | * 为了解决属性循环的问题,比如Tree结构,不会生成过深的结构 12 | * @author 有尘 13 | * @date 2021/11/24 14 | */ 15 | @Getter 16 | @Setter 17 | public class ParentTree { 18 | private ParentTree parent; 19 | private Map sonList; 20 | private String value; 21 | 22 | @Override 23 | public boolean equals(Object o) { 24 | if (this == o) { return true; } 25 | if (o == null || getClass() != o.getClass()) { return false; } 26 | ParentTree that = (ParentTree)o; 27 | return value.equals(that.value); 28 | } 29 | 30 | @Override 31 | public int hashCode() { 32 | return Objects.hash(value); 33 | } 34 | 35 | public ParentTree(String value) { 36 | this.value = value; 37 | this.sonList=new HashMap<>(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /file/FileUtils: -------------------------------------------------------------------------------- 1 | package com.cq.file; 2 | 3 | import com.intellij.psi.PsiClass; 4 | import com.intellij.psi.PsiFile; 5 | 6 | /** 7 | * @author 有尘 8 | * @date 2021/9/28 9 | */ 10 | public class FileUtils { 11 | public static String getUnitFilePath(PsiFile psiFile) { 12 | String classPath = psiFile.getParent().getVirtualFile().getPath(); 13 | return classPath.replace("/src/main/java", "/src/test/java"); 14 | } 15 | 16 | public static String genJavaFileName(PsiClass psiClass) { 17 | String name = psiClass.getName(); 18 | return name + "Test.java"; 19 | } 20 | 21 | public static String getJsonFilePath(PsiFile psiFile) { 22 | 23 | String classPath = psiFile.getVirtualFile().getPath(); 24 | classPath = classPath.replace("/src/main/java", "/src/test/java"); 25 | classPath = classPath.replace(".java", ""); 26 | return classPath.replace("java", "resources"); 27 | } 28 | 29 | public static String getJsonFileName(String name) { 30 | return name + ".json"; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /file/FileAppendTask.java: -------------------------------------------------------------------------------- 1 | package com.cq.file; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.nio.file.Files; 6 | import java.util.List; 7 | import java.util.regex.Pattern; 8 | 9 | import com.cq.valuegenerator.ValueContext; 10 | import com.intellij.openapi.fileEditor.OpenFileDescriptor; 11 | import com.intellij.openapi.vfs.LocalFileSystem; 12 | import com.intellij.openapi.vfs.VirtualFile; 13 | 14 | /** 15 | * @author 有尘 16 | * @date 2022/1/12 17 | */ 18 | public class FileAppendTask implements Runnable { 19 | private final File file; 20 | private final String text; 21 | 22 | public FileAppendTask(File file, String text) { 23 | this.file = file; 24 | this.text = text; 25 | } 26 | 27 | @Override 28 | public void run() { 29 | 30 | try { 31 | StringBuilder sb = new StringBuilder(); 32 | List strings = Files.readAllLines(ValueContext.getPath()); 33 | for (int i = 0; i < strings.size(); i++) { 34 | if (Pattern.matches("^}", strings.get(i))) { 35 | sb.append(text); 36 | sb.append("\n"); 37 | sb.append("}"); 38 | break; 39 | } else { 40 | sb.append(strings.get(i)); 41 | sb.append("\n"); 42 | } 43 | } 44 | Files.write(ValueContext.getPath(), sb.toString().getBytes()); 45 | VirtualFile virtualFile = LocalFileSystem.getInstance().refreshAndFindFileByPath( 46 | ValueContext.getPath().toString()); 47 | new OpenFileDescriptor(ValueContext.getEvent().getProject(), virtualFile).navigate(true); 48 | 49 | } catch (IOException e) { 50 | e.printStackTrace(); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /toolwindow/MethodSelectDialog.java: -------------------------------------------------------------------------------- 1 | package com.cq.toolwindow; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | import java.util.stream.Collectors; 6 | 7 | import javax.swing.*; 8 | 9 | import com.intellij.ide.util.DefaultPsiElementCellRenderer; 10 | import com.intellij.openapi.ui.DialogWrapper; 11 | import com.intellij.openapi.ui.LabeledComponent; 12 | import com.intellij.psi.PsiClass; 13 | import com.intellij.psi.PsiMethod; 14 | import com.intellij.ui.CollectionListModel; 15 | import com.intellij.ui.ToolbarDecorator; 16 | import com.intellij.ui.components.JBList; 17 | 18 | /** 19 | * @author chenqiong 20 | * @date 2022/1/12 21 | */ 22 | public class MethodSelectDialog extends DialogWrapper { 23 | private final CollectionListModel methodList; 24 | private final LabeledComponent pannel; 25 | 26 | public MethodSelectDialog(PsiClass psiClass) { 27 | super(psiClass.getProject()); 28 | this.setTitle("配置测试用例"); 29 | List collect = Arrays.asList(psiClass.getMethods()).stream().filter( 30 | (a) -> !a.getModifierList().hasModifierProperty("private")).collect(Collectors.toList()); 31 | this.methodList = new CollectionListModel(collect); 32 | JBList fieldList = new JBList(this.methodList); 33 | fieldList.setCellRenderer(new DefaultPsiElementCellRenderer()); 34 | ToolbarDecorator decorator = ToolbarDecorator.createDecorator(fieldList); 35 | JPanel panel = decorator.createPanel(); 36 | this.pannel = LabeledComponent.create(panel, "选择要测试的方法"); 37 | this.init(); 38 | } 39 | 40 | @Override 41 | protected JComponent createCenterPanel() { 42 | return this.pannel; 43 | } 44 | 45 | public List getMethods() { 46 | return this.methodList.getItems(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /file/FileCreateTask.java: -------------------------------------------------------------------------------- 1 | package com.cq.file; 2 | 3 | import java.io.IOException; 4 | import java.nio.file.Files; 5 | import java.nio.file.Path; 6 | import java.nio.file.Paths; 7 | 8 | import com.cq.valuegenerator.ValueContext; 9 | import com.intellij.openapi.fileEditor.OpenFileDescriptor; 10 | import com.intellij.openapi.project.Project; 11 | import com.intellij.openapi.vfs.LocalFileSystem; 12 | import com.intellij.openapi.vfs.VirtualFile; 13 | import com.intellij.psi.PsiFile; 14 | import com.intellij.psi.PsiManager; 15 | import com.intellij.psi.codeStyle.CodeStyleManager; 16 | import org.apache.commons.lang3.StringUtils; 17 | 18 | /** 19 | * @author chenqiong 20 | * @date 2021/9/27 21 | */ 22 | public class FileCreateTask implements Runnable { 23 | private final String path; 24 | private final String name; 25 | private final String content; 26 | 27 | public FileCreateTask(String path, String name, String content) { 28 | this.path = path; 29 | this.name = name; 30 | this.content = content; 31 | } 32 | 33 | @Override 34 | public void run() { 35 | Path path = Paths.get(this.path); 36 | try { 37 | if (StringUtils.isEmpty(content)) { 38 | return; 39 | } 40 | if (!Files.exists(path)) { 41 | Files.createDirectories(path); 42 | } 43 | Files.write(Paths.get(this.path, name), content.getBytes()); 44 | 45 | 46 | if (name.endsWith(".java")&&!name.contains("TestUtil")) { 47 | Project project = ValueContext.getEvent().getProject(); 48 | VirtualFile virtualFile = LocalFileSystem.getInstance().refreshAndFindFileByPath( 49 | Paths.get(this.path, name).toString()); 50 | new OpenFileDescriptor(project, virtualFile).navigate(true); 51 | PsiFile file = PsiManager.getInstance(ValueContext.getEvent().getProject()).findFile(virtualFile); 52 | CodeStyleManager.getInstance(ValueContext.event.getProject()).reformat(file); 53 | } 54 | 55 | } catch (IOException e) { 56 | e.printStackTrace(); 57 | } 58 | } 59 | 60 | public String getPath() { 61 | return path; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /common/MockitoConstants.java: -------------------------------------------------------------------------------- 1 | package com.cq.common; 2 | 3 | import java.util.HashSet; 4 | import java.util.Set; 5 | 6 | /** 7 | * @author 有尘 8 | * @date 2021/9/28 9 | */ 10 | public class MockitoConstants { 11 | public static String COMMON_IMPORT = "import lombok.extern.slf4j.Slf4j;\n" 12 | + "import com.alibaba.fastjson.TypeReference;\n" 13 | + "import com.alibaba.fastjson.JSONObject;\n" 14 | + "import org.junit.jupiter.api.BeforeEach;\n" 15 | + "import org.mockito.junit.jupiter.MockitoSettings;\n" 16 | + "import org.mockito.quality.Strictness;\n" 17 | + "import org.junit.jupiter.api.DisplayName;\n" 18 | + "import org.junit.jupiter.api.extension.ExtendWith;\n" 19 | + "import org.junit.jupiter.params.ParameterizedTest;\n" 20 | + "import org.mockito.InjectMocks;\n" 21 | + "import org.mockito.Matchers;\n" 22 | + "import org.mockito.Mock;\n" 23 | + "import org.mockito.MockitoAnnotations;\n" 24 | + "import org.mockito.junit.jupiter.MockitoExtension;\n" 25 | + "\n" 26 | + "import java.util.*;\n\n" 27 | + "import static org.junit.jupiter.api.Assertions.assertEquals;\n" 28 | + "import static org.mockito.ArgumentMatchers.any;\n" 29 | + "import static org.mockito.Mockito.doReturn;\n" 30 | + "import static org.mockito.Mockito.when;\n\n"; 31 | 32 | public static String COMMON_ANNOTATION = "@ExtendWith(MockitoExtension.class)\n" 33 | + "@MockitoSettings(strictness = Strictness.LENIENT)\n"; 34 | 35 | public static String BEFORE_SETUP = "\t@BeforeEach\n" 36 | + "\tpublic void setUp() throws Exception {\n" 37 | + "\t\tMockitoAnnotations.initMocks(this);\n" 38 | + "\t}\n"; 39 | public static Set BASE_TYPE_LIST = new HashSet() {{ 40 | add("int"); 41 | add("char"); 42 | add("double"); 43 | add("lang"); 44 | add("byte"); 45 | add("short"); 46 | add("float"); 47 | add("boolean"); 48 | }}; 49 | 50 | public static String TEST_UTILS_CLASS = "package com.util;\n" 51 | + "\n" 52 | + "import java.io.BufferedReader;\n" 53 | + "import java.io.InputStreamReader;\n" 54 | + "\n" 55 | + "import com.alibaba.fastjson.JSONObject;\n" 56 | + "import com.alibaba.fastjson.JSONReader;\n" 57 | + "\n" 58 | + "/**\n" 59 | + " * @author 有尘\n" 60 | + " * @date 2021/11/29\n" 61 | + " */\n" 62 | + "public class TestUtils {\n" 63 | + " public static JSONObject getTestArg(String path) {\n" 64 | + " String s = new JSONReader(\n" 65 | + " new BufferedReader(new InputStreamReader(TestUtils.class.getResourceAsStream(path))))\n" 66 | + " .readString();\n" 67 | + " return JSONObject.parseObject(s);\n" 68 | + " }\n" 69 | + "}"; 70 | } 71 | -------------------------------------------------------------------------------- /ClassCodeGenerateAction.java: -------------------------------------------------------------------------------- 1 | package com.cq; 2 | 3 | import java.nio.file.Files; 4 | 5 | import com.cq.file.CodeGenerator; 6 | import com.cq.file.FileCreateTask; 7 | import com.cq.toolwindow.CodeGeneratorDialog; 8 | import com.cq.valuegenerator.ValueContext; 9 | import com.intellij.ide.highlighter.JavaClassFileType; 10 | import com.intellij.ide.highlighter.JavaFileType; 11 | import com.intellij.openapi.actionSystem.AnAction; 12 | import com.intellij.openapi.actionSystem.AnActionEvent; 13 | import com.intellij.openapi.actionSystem.CommonDataKeys; 14 | import com.intellij.openapi.application.ApplicationManager; 15 | import com.intellij.openapi.fileEditor.OpenFileDescriptor; 16 | import com.intellij.openapi.fileTypes.FileType; 17 | import com.intellij.openapi.project.Project; 18 | import com.intellij.openapi.vfs.LocalFileSystem; 19 | import com.intellij.psi.PsiFile; 20 | import lombok.extern.slf4j.Slf4j; 21 | import org.jetbrains.annotations.NotNull; 22 | 23 | /** 24 | * 单元测试生成的action 25 | * 26 | * @author chenqiong 27 | */ 28 | @Slf4j 29 | public class ClassCodeGenerateAction extends AnAction { 30 | 31 | public ClassCodeGenerateAction() { 32 | getTemplatePresentation().setText("生成单元测试类"); 33 | } 34 | 35 | @Override 36 | public void actionPerformed(@NotNull AnActionEvent e) { 37 | Project project = e.getProject(); 38 | 39 | ValueContext.setEvent(e); 40 | if (Files.exists(ValueContext.getPath())) { 41 | System.out.println("文件已经存在需要覆盖吗?"); 42 | new OpenFileDescriptor(project, 43 | LocalFileSystem.getInstance().refreshAndFindFileByPath(ValueContext.getPath().toString())) 44 | .navigate(true); 45 | return; 46 | } 47 | 48 | CodeGeneratorDialog radioSelection = new CodeGeneratorDialog(ValueContext.getPsiClass()); 49 | ValueContext.getContext().loadClass(ValueContext.getPsiClass()); 50 | radioSelection.show(); 51 | if (radioSelection.isOK()) { 52 | String source = radioSelection.getData(); 53 | ValueContext.setIsJsonFileSource(source.equals("JsonFileSource")); 54 | // 生成java文件 55 | CodeGenerator codeGenerator = new CodeGenerator(radioSelection.getFields(), radioSelection.getMethods()); 56 | ApplicationManager.getApplication().runWriteAction( 57 | new FileCreateTask(ValueContext.getFilePath(), ValueContext.getFileName(), codeGenerator.genContent())); 58 | 59 | } 60 | 61 | } 62 | /** 63 | * 只有java文件才能使用该功能 64 | * 65 | * @see com.intellij.openapi.actionSystem.AnAction#update(com.intellij.openapi.actionSystem.AnActionEvent) 66 | */ 67 | @Override 68 | public void update(@NotNull AnActionEvent e) { 69 | super.update(e); 70 | PsiFile psiFile = e.getData(CommonDataKeys.PSI_FILE); 71 | FileType fileType = psiFile.getFileType(); 72 | if (!((fileType instanceof JavaFileType) || (fileType instanceof JavaClassFileType))) { 73 | e.getPresentation().setEnabled(false); 74 | return; 75 | } 76 | 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /CodeGroupTest.java: -------------------------------------------------------------------------------- 1 | package com.cq; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import com.intellij.ide.highlighter.JavaFileType; 7 | import com.intellij.openapi.actionSystem.ActionGroup; 8 | import com.intellij.openapi.actionSystem.ActionManager; 9 | import com.intellij.openapi.actionSystem.AnAction; 10 | import com.intellij.openapi.actionSystem.AnActionEvent; 11 | import com.intellij.openapi.actionSystem.CommonDataKeys; 12 | import com.intellij.openapi.actionSystem.PlatformDataKeys; 13 | import com.intellij.openapi.fileTypes.FileType; 14 | import com.intellij.openapi.project.Project; 15 | import com.intellij.psi.PsiFile; 16 | import org.jetbrains.annotations.NotNull; 17 | 18 | /** 19 | * @author 有尘 20 | * @date 2021/9/27 21 | */ 22 | public class CodeGroupTest extends ActionGroup { 23 | 24 | /** 25 | * @see com.intellij.openapi.actionSystem.ActionGroup#getChildren(com.intellij.openapi.actionSystem.AnActionEvent) 26 | */ 27 | @NotNull 28 | @Override 29 | public AnAction[] getChildren(AnActionEvent anActionEvent) { 30 | if (anActionEvent == null) { 31 | return AnAction.EMPTY_ARRAY; 32 | } 33 | Project project = PlatformDataKeys.PROJECT.getData(anActionEvent.getDataContext()); 34 | if (project == null) { 35 | return AnAction.EMPTY_ARRAY; 36 | } 37 | final List children = new ArrayList<>(); 38 | AnAction classCodeGenerateAction = getAction("ClassCodeGenerateAction", ClassCodeGenerateAction.class); 39 | AnAction modelToJsonAction = getAction("ModelToJsonAction", ModelToJsonAction.class); 40 | AnAction methodCodeGenerateAction =getAction("MethodCodeGenerateAction", MethodCodeGenerateAction.class); 41 | children.add(classCodeGenerateAction); 42 | children.add(modelToJsonAction); 43 | children.add(methodCodeGenerateAction); 44 | return children.toArray(new AnAction[children.size()]); 45 | } 46 | 47 | /** 48 | * 获取或新增一个子元素 49 | * 50 | * @return 51 | */ 52 | private AnAction getAction(String name, Class t) { 53 | final String actionId = "JCode5.Menu.Action." + name; 54 | AnAction action = ActionManager.getInstance().getAction(actionId); 55 | if (action == null) { 56 | try { 57 | action = t.newInstance(); 58 | } catch (Exception e) { 59 | throw new RuntimeException("系统错误"); 60 | } 61 | ActionManager.getInstance().registerAction(actionId, action); 62 | } 63 | return action; 64 | } 65 | 66 | /** 67 | * 只有java文件才能使用该功能 68 | * 69 | * @see com.intellij.openapi.actionSystem.AnAction#update(com.intellij.openapi.actionSystem.AnActionEvent) 70 | */ 71 | @Override 72 | public void update(@NotNull AnActionEvent e) { 73 | super.update(e); 74 | PsiFile psiFile = e.getData(CommonDataKeys.PSI_FILE); 75 | FileType fileType = psiFile.getFileType(); 76 | if (!(fileType instanceof JavaFileType)) { 77 | e.getPresentation().setEnabled(false); 78 | return; 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /ModelToJsonAction.java: -------------------------------------------------------------------------------- 1 | package com.cq; 2 | 3 | import java.awt.*; 4 | import java.awt.datatransfer.Clipboard; 5 | import java.awt.datatransfer.StringSelection; 6 | import java.util.Arrays; 7 | import java.util.Map; 8 | import java.util.stream.Collectors; 9 | 10 | import com.alibaba.fastjson.JSONObject; 11 | 12 | import com.cq.file.JsonFileGenerator; 13 | import com.intellij.ide.highlighter.JavaClassFileType; 14 | import com.intellij.ide.highlighter.JavaFileType; 15 | import com.intellij.notification.Notification; 16 | import com.intellij.notification.NotificationDisplayType; 17 | import com.intellij.notification.NotificationGroup; 18 | import com.intellij.notification.NotificationType; 19 | import com.intellij.notification.Notifications; 20 | import com.intellij.openapi.actionSystem.AnAction; 21 | import com.intellij.openapi.actionSystem.AnActionEvent; 22 | import com.intellij.openapi.actionSystem.CommonDataKeys; 23 | import com.intellij.openapi.fileTypes.FileType; 24 | import com.intellij.psi.PsiClass; 25 | import com.intellij.psi.PsiFile; 26 | import com.intellij.psi.util.PsiUtil; 27 | import lombok.extern.slf4j.Slf4j; 28 | import org.jetbrains.annotations.NotNull; 29 | 30 | import static com.intellij.openapi.actionSystem.CommonDataKeys.PSI_ELEMENT; 31 | 32 | /** 33 | * @author cq 34 | */ 35 | @Slf4j 36 | public class ModelToJsonAction extends AnAction { 37 | public static final NotificationGroup notificationGroup = new NotificationGroup("junitCode", 38 | NotificationDisplayType.BALLOON, true); 39 | 40 | ModelToJsonAction(){ 41 | getTemplatePresentation().setText("生成json数据"); 42 | } 43 | 44 | /** 45 | * @see com.intellij.openapi.actionSystem.AnAction#actionPerformed(com.intellij.openapi.actionSystem.AnActionEvent) 46 | */ 47 | @Override 48 | public void actionPerformed(AnActionEvent e) { 49 | PsiClass topLevelClass = PsiUtil.getTopLevelClass(e.getData(PSI_ELEMENT)); 50 | Map fieldMap = Arrays.stream(topLevelClass.getAllFields()).collect( 51 | Collectors.toMap(field -> field.getName(), field -> JsonFileGenerator.typeResolve(field.getType(), 0),(a,b)->b)); 52 | String s = JSONObject.toJSONString(fieldMap, true); 53 | Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); 54 | StringSelection selection = new StringSelection(s); 55 | clipboard.setContents(selection, selection); 56 | String message = topLevelClass.getName() + "类的json格式已复制到剪切板"; 57 | Notification success = notificationGroup.createNotification(message, NotificationType.INFORMATION); 58 | Notifications.Bus.notify(success, e.getProject()); 59 | } 60 | 61 | /** 62 | * @see com.intellij.openapi.actionSystem.AnAction#update(com.intellij.openapi.actionSystem.AnActionEvent) 63 | */ 64 | @Override 65 | public void update(@NotNull AnActionEvent e) { 66 | super.update(e); 67 | PsiFile psiFile = e.getData(CommonDataKeys.PSI_FILE); 68 | FileType fileType = psiFile.getFileType(); 69 | if (!((fileType instanceof JavaFileType)||(fileType instanceof JavaClassFileType))) { 70 | log.warn("setEnabled{}", false); 71 | e.getPresentation().setEnabled(false); 72 | return; 73 | } 74 | 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /toolwindow/CodeGeneratorDialog.java: -------------------------------------------------------------------------------- 1 | package com.cq.toolwindow; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | import java.util.stream.Collectors; 6 | 7 | import javax.swing.*; 8 | 9 | import com.cq.common.CodeUtils; 10 | import com.intellij.ide.util.DefaultPsiElementCellRenderer; 11 | import com.intellij.openapi.ui.DialogWrapper; 12 | import com.intellij.openapi.ui.LabeledComponent; 13 | import com.intellij.psi.PsiClass; 14 | import com.intellij.psi.PsiField; 15 | import com.intellij.psi.PsiMethod; 16 | import com.intellij.ui.CollectionListModel; 17 | import com.intellij.ui.ToolbarDecorator; 18 | import com.intellij.ui.components.JBList; 19 | import org.jetbrains.annotations.Nullable; 20 | 21 | /** 22 | * @author 有尘 23 | * @date 2021/11/29 24 | */ 25 | public class CodeGeneratorDialog extends DialogWrapper { 26 | private JPanel panel1; 27 | private JComboBox source; 28 | private JLabel testSourceLable; 29 | private JPanel fieldPanel; 30 | private JPanel methodPanel; 31 | private JLabel methodLabel; 32 | private JLabel fieldLabel; 33 | 34 | private CollectionListModel myMethods; 35 | private CollectionListModel myFields; 36 | private PsiClass psiClass; 37 | public CodeGeneratorDialog( PsiClass psiClass) { 38 | super(psiClass.getProject()); 39 | this.psiClass=psiClass; 40 | initMethodPanel(); 41 | initFieldPanel(); 42 | init(); 43 | } 44 | 45 | @Override 46 | protected @Nullable JComponent createCenterPanel() { 47 | return this.panel1; 48 | } 49 | public String getData(){ 50 | return source.getSelectedItem().toString(); 51 | } 52 | 53 | public List getFields() { 54 | return myFields.getItems(); 55 | } 56 | 57 | 58 | public List getMethods() { 59 | return myMethods.getItems(); 60 | } 61 | 62 | public void initMethodPanel(){ 63 | List collect = Arrays.asList(psiClass.getMethods()).stream().filter( 64 | a -> !a.getModifierList().hasModifierProperty("private")).collect( 65 | Collectors.toList()); 66 | myMethods = new CollectionListModel<>(collect); 67 | JBList jMethodList = new JBList(myMethods); 68 | jMethodList.setCellRenderer(new DefaultPsiElementCellRenderer()); 69 | ToolbarDecorator decorator = ToolbarDecorator.createDecorator(jMethodList); 70 | JPanel panel = decorator.createPanel(); 71 | LabeledComponent labeledComponent = LabeledComponent.create(panel, ""); 72 | methodPanel.add(labeledComponent); 73 | 74 | } 75 | 76 | public void initFieldPanel(){ 77 | 78 | List collect = Arrays.stream(psiClass.getAllFields()).filter( 79 | a -> !CodeUtils.isPrimitiveType(a.getType())).collect( 80 | Collectors.toList()); 81 | myFields = new CollectionListModel(collect); 82 | JList Jfields=new JBList(myFields); 83 | Jfields.setCellRenderer(new DefaultPsiElementCellRenderer()); 84 | ToolbarDecorator decorator = ToolbarDecorator.createDecorator(Jfields); 85 | JPanel panel = decorator.createPanel(); 86 | LabeledComponent tste = LabeledComponent.create(panel, ""); 87 | fieldPanel.add(tste); 88 | 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /MethodCodeGenerateAction.java: -------------------------------------------------------------------------------- 1 | package com.cq; 2 | 3 | import java.nio.file.Files; 4 | import java.util.List; 5 | 6 | import com.cq.file.CodeGenerator; 7 | import com.cq.file.FileAppendTask; 8 | import com.cq.toolwindow.MethodSelectDialog; 9 | import com.cq.valuegenerator.ValueContext; 10 | import com.intellij.ide.highlighter.JavaClassFileType; 11 | import com.intellij.ide.highlighter.JavaFileType; 12 | import com.intellij.openapi.actionSystem.AnAction; 13 | import com.intellij.openapi.actionSystem.AnActionEvent; 14 | import com.intellij.openapi.actionSystem.CommonDataKeys; 15 | import com.intellij.openapi.fileTypes.FileType; 16 | import com.intellij.openapi.project.Project; 17 | import com.intellij.openapi.ui.Messages; 18 | import com.intellij.psi.PsiClass; 19 | import com.intellij.psi.PsiFile; 20 | import com.intellij.psi.PsiMethod; 21 | import lombok.SneakyThrows; 22 | import lombok.extern.slf4j.Slf4j; 23 | import org.jetbrains.annotations.NotNull; 24 | 25 | /** 26 | * 单元测试生成的action 27 | * 28 | * @author youchen 29 | */ 30 | @Slf4j 31 | public class MethodCodeGenerateAction extends AnAction { 32 | 33 | public MethodCodeGenerateAction() { 34 | getTemplatePresentation().setText("增加测试方法"); 35 | } 36 | 37 | @SneakyThrows 38 | @Override 39 | public void actionPerformed(@NotNull AnActionEvent e) { 40 | Project project = e.getProject(); 41 | 42 | ValueContext.setEvent(e); 43 | if (!Files.exists(ValueContext.getPath())) { 44 | String message = ("测试类不存在,请先生成单元测试类"); 45 | Messages.showMessageDialog(project, message, "Generate Failed", null); 46 | return; 47 | } 48 | boolean valueSource = Files.readAllLines(ValueContext.getPath()).stream().anyMatch( 49 | a -> a.contains("ValueSource")); 50 | ValueContext.setIsJsonFileSource(!valueSource); 51 | PsiClass psiClass = ValueContext.getPsiClass(); 52 | MethodSelectDialog methodSelectDialog = new MethodSelectDialog(psiClass); 53 | ValueContext.getContext().loadClass(psiClass); 54 | 55 | methodSelectDialog.show(); 56 | if (methodSelectDialog.isOK()) { 57 | List methods = methodSelectDialog.getMethods(); 58 | // 生成方法 59 | CodeGenerator codeGenerator = new CodeGenerator(psiClass, methods, ValueContext.getPsiFile()); 60 | 61 | String s = codeGenerator.genMethodBody(); 62 | //VirtualFile virtualFile = LocalFileSystem.getInstance().findFileByIoFile( 63 | // ValueContext.getPath().toFile()); 64 | // 65 | //PsiJavaFile file = (PsiJavaFile)PsiManager.getInstance(ValueContext.getEvent().getProject()).findFile( 66 | // virtualFile); 67 | // 68 | //PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(file.getProject()); 69 | //String body="public void test(){}"; 70 | //PsiMethod methodFromText = elementFactory.createMethodFromText(s, file.getClasses()[0]); 71 | //psiClass.add(methodFromText); 72 | 73 | //ApplicationManager.getApplication().runWriteAction( 74 | new FileAppendTask(ValueContext.getPath().toFile(),s).run(); 75 | } 76 | } 77 | 78 | /** 79 | * 只有java文件才能使用该功能 80 | * 81 | * @see AnAction#update(AnActionEvent) 82 | */ 83 | @Override 84 | public void update(@NotNull AnActionEvent e) { 85 | super.update(e); 86 | PsiFile psiFile = e.getData(CommonDataKeys.PSI_FILE); 87 | FileType fileType = psiFile.getFileType(); 88 | if (!((fileType instanceof JavaFileType) || (fileType instanceof JavaClassFileType))) { 89 | e.getPresentation().setEnabled(false); 90 | return; 91 | } 92 | 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /ValueContext.java: -------------------------------------------------------------------------------- 1 | package com.cq.valuegenerator; 2 | 3 | import java.nio.file.Path; 4 | import java.nio.file.Paths; 5 | import java.util.Arrays; 6 | import java.util.Collections; 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | import com.cq.common.CodeUtils; 11 | import com.cq.file.FileUtils; 12 | import com.github.javafaker.Faker; 13 | import com.intellij.openapi.actionSystem.AnActionEvent; 14 | import com.intellij.openapi.actionSystem.CommonDataKeys; 15 | import com.intellij.psi.PsiClass; 16 | import com.intellij.psi.PsiFile; 17 | import com.intellij.psi.PsiMethod; 18 | import com.intellij.psi.PsiParameterList; 19 | import com.intellij.psi.PsiType; 20 | import com.intellij.psi.util.PsiUtil; 21 | 22 | import static com.intellij.openapi.actionSystem.CommonDataKeys.PSI_ELEMENT; 23 | 24 | /** 25 | * 系统环境变量 26 | * 27 | * @author 有尘 28 | * @date 2021/9/29 29 | */ 30 | public class ValueContext { 31 | public static ValueContext INSTANCE = new ValueContext(); 32 | public static boolean isJsonFileSource = false; 33 | 34 | Faker faker = new Faker(); 35 | /** 36 | * 事件 37 | */ 38 | public static AnActionEvent event; 39 | /** 40 | * 目标java类 41 | */ 42 | public static PsiClass psiClass; 43 | 44 | /** 45 | * java文件 46 | */ 47 | public static PsiFile psiFile; 48 | /** 49 | * 测试文件路径 50 | */ 51 | public static String filePath; 52 | /** 53 | * 测试文件名称 54 | */ 55 | public static String fileName; 56 | /** 57 | * 测试文件路径 58 | */ 59 | public static Path path; 60 | 61 | public static PsiClass getPsiClass() { 62 | return psiClass; 63 | } 64 | 65 | public static PsiFile getPsiFile() { 66 | return psiFile; 67 | } 68 | 69 | public static String getFilePath() { 70 | return filePath; 71 | } 72 | 73 | public static String getFileName() { 74 | return fileName; 75 | } 76 | 77 | public static Path getPath() { 78 | return path; 79 | } 80 | 81 | public static void setEvent(AnActionEvent e) { 82 | event = e; 83 | psiClass = PsiUtil.getTopLevelClass(event.getData(PSI_ELEMENT)); 84 | psiFile = event.getData(CommonDataKeys.PSI_FILE); 85 | filePath = FileUtils.getUnitFilePath(psiFile); 86 | fileName = FileUtils.genJavaFileName(psiClass); 87 | path = Paths.get(filePath, fileName); 88 | } 89 | 90 | public static AnActionEvent getEvent() { 91 | return event; 92 | } 93 | 94 | public static void setIsJsonFileSource(boolean v) { 95 | isJsonFileSource = v; 96 | } 97 | 98 | public static boolean isJsonFileSource() { 99 | return isJsonFileSource; 100 | } 101 | 102 | public static ValueContext getContext() { 103 | return INSTANCE; 104 | } 105 | 106 | private final Map cachedCLass = Collections.synchronizedMap(new HashMap<>()); 107 | private final Map cachedMethod = Collections.synchronizedMap(new HashMap<>()); 108 | 109 | public Faker getFaker() { 110 | return faker; 111 | } 112 | 113 | public void loadClass(PsiClass psiClass) { 114 | Arrays.stream(psiClass.getAllFields()) 115 | .filter(a -> !CodeUtils.isPrimitiveType(a.getType())) 116 | .forEach(field -> { 117 | String fieldTypeName = field.getType().getCanonicalText(); 118 | PsiClass psiClass1 = PsiUtil.resolveClassInClassTypeOnly(field.getType()); 119 | if (psiClass1 != null) { 120 | cachedCLass.put(fieldTypeName, psiClass1); 121 | PsiMethod[] methods = psiClass1.getAllMethods(); 122 | for (PsiMethod a : methods) { 123 | if (!a.getModifierList().hasModifierProperty("private")) { 124 | // 参数列表 125 | PsiParameterList parameterList = a.getParameterList(); 126 | String name = fieldTypeName + a.getName() + parameterList.getParametersCount(); 127 | String shortName = field.getType().getPresentableText() + a.getName() + parameterList 128 | .getParametersCount(); 129 | System.out.println("cache method: " + name); 130 | cachedMethod.put(name, a); 131 | cachedMethod.put(shortName, a); 132 | for (int i = 0; i < parameterList.getParametersCount(); i++) { 133 | PsiType fieldArgType = parameterList.getParameter(i).getType(); 134 | PsiClass fieldArgClass = PsiUtil.resolveClassInClassTypeOnly(fieldArgType); 135 | cachedCLass.put(fieldArgType.getCanonicalText(), fieldArgClass); 136 | } 137 | } 138 | 139 | } 140 | } 141 | } 142 | ); 143 | } 144 | 145 | public PsiClass getClass(String typeName) { 146 | return cachedCLass.get(typeName); 147 | } 148 | 149 | public PsiMethod getMethod(String className, String methodName, int argSize) { 150 | return getMethod(className + methodName + argSize); 151 | } 152 | 153 | public PsiMethod getMethod(String methodKey) { 154 | return cachedMethod.get(methodKey); 155 | } 156 | 157 | public void clear() { 158 | cachedCLass.clear(); 159 | cachedMethod.clear(); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /file/CodeGenerator.java: -------------------------------------------------------------------------------- 1 | package com.cq.file; 2 | 3 | import java.util.Arrays; 4 | import java.util.HashMap; 5 | import java.util.HashSet; 6 | import java.util.List; 7 | import java.util.Map; 8 | import java.util.Set; 9 | import java.util.stream.Collectors; 10 | 11 | import com.cq.common.CodeUtils; 12 | import com.cq.common.MockitoConstants; 13 | import com.cq.valuegenerator.ValueContext; 14 | import com.intellij.openapi.application.ApplicationManager; 15 | import com.intellij.openapi.vfs.LocalFileSystem; 16 | import com.intellij.openapi.vfs.VirtualFile; 17 | import com.intellij.psi.PsiClass; 18 | import com.intellij.psi.PsiField; 19 | import com.intellij.psi.PsiFile; 20 | import com.intellij.psi.PsiJavaFile; 21 | import com.intellij.psi.PsiManager; 22 | import com.intellij.psi.PsiMethod; 23 | import com.intellij.psi.util.PsiUtil; 24 | import lombok.Data; 25 | 26 | import static com.cq.common.MockitoConstants.BEFORE_SETUP; 27 | import static com.cq.common.MockitoConstants.COMMON_ANNOTATION; 28 | import static com.cq.common.MockitoConstants.COMMON_IMPORT; 29 | 30 | /** 31 | * @author 有尘 32 | * @date 2021/9/28 33 | */ 34 | @Data 35 | public class CodeGenerator { 36 | ValueContext valueContext = ValueContext.getContext(); 37 | 38 | /** 39 | * 当前类 40 | */ 41 | private PsiClass psiClass; 42 | /** 43 | * 需要mock的类变量 44 | */ 45 | private List needMockFields; 46 | /** 47 | * 需要输出测试的方法 48 | */ 49 | private List needMockMethods; 50 | /** 51 | * 当前文件,主要为了获取路径 52 | */ 53 | private PsiFile psiFile; 54 | private Set needImports = new HashSet<>(); 55 | private Map methodCount = new HashMap<>(); 56 | JsonFileGenerator jsonFileGenerator = new JsonFileGenerator(); 57 | 58 | public CodeGenerator(List fields, List needMockMethods) { 59 | this.psiClass = ValueContext.getPsiClass(); 60 | this.needMockFields = fields; 61 | this.needMockMethods = needMockMethods.stream().map(a -> new MyMethod(a, this)).collect(Collectors.toList()); 62 | this.psiFile = ValueContext.getPsiFile(); 63 | if (!ValueContext.isJsonFileSource()) { 64 | needImports.add("import com.util.TestUtils;\n"); 65 | needImports.add("import org.junit.jupiter.params.provider.ValueSource;\n"); 66 | saveTestUtils(); 67 | } 68 | } 69 | 70 | public CodeGenerator(PsiClass psiClass, List needMockMethods, PsiFile psiFile) { 71 | this.psiClass = psiClass; 72 | this.needMockMethods = needMockMethods.stream().map(a -> new MyMethod(a, this)).collect(Collectors.toList()); 73 | // todo 有一些小问题,比如不需要mock的fields 74 | VirtualFile virtualFile = LocalFileSystem.getInstance().findFileByIoFile(ValueContext.getPath().toFile()); 75 | PsiJavaFile file = (PsiJavaFile)PsiManager.getInstance(ValueContext.getEvent().getProject()).findFile(virtualFile); 76 | PsiClass aClass = file.getClasses()[0]; 77 | this.needMockFields = Arrays.asList(aClass.getAllFields()); 78 | this.psiFile = psiFile; 79 | } 80 | 81 | public String genContent() { 82 | 83 | generateImports(); 84 | for (MyMethod method : needMockMethods) { 85 | method.build(); 86 | needImports.addAll(method.getNeedImports()); 87 | } 88 | StringBuilder code = new StringBuilder(); 89 | code.append(generatePackageInfo()); 90 | code.append("\n"); 91 | //todo 放到后面 code.append(generateImports()); 92 | needImports.stream().filter(a -> a.contains(".")).forEach(a -> code.append(a)); 93 | code.append(generateCommonUnitImport()); 94 | code.append(generateClassDeclaration()); 95 | code.append(generateTestObject()); 96 | code.append(generateMockObjects()); 97 | code.append(generateSetUpMethod()); 98 | 99 | for (MyMethod method : needMockMethods) { 100 | code.append(method.getText()); 101 | } 102 | code.append("}"); 103 | return code.toString(); 104 | 105 | } 106 | 107 | public String genMethodBody() { 108 | StringBuilder code = new StringBuilder(); 109 | for (MyMethod method : needMockMethods) { 110 | method.build(); 111 | code.append(method.getText()); 112 | } 113 | return code.toString(); 114 | } 115 | 116 | /** 117 | * 打包信息,package xxx.xxx 118 | * 119 | * @return 120 | */ 121 | private String generatePackageInfo() { 122 | return String.format("package %s;\n", PsiUtil.getPackageName(psiClass)); 123 | } 124 | 125 | /** 126 | * 生成所需要的对象依赖 127 | * 128 | * @return 129 | */ 130 | private void generateImports() { 131 | needMockFields.stream().map(a -> { 132 | String canonicalText = a.getType().getCanonicalText(); 133 | return CodeUtils.filterGeneric(canonicalText); 134 | }).distinct().forEach(t -> needImports.add(String.format("import %s;\n", t))); 135 | } 136 | 137 | /** 138 | * 通用依赖,包括junit、util、Mokito 139 | * 140 | * @return 141 | */ 142 | private String generateCommonUnitImport() { 143 | return COMMON_IMPORT; 144 | } 145 | 146 | /** 147 | * 生成类描述 148 | * 149 | * @return 150 | */ 151 | private String generateClassDeclaration() { 152 | return COMMON_ANNOTATION + String.format("public class %sTest {\n", psiClass.getName()); 153 | } 154 | 155 | /** 156 | * 生成测试对象 157 | * 158 | * @return 159 | */ 160 | private String generateTestObject() { 161 | String name = psiClass.getName(); 162 | return "\t@InjectMocks\n" + String.format("\tprivate %s %s=new %s(); \n", name, CodeUtils.getCamelCase(name), 163 | name); 164 | } 165 | 166 | /** 167 | * 生成需要mock的成员 168 | * 169 | * @return 170 | */ 171 | private String generateMockObjects() { 172 | String collect = needMockFields.stream().map(a -> { 173 | String canonicalText = a.getType().getPresentableText(); 174 | return "\t@Mock\n" + String.format("\tprivate %s %s; \n\n", canonicalText, 175 | a.getName()); 176 | }).collect(Collectors.joining()); 177 | 178 | return collect; 179 | } 180 | 181 | /** 182 | * 初始化测试对象 183 | * 184 | * @return 185 | */ 186 | private String generateSetUpMethod() { 187 | return BEFORE_SETUP + "\n"; 188 | } 189 | 190 | private void saveTestUtils() { 191 | String path = psiFile.getParent().getVirtualFile().getPath(); 192 | String s = path.replaceAll("/src/main/java.*", "/src/test/java/com/util/"); 193 | ApplicationManager.getApplication().runWriteAction( 194 | new FileCreateTask(s, "TestUtils.java", MockitoConstants.TEST_UTILS_CLASS)); 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /common/CodeUtils.java: -------------------------------------------------------------------------------- 1 | package com.cq.common; 2 | 3 | import java.util.Arrays; 4 | import java.util.HashSet; 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.Set; 8 | import java.util.Stack; 9 | import java.util.stream.Collectors; 10 | 11 | import com.intellij.psi.PsiBlockStatement; 12 | import com.intellij.psi.PsiCodeBlock; 13 | import com.intellij.psi.PsiElement; 14 | import com.intellij.psi.PsiIfStatement; 15 | import com.intellij.psi.PsiLoopStatement; 16 | import com.intellij.psi.PsiMethod; 17 | import com.intellij.psi.PsiPrimitiveType; 18 | import com.intellij.psi.PsiStatement; 19 | import com.intellij.psi.PsiType; 20 | import com.intellij.psi.impl.source.PsiClassReferenceType; 21 | import com.intellij.psi.util.PsiUtil; 22 | 23 | /** 24 | * @author 有尘 25 | * @date 2021/9/28 26 | */ 27 | public class CodeUtils { 28 | public static Set PRIMITIVE_TYPES = new HashSet() {{ 29 | add("Boolean"); 30 | add("Float"); 31 | add("Double"); 32 | add("Integer"); 33 | add("Long"); 34 | add("Number"); 35 | add("Character"); 36 | add("CharSequence"); 37 | add("String"); 38 | add("Date"); 39 | add("Byte"); 40 | }}; 41 | 42 | public static String getCamelCase(String str) { 43 | String substring = str.substring(1); 44 | return Character.toLowerCase(str.charAt(0)) + substring; 45 | } 46 | 47 | public static boolean isPrimitiveType(PsiType type) { 48 | return PRIMITIVE_TYPES.contains(type.getPresentableText()) || (type instanceof PsiPrimitiveType); 49 | } 50 | 51 | public static int getCount(String source, String c) { 52 | int length = source.replaceAll("\\{.*}", "").split(c).length; 53 | if (length > 1) { 54 | return length; 55 | } else if (source.endsWith("()")) { 56 | return 0; 57 | } else { 58 | return 1; 59 | } 60 | } 61 | 62 | public static String filterGeneric(String canonicalText) { 63 | int i = canonicalText.indexOf("<"); 64 | if (i >= 0) { 65 | canonicalText = canonicalText.substring(0, i); 66 | } 67 | return canonicalText; 68 | } 69 | public static PsiType getCollectionType(PsiType type){ 70 | PsiType deepComponentType = PsiUtil.extractIterableTypeParameter(type, false); 71 | if (deepComponentType==null){ 72 | // 集合泛型 73 | System.out.println("deepComponentType is null"+type.getCanonicalText()); 74 | if(type instanceof PsiClassReferenceType) { 75 | PsiClassReferenceType referenceType = (PsiClassReferenceType)type; 76 | if("List".equals(referenceType.getName())) { 77 | return referenceType.getParameters()[0]; 78 | } 79 | } 80 | } 81 | return deepComponentType; 82 | } 83 | public static String getBody(PsiMethod method, Map codeBlockMap) { 84 | PsiCodeBlock body = method.getBody(); 85 | if(body==null){ 86 | return ""; 87 | } 88 | codeBlockMap.put(method, false); 89 | 90 | PsiStatement[] statements = body.getStatements(); 91 | String collect = Arrays.stream(statements).map( 92 | a -> getString(a)).collect(Collectors.joining()); 93 | 94 | String s = codeBlockMap.entrySet().stream().filter( 95 | a -> a.getValue() && (collect.contains(a.getKey().getName() + "(") || collect.contains( 96 | "super." + a.getKey().getName() + "("))).map( 97 | a -> getBody(a.getKey(), codeBlockMap)).collect(Collectors.joining()) + collect; 98 | return s; 99 | } 100 | 101 | public static String getString(PsiStatement psiStatement) { 102 | StringBuilder sb = new StringBuilder(); 103 | PsiElement[] children = psiStatement.getChildren(); 104 | for (int i = 0; i < children.length; i++) { 105 | PsiElement child = children[i]; 106 | if (child instanceof PsiLoopStatement) { 107 | sb.append(getString(((PsiLoopStatement)child).getBody())); 108 | } else if (child instanceof PsiBlockStatement) { 109 | PsiStatement[] statements = ((PsiBlockStatement)child).getCodeBlock().getStatements(); 110 | String collect = Arrays.stream(statements).map(a -> 111 | { 112 | if ((a instanceof PsiLoopStatement) || (a instanceof PsiIfStatement)) { 113 | return getString(a); 114 | } else { 115 | return "\n" + a.getText().replace("\n", ""); 116 | } 117 | }).collect(Collectors.joining("\n")); 118 | sb.append(collect); 119 | } else { 120 | sb.append("\n" + child.getText().replace("\n", "")); 121 | } 122 | } 123 | return "\n" + sb.toString(); 124 | } 125 | 126 | /** 127 | * 获取方法名(方法名+参数个数) 128 | * 参数中可能存在方法调用 129 | * @param line 130 | * @param name 131 | * @return 132 | */ 133 | public static String getStringOnlyBlock(String line, String name) { 134 | line = line.replaceAll("\\{.*}", ""); 135 | Stack stack = new Stack<>(); 136 | int count = -1; 137 | int index = 0; 138 | while (!stack.isEmpty() || index < line.length()) { 139 | char c = line.charAt(index); 140 | if (c == '(') { 141 | count++; 142 | stack.push('('); 143 | } else if (c == ')') { 144 | if (count == 0) { 145 | stack.push(c); 146 | break; 147 | } 148 | while (!stack.empty() && stack.peek() != '(') { 149 | stack.pop(); 150 | } 151 | if (stack.peek() == '(') { 152 | stack.pop(); 153 | count--; 154 | } 155 | } else { 156 | stack.push(c); 157 | } 158 | index++; 159 | } 160 | StringBuilder sb = new StringBuilder(); 161 | while (!stack.isEmpty()) { 162 | sb.append(stack.pop()); 163 | } 164 | return sb.reverse().toString(); 165 | } 166 | 167 | public static boolean isCollection(PsiType type){ 168 | 169 | // 父类类型 170 | PsiType[] types = type.getSuperTypes(); 171 | List fieldTypeNames= Arrays.stream(types).map(PsiType::getPresentableText).collect(Collectors.toList()); 172 | //集合类,或迭代器类 173 | if (fieldTypeNames.stream().anyMatch(s -> s.startsWith("Collection") || s.startsWith("Iterable"))) { 174 | return true; 175 | } 176 | // 集合泛型 177 | if(type instanceof PsiClassReferenceType) { 178 | PsiClassReferenceType referenceType = (PsiClassReferenceType)type; 179 | if("List".equals(referenceType.getName())) { 180 | return true; 181 | } 182 | } 183 | return false; 184 | } 185 | 186 | } 187 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /file/JsonFileGenerator.java: -------------------------------------------------------------------------------- 1 | package com.cq.file; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.HashMap; 6 | import java.util.LinkedHashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | import java.util.stream.Collectors; 10 | 11 | import com.cq.common.CodeUtils; 12 | import com.cq.model.ParentTree; 13 | import com.cq.valuegenerator.JsonValueService; 14 | import com.cq.valuegenerator.impl.BooleanGenerator; 15 | import com.cq.valuegenerator.impl.ByteGenerator; 16 | import com.cq.valuegenerator.impl.CharacterGenerator; 17 | import com.cq.valuegenerator.impl.DateGenerator; 18 | import com.cq.valuegenerator.impl.DoubleGenerator; 19 | import com.cq.valuegenerator.impl.FloatGenerator; 20 | import com.cq.valuegenerator.impl.IntegerGenerator; 21 | import com.cq.valuegenerator.impl.LongGenerator; 22 | import com.cq.valuegenerator.impl.StringGenerator; 23 | import com.google.common.collect.Maps; 24 | import com.intellij.psi.PsiAnnotation; 25 | import com.intellij.psi.PsiArrayType; 26 | import com.intellij.psi.PsiClass; 27 | import com.intellij.psi.PsiEnumConstant; 28 | import com.intellij.psi.PsiField; 29 | import com.intellij.psi.PsiPrimitiveType; 30 | import com.intellij.psi.PsiType; 31 | import com.intellij.psi.PsiTypeParameter; 32 | import com.intellij.psi.impl.source.PsiClassReferenceType; 33 | import com.intellij.psi.util.PsiUtil; 34 | import org.apache.commons.lang.StringUtils; 35 | 36 | import static com.cq.common.CodeUtils.isCollection; 37 | 38 | /** 39 | * @author 有尘 40 | * @date 2021/9/28 41 | */ 42 | public class JsonFileGenerator { 43 | 44 | private final static Map normalTypes = new HashMap<>(); 45 | 46 | private static ParentTree node; 47 | 48 | static { 49 | //Fake fakeDecimal = new FakeDecimal(); 50 | //FakeDateTime fakeDateTime = new FakeDateTime(); 51 | 52 | normalTypes.put("Boolean", new BooleanGenerator()); 53 | normalTypes.put("Float", new FloatGenerator()); 54 | normalTypes.put("Double", new DoubleGenerator()); 55 | normalTypes.put("Integer", new IntegerGenerator()); 56 | normalTypes.put("Long", new LongGenerator()); 57 | normalTypes.put("Number", new IntegerGenerator()); 58 | normalTypes.put("Character", new CharacterGenerator()); 59 | normalTypes.put("CharSequence", new StringGenerator()); 60 | normalTypes.put("String", new StringGenerator()); 61 | normalTypes.put("Date", new DateGenerator()); 62 | normalTypes.put("Byte", new ByteGenerator()); 63 | } 64 | 65 | public static Object getFields(PsiType type) { 66 | Map map = new LinkedHashMap<>(); 67 | 68 | if (type == null) { 69 | return map; 70 | } 71 | 72 | PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(type); 73 | for (PsiField field : psiClass.getAllFields()) { 74 | map.put(fieldResolve(field), typeResolve(field.getType(), 0)); 75 | } 76 | 77 | return map; 78 | } 79 | 80 | /** 81 | * 属性值 82 | * 83 | * @param type 84 | * @param level 85 | * @return 86 | */ 87 | public static Object typeResolve(PsiType type, int level) { 88 | if (level == 0) { 89 | node = new ParentTree(type.getCanonicalText()); 90 | } else { 91 | if (alreadyExist(node)) { 92 | return null; 93 | } else { 94 | // 转换node 95 | ParentTree newNode = node.getSonList().computeIfAbsent(type.getCanonicalText(), 96 | a -> { 97 | ParentTree parentTree1 = new ParentTree(a); 98 | parentTree1.setParent(node); 99 | return parentTree1; 100 | }); 101 | node = newNode; 102 | } 103 | } 104 | level = ++level; 105 | try { 106 | /** 107 | * 原始类型 108 | */ 109 | if (type instanceof PsiPrimitiveType) { 110 | return normalTypes.get(getPackageType(type)).defaultValue(); 111 | } 112 | 113 | /** 114 | * 数组类型 115 | */ 116 | if (type instanceof PsiArrayType) { 117 | System.out.println("is Array :" + type); 118 | List list = new ArrayList<>(); 119 | PsiType deepType = type.getDeepComponentType(); 120 | list.add(typeResolve(deepType, level)); 121 | return list; 122 | } 123 | 124 | List fieldTypeNames = new ArrayList<>(); 125 | 126 | fieldTypeNames.add(type.getPresentableText()); 127 | // 父类类型 128 | PsiType[] types = type.getSuperTypes(); 129 | fieldTypeNames.addAll(Arrays.stream(types).map(PsiType::getPresentableText).collect(Collectors.toList())); 130 | 131 | //集合类,或迭代器类 132 | if (isCollection(type)) { 133 | List list = new ArrayList<>(); 134 | PsiType deepType = CodeUtils.getCollectionType(type); 135 | System.out.println("json deepType:"+deepType.getCanonicalText()); 136 | list.add(typeResolve(deepType, level)); 137 | return list; 138 | } 139 | Map map = new LinkedHashMap<>(); 140 | 141 | PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(type); 142 | 143 | /** 144 | * 枚举类型 145 | */ 146 | if (psiClass != null && psiClass.isEnum()) { 147 | for (PsiField field : psiClass.getFields()) { 148 | if (field instanceof PsiEnumConstant) { 149 | return field.getName(); 150 | } 151 | } 152 | return ""; 153 | 154 | } 155 | // 其他Object类型 156 | 157 | //todo map类型 158 | if (fieldTypeNames.stream().anyMatch(s -> s.startsWith("Map"))) { 159 | HashMap objectHashMap = Maps.newHashMap(); 160 | PsiType[] parameters = ((PsiClassReferenceType)type).getParameters(); 161 | if (parameters.length >= 2) { 162 | objectHashMap.put(typeResolve(parameters[0], level).toString(), typeResolve(parameters[1], level)); 163 | } 164 | return objectHashMap; 165 | } 166 | 167 | // Class类型 168 | if (fieldTypeNames.stream().anyMatch(s -> s.startsWith("Class"))) { 169 | return null; 170 | } 171 | 172 | List retain = new ArrayList<>(fieldTypeNames); 173 | retain.retainAll(normalTypes.keySet()); 174 | if (!retain.isEmpty()) { 175 | return normalTypes.get(retain.get(0)).defaultValue(); 176 | } else { 177 | 178 | if (level > 500) { 179 | throw new RuntimeException( 180 | "This class reference level exceeds maximum limit or has nested references!"); 181 | } 182 | if(psiClass==null){ 183 | return map; 184 | } 185 | for (PsiField field : psiClass.getAllFields()) { 186 | // 静态变量无需配置 187 | if (field.hasModifierProperty("static")) { 188 | continue; 189 | } 190 | // 针对泛型 191 | if (type instanceof PsiClassReferenceType) { 192 | // 泛型参数类型 193 | PsiType[] parameters = ((PsiClassReferenceType)type).getParameters(); 194 | // 泛型表示,比如T,R 195 | PsiTypeParameter[] typeParameters = psiClass.getTypeParameters(); 196 | for (int i = 0; i < parameters.length; i++) { 197 | // 得到里面的类型 198 | String genericName = typeParameters[i].getText(); 199 | // 得到T 200 | String presentableText = field.getType().getPresentableText(); 201 | if (presentableText.equals(genericName)) { 202 | map.put(fieldResolve(field), typeResolve(parameters[i], level)); 203 | } 204 | } 205 | } 206 | if (!map.containsKey(fieldResolve(field))) { 207 | map.put(fieldResolve(field), typeResolve(field.getType(), level)); 208 | } 209 | 210 | } 211 | return map; 212 | } 213 | } catch (Exception e) { 214 | return null; 215 | } finally { 216 | node = node.getParent(); 217 | } 218 | 219 | } 220 | 221 | public static String getPackageType(PsiType type) { 222 | switch (type.getCanonicalText()) { 223 | case "boolean": 224 | return "Boolean"; 225 | case "byte": 226 | return "Byte"; 227 | case "short": 228 | case "int": 229 | return "Integer"; 230 | case "long": 231 | return "Long"; 232 | case "float": 233 | return "Float"; 234 | case "double": 235 | return "Double"; 236 | case "char": 237 | return "Character"; 238 | default: 239 | return null; 240 | } 241 | } 242 | 243 | /** 244 | * 属性名 245 | * 246 | * @param field 247 | * @return 248 | */ 249 | private static String fieldResolve(PsiField field) { 250 | 251 | PsiAnnotation annotation = field.getAnnotation(com.fasterxml.jackson.annotation.JsonProperty.class.getName()); 252 | if (annotation != null) { 253 | String fieldName = annotation.findAttributeValue("value").getText() 254 | .replace("\"", ""); 255 | if (StringUtils.isNotBlank(fieldName)) { 256 | return fieldName; 257 | } 258 | } 259 | 260 | annotation = field.getAnnotation("com.alibaba.fastjson.annotation.JSONField"); 261 | if (annotation != null) { 262 | String fieldName = annotation.findAttributeValue("name").getText() 263 | .replace("\"", ""); 264 | if (StringUtils.isNotBlank(fieldName)) { 265 | return fieldName; 266 | } 267 | } 268 | return field.getName(); 269 | } 270 | 271 | public static Object tryGetPrimitiveType(PsiType type) { 272 | Object res; 273 | if (normalTypes.containsKey(type.getPresentableText())) { 274 | return normalTypes.get(type.getPresentableText()).defaultValue(); 275 | } else { 276 | String packageType = getPackageType(type); 277 | 278 | res = packageType == null ? null : normalTypes.get(packageType).defaultValue(); 279 | } 280 | return res; 281 | } 282 | 283 | public static String getPrimitiveTypeStr(PsiType type) { 284 | if (normalTypes.containsKey(type.getPresentableText())) { 285 | return type.getPresentableText(); 286 | } else { 287 | return getPackageType(type); 288 | } 289 | } 290 | 291 | /** 292 | * 获取json对象 293 | * 294 | * @param type 295 | * @return 296 | */ 297 | public static Object getJsonObject(PsiType type) { 298 | Object randomValue = tryGetPrimitiveType(type); 299 | if (randomValue == null) { 300 | Object fieldMap = typeResolve(type, 0); 301 | return fieldMap; 302 | // 获取json数据 303 | } else { 304 | return randomValue; 305 | } 306 | } 307 | 308 | 309 | public static boolean isGeneric(String presentableText) { 310 | return presentableText.contains("<"); 311 | } 312 | 313 | public static boolean alreadyExist(ParentTree node) { 314 | if (node == null) { 315 | return false; 316 | } 317 | String name = node.getValue(); 318 | while (node.getParent() != null) { 319 | node = node.getParent(); 320 | if (name.equals(node.getValue())) { 321 | return true; 322 | } 323 | } 324 | return false; 325 | } 326 | } 327 | -------------------------------------------------------------------------------- /file/MyMethod.java: -------------------------------------------------------------------------------- 1 | package com.cq.file; 2 | 3 | import java.util.Arrays; 4 | import java.util.HashMap; 5 | import java.util.HashSet; 6 | import java.util.Map; 7 | import java.util.Set; 8 | import java.util.function.Function; 9 | import java.util.regex.Matcher; 10 | import java.util.regex.Pattern; 11 | import java.util.stream.Collectors; 12 | import java.util.stream.IntStream; 13 | 14 | import com.alibaba.fastjson.JSONObject; 15 | 16 | import com.cq.common.CodeUtils; 17 | import com.cq.common.MutiValuesWithClass; 18 | import com.cq.valuegenerator.ValueContext; 19 | import com.intellij.openapi.application.ApplicationManager; 20 | import com.intellij.psi.PsiArrayType; 21 | import com.intellij.psi.PsiClass; 22 | import com.intellij.psi.PsiField; 23 | import com.intellij.psi.PsiMethod; 24 | import com.intellij.psi.PsiParameterList; 25 | import com.intellij.psi.PsiType; 26 | import com.intellij.psi.impl.source.PsiClassReferenceType; 27 | import com.intellij.psi.util.PsiUtil; 28 | import lombok.Data; 29 | import org.apache.commons.lang.StringUtils; 30 | 31 | import static com.cq.file.JsonFileGenerator.getJsonObject; 32 | import static com.cq.file.JsonFileGenerator.getPrimitiveTypeStr; 33 | import static com.cq.file.JsonFileGenerator.isCollection; 34 | import static com.cq.file.JsonFileGenerator.isGeneric; 35 | 36 | /** 37 | * @author 有尘 38 | * @date 2021/10/9 39 | */ 40 | @Data 41 | public class MyMethod { 42 | /** 43 | * 测试方法 44 | */ 45 | PsiMethod method; 46 | /** 47 | * 需要mock的数据 48 | */ 49 | Map needMockFields; 50 | /** 51 | * 类变量中需要mock的方法 52 | */ 53 | Map needMockFieldMethod; 54 | 55 | private Set needImports; 56 | 57 | private CodeGenerator codeGenerator; 58 | 59 | private String text; 60 | Map nameCount = new HashMap<>(); 61 | ValueContext valueContext = ValueContext.getContext(); 62 | 63 | public MyMethod(PsiMethod method, CodeGenerator codeGenerator) { 64 | this.method = method; 65 | this.codeGenerator = codeGenerator; 66 | needMockFieldMethod = new HashMap<>(); 67 | needMockFields = new HashMap<>(); 68 | needImports = new HashSet<>(); 69 | } 70 | 71 | public void build() { 72 | String methodName = method.getName(); 73 | String methodNameCount = methodName; 74 | // 同名方法 75 | Map methodCount = codeGenerator.getMethodCount(); 76 | if (methodCount.containsKey(methodName)) { 77 | Integer count = methodCount.get(methodName); 78 | methodNameCount = methodNameCount + count; 79 | methodCount.put(methodName, count + 1); 80 | } else { 81 | methodCount.put(methodName, 0); 82 | } 83 | 84 | String methodContent = this.generateMethodContent(method, methodNameCount); 85 | 86 | String filePath = codeGenerator.getPsiFile().getVirtualFile().getPath(); 87 | int index = filePath.indexOf("java"); 88 | filePath = filePath.substring(index + 5).replace(".java", ""); 89 | String fileName = FileUtils.getJsonFileName(methodNameCount); 90 | this.text = generateText(filePath, fileName, methodNameCount, methodContent); 91 | } 92 | 93 | /** 94 | * 生成方法内容 95 | * 96 | * @param filePath 97 | * @param fileName 98 | * @param methodNameCount 99 | * @param methodContent 100 | * @return 101 | */ 102 | private String generateText(String filePath, String fileName, String methodNameCount, String methodContent) { 103 | if (ValueContext.isJsonFileSource()) { 104 | return String.format("\t@ParameterizedTest\n" 105 | + "\t@JsonFileSource(resources = {\"/%s/%s\"})\n" 106 | + " \tpublic void %sTest(JSONObject arg) {\n%s\t}\n\n", filePath, fileName, methodNameCount, 107 | methodContent); 108 | } else { 109 | return "\t@ParameterizedTest\n" + 110 | String.format("\t@ValueSource(strings = {\"/%s/%s\"})\n" 111 | + " \tpublic vaoid %sTest(String str) {\n\t\tJSONObject arg= TestUtils.getTestArg(str);\n" 112 | + "%s\t}\n\n", filePath, fileName, methodNameCount, 113 | methodContent); 114 | } 115 | } 116 | 117 | /** 118 | * 生成方法内容,并且生成需要的测试数据 119 | * 每一个方法生成一个大的 json对象 120 | * 121 | * @param method 测试方法 122 | * @param fileName 数据文件名称,每一个方法对应一个文件名 123 | * @return 124 | */ 125 | private String generateMethodContent(PsiMethod method, String fileName) { 126 | StringBuilder code = new StringBuilder(512); 127 | String param = generateMethodArg(method); 128 | // 用来生成测试数据--json文件 129 | Map fields = new HashMap<>(); 130 | 131 | generateInputData(method, fields); 132 | String methodStr = generateMockMethod(method, fields); 133 | String attr = generateVarFromJsonCode(fields); 134 | // todo 135 | code.append(attr); 136 | code.append(methodStr); 137 | PsiType returnType = method.getReturnType(); 138 | if (returnType.getPresentableText().equals("void")) { 139 | code.append( 140 | String.format("\t\t%s.%s(%s);\n", 141 | CodeUtils.getCamelCase(codeGenerator.getPsiClass().getName()), 142 | method.getName(), param)); 143 | } else { 144 | getImport(returnType); 145 | code.append( 146 | String.format("\t\t%s result = %s.%s(%s);\n", returnType.getPresentableText(), 147 | CodeUtils.getCamelCase(codeGenerator.getPsiClass().getName()), 148 | method.getName(), param)); 149 | } 150 | // 保存json数据 151 | saveSourceData(fileName, fields); 152 | code.append("\t\t//todo verify the result\n"); 153 | return code.toString(); 154 | } 155 | 156 | /** 157 | * 生成方法内部的调用参数 158 | * 159 | * @param method 160 | * @return 161 | */ 162 | private String generateMethodArg(PsiMethod method) { 163 | PsiParameterList parameters = method.getParameterList(); 164 | StringBuilder param = new StringBuilder(); 165 | IntStream.range(0, parameters.getParametersCount()).forEach((i) -> { 166 | PsiType type = parameters.getParameter(i).getType(); 167 | getImport(type); 168 | // 生成参数 169 | param.append(parameters.getParameter(i).getName() + ","); 170 | 171 | }); 172 | if (parameters.getParametersCount() > 0) { 173 | param.deleteCharAt(param.length() - 1); 174 | } 175 | return param.toString(); 176 | } 177 | 178 | /** 179 | * 生成测试数据以及获取数据的code 180 | * 181 | * @param method 182 | * @param fields 183 | * @return 184 | */ 185 | private void generateInputData(PsiMethod method, Map fields) { 186 | PsiParameterList parameters = method.getParameterList(); 187 | IntStream.range(0, parameters.getParametersCount()).forEach((i) -> { 188 | PsiType type = parameters.getParameter(i).getType(); 189 | Object jsonObject = getJsonObject(type); 190 | // todo 入参的名字,注意范型 191 | MutiValuesWithClass objectListMap = fields.computeIfAbsent(type, a -> new MutiValuesWithClass(jsonObject)); 192 | objectListMap.addNames(parameters.getParameter(i).getName()); 193 | }); 194 | } 195 | 196 | /** 197 | * 生成mock对象对应的mock方法,从方法体找到所有mock对象的用到的方法 198 | * 199 | * @param method 200 | * @param fields 201 | * @return 202 | */ 203 | private String generateMockMethod(PsiMethod method, Map fields) { 204 | StringBuilder content = new StringBuilder(); 205 | //modifierList.get 206 | Map collect = Arrays.stream(codeGenerator.getPsiClass().getAllMethods()).filter( 207 | a -> !a.equals(method) && !a.getName().equals("equals")).collect( 208 | Collectors.toMap(Function.identity(), a -> true)); 209 | 210 | String body = CodeUtils.getBody(method, collect); 211 | for (int i = 0; i < codeGenerator.getNeedMockFields().size(); i++) { 212 | PsiField field = codeGenerator.getNeedMockFields().get(i); 213 | Set alreadyMockMethods = new HashSet<>(); 214 | Pattern pattern = Pattern.compile(field.getName() + ".\\w+\\(.*\\)"); 215 | Matcher matcher = pattern.matcher(body); 216 | String filedCanonicalName = field.getType().getCanonicalText(); 217 | while (matcher.find()) { 218 | String methodName = matcher.group(); 219 | methodName = CodeUtils.getStringOnlyBlock(methodName, field.getName()); 220 | String methodShortName = methodName.substring(methodName.indexOf('.') + 1, methodName.indexOf('(')); 221 | 222 | String methodKey = filedCanonicalName + methodShortName + CodeUtils.getCount(methodName, 223 | ","); 224 | System.out.println("methodKey:" + methodKey); 225 | if (alreadyMockMethods.contains(methodKey)) { 226 | continue; 227 | } else { 228 | alreadyMockMethods.add(methodKey); 229 | // 找到方法 230 | PsiMethod fieldMethod = valueContext.getMethod(methodKey); 231 | if (fieldMethod == null) { 232 | throw new RuntimeException( 233 | "方法太复杂,无法解析该方法:: " + methodKey); 234 | } 235 | PsiType returnType = fieldMethod.getReturnType(); 236 | String fieldMethodReturnType = getImport(returnType); 237 | if (!StringUtils.equalsIgnoreCase(fieldMethodReturnType, "void")) { 238 | Object jsonObject = getJsonObject(returnType); 239 | MutiValuesWithClass mutiValuesWithClass = fields.computeIfAbsent(returnType, 240 | a -> new MutiValuesWithClass(jsonObject)); 241 | 242 | String attrName = generateArgName(fieldMethodReturnType, returnType); 243 | mutiValuesWithClass.addNames(attrName); 244 | content.append(String.format("\t\twhen(%s(" + generateFieldMethodArg(fieldMethod) + 245 | ")).thenReturn(%s);\n", field.getName() + "." + methodShortName, attrName)); 246 | } 247 | } 248 | } 249 | } 250 | 251 | return content.toString(); 252 | } 253 | 254 | /** 255 | * 解决名字重复、名字是基础类型的问题 256 | * 257 | * @param fieldMethodReturnType 258 | * @param returnType 259 | * @return 260 | */ 261 | private String generateArgName(String fieldMethodReturnType, PsiType returnType) { 262 | String attrName = CodeUtils.filterGeneric(CodeUtils.getCamelCase(fieldMethodReturnType)); 263 | 264 | if (CodeUtils.isPrimitiveType(returnType)) { 265 | attrName += "Arg"; 266 | } 267 | // 防止重复 268 | String result = attrName; 269 | if (nameCount.containsKey(attrName)) { 270 | int count = nameCount.get(attrName); 271 | System.out.println("map:" + nameCount + "attrname:" + attrName + "count:" + count); 272 | result = attrName + count; 273 | nameCount.put(attrName, count + 1); 274 | } else { 275 | nameCount.put(attrName, 0); 276 | } 277 | return result; 278 | } 279 | 280 | private String generateVarFromJsonCode(Map fields) { 281 | StringBuilder jsonObjectBuilder = new StringBuilder(); 282 | fields.entrySet().forEach(a -> { 283 | PsiType type = a.getKey(); 284 | String shortName = type.getPresentableText(); 285 | MutiValuesWithClass value = a.getValue(); 286 | value.getNames().stream().forEach(name -> { 287 | PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(type); 288 | if (CodeUtils.isPrimitiveType(type)) { 289 | jsonObjectBuilder.append(String.format("\t\t%s %s = arg.get%s(\"%s\"); \n", 290 | shortName, name, getPrimitiveTypeStr(type), 291 | shortName)); 292 | } else if (isCollection(type)) { 293 | PsiType deepComponentType = CodeUtils.getCollectionType(type); 294 | jsonObjectBuilder.append( 295 | String.format("\t\t%s %s = JSONObject.parseArray(arg.getString(\"%s\"),%s.class); \n", 296 | shortName, name, 297 | shortName, deepComponentType.getPresentableText())); 298 | } else if (isGeneric(shortName)) { 299 | jsonObjectBuilder.append( 300 | String.format( 301 | "\t\t%s %s = JSONObject.parseObject(arg.getString(\"%s\"),new TypeReference<%s>(){}); \n", 302 | shortName, name, shortName, shortName)); 303 | } else if (psiClass != null && psiClass.isEnum()) { 304 | jsonObjectBuilder.append(String.format("\t\t%s %s = %s.values()[0]; \n", 305 | shortName, name, shortName)); 306 | } else { 307 | // todo 增加泛型 308 | jsonObjectBuilder.append( 309 | String.format("\t\t%s %s = JSONObject.parseObject(arg.getString(\"%s\"),%s.class); \n", 310 | shortName, name, shortName, CodeUtils.filterGeneric(shortName))); 311 | } 312 | }); 313 | 314 | }); 315 | return jsonObjectBuilder.toString(); 316 | } 317 | 318 | /** 319 | * 保存json数据 320 | * 321 | * @param methodName 322 | * @param fields 323 | */ 324 | private void saveSourceData(String methodName, Map fields) { 325 | String filePath = FileUtils.getJsonFilePath(this.codeGenerator.getPsiFile()); 326 | String fileName = FileUtils.getJsonFileName(methodName); 327 | Map collect = fields.entrySet().stream().collect( 328 | Collectors.toMap(a -> a.getKey().getPresentableText(), a -> { 329 | if (a.getValue() != null && a.getValue().getObject() != null) { 330 | return a.getValue().getObject(); 331 | } else { return ""; } 332 | } 333 | )); 334 | ApplicationManager.getApplication().runWriteAction( 335 | new FileCreateTask(filePath, fileName, JSONObject.toJSONString(collect, true))); 336 | } 337 | 338 | /** 339 | * 生成方法中所用到的变量的mock方法 340 | * 341 | * @param method 342 | * @return 343 | */ 344 | private String generateFieldMethodArg(PsiMethod method) { 345 | PsiParameterList parameters = method.getParameterList(); 346 | return IntStream.range(0, parameters.getParametersCount()).mapToObj( 347 | i -> { 348 | getImport(parameters.getParameter(i).getType()); 349 | return "any(" + CodeUtils.filterGeneric(parameters.getParameter(i) 350 | .getType().getPresentableText()) + ".class)"; 351 | }).collect( 352 | Collectors.joining(",")); 353 | } 354 | 355 | /** 356 | * 范型类需要特殊处理 357 | * 358 | * @param type 359 | */ 360 | private void getImportFromGeneric(PsiType type) { 361 | // 泛型类 362 | if (type instanceof PsiClassReferenceType) { 363 | PsiType[] parameters = ((PsiClassReferenceType)type).getParameters(); 364 | if (parameters.length > 0) { 365 | for (int i = 0; i < parameters.length; i++) { 366 | // 得到里面的类型 367 | // todo 当有多层的时候 368 | getImport(parameters[i]); 369 | } 370 | } 371 | } 372 | } 373 | 374 | private String getImport(PsiType returnType) { 375 | String fieldMethodReturnType = returnType.getPresentableText(); 376 | if (isCollection(returnType)) { 377 | PsiType deepComponentType = CodeUtils.getCollectionType(returnType); 378 | if (deepComponentType != null) { 379 | fieldMethodReturnType = deepComponentType.getPresentableText() + "List"; 380 | needImports.add(String.format("import %s;\n", deepComponentType.getCanonicalText())); 381 | } 382 | } else if ((returnType instanceof PsiArrayType) && !CodeUtils.isPrimitiveType( 383 | returnType.getDeepComponentType())) { 384 | needImports.add( 385 | String.format("import %s;\n", returnType.getDeepComponentType().getCanonicalText())); 386 | } else if (!CodeUtils.isPrimitiveType(returnType.getDeepComponentType())) { 387 | needImports.add( 388 | String.format("import %s;\n", CodeUtils.filterGeneric(returnType.getCanonicalText()))); 389 | // todo 多层 390 | getImportFromGeneric(returnType); 391 | } 392 | return fieldMethodReturnType; 393 | } 394 | } 395 | --------------------------------------------------------------------------------