From 908ba602fcbf135daa531153a435c8b17c351773 Mon Sep 17 00:00:00 2001 From: PasseRR Date: Wed, 30 Jun 2021 19:23:45 +0800 Subject: [PATCH 01/41] =?UTF-8?q?java=E6=94=B9=E5=86=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 4 +- .../idea/plugins/BaseTableModel.groovy | 64 ---- .../idea/plugins/NotificationThread.groovy | 36 -- .../idea/plugins/camel/ToggleCamelCase.groovy | 118 ------ .../idea/plugins/mybatis/LogConstants.groovy | 58 --- .../idea/plugins/mybatis/LogParser.groovy | 115 ------ .../mybatis/MybatisLog2SqlAction.groovy | 30 -- .../idea/plugins/mybatis/SqlFormatter.groovy | 361 ------------------ .../spring/web/ApiDocConfigView.groovy | 101 ----- .../spring/web/ApiDocConfigurable.groovy | 91 ----- .../spring/web/ApiDocStateComponent.groovy | 45 --- .../spring/web/BaseWebCopyAction.groovy | 71 ---- .../spring/web/CopyMethodApiDocAction.groovy | 124 ------ .../spring/web/po/ApiDocAliasPairPo.groovy | 37 -- .../spring/web/po/ApiDocSettingPo.groovy | 80 ---- .../idea/plugins/tool/ConvertType.groovy | 111 ------ .../idea/plugins/tool/TextFormatView.groovy | 224 ----------- .../plugins/tool/TextHandlerToolWindow.groovy | 21 - .../passerr/idea/plugins/tool/ToolMenu.groovy | 84 ---- .../passerr/idea/plugins/BaseTableModel.java | 67 ++++ .../passerr/idea/plugins/ColumnIndex.java | 10 + .../plugins/spring/web/BaseWebCopyAction.java | 75 ++++ .../spring/web/CopyMethodPathAction.java} | 24 +- .../web/PsiAnnotationMemberValueUtil.java} | 63 +-- .../plugins/spring/web/WebCopyConstants.java} | 80 ++-- src/main/resources/META-INF/plugin.xml | 50 ++- 26 files changed, 266 insertions(+), 1878 deletions(-) delete mode 100644 src/main/groovy/com/github/passerr/idea/plugins/BaseTableModel.groovy delete mode 100644 src/main/groovy/com/github/passerr/idea/plugins/NotificationThread.groovy delete mode 100644 src/main/groovy/com/github/passerr/idea/plugins/camel/ToggleCamelCase.groovy delete mode 100644 src/main/groovy/com/github/passerr/idea/plugins/mybatis/LogConstants.groovy delete mode 100644 src/main/groovy/com/github/passerr/idea/plugins/mybatis/LogParser.groovy delete mode 100644 src/main/groovy/com/github/passerr/idea/plugins/mybatis/MybatisLog2SqlAction.groovy delete mode 100644 src/main/groovy/com/github/passerr/idea/plugins/mybatis/SqlFormatter.groovy delete mode 100644 src/main/groovy/com/github/passerr/idea/plugins/spring/web/ApiDocConfigView.groovy delete mode 100644 src/main/groovy/com/github/passerr/idea/plugins/spring/web/ApiDocConfigurable.groovy delete mode 100644 src/main/groovy/com/github/passerr/idea/plugins/spring/web/ApiDocStateComponent.groovy delete mode 100644 src/main/groovy/com/github/passerr/idea/plugins/spring/web/BaseWebCopyAction.groovy delete mode 100644 src/main/groovy/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.groovy delete mode 100644 src/main/groovy/com/github/passerr/idea/plugins/spring/web/po/ApiDocAliasPairPo.groovy delete mode 100644 src/main/groovy/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.groovy delete mode 100644 src/main/groovy/com/github/passerr/idea/plugins/tool/ConvertType.groovy delete mode 100644 src/main/groovy/com/github/passerr/idea/plugins/tool/TextFormatView.groovy delete mode 100644 src/main/groovy/com/github/passerr/idea/plugins/tool/TextHandlerToolWindow.groovy delete mode 100644 src/main/groovy/com/github/passerr/idea/plugins/tool/ToolMenu.groovy create mode 100644 src/main/java/com/github/passerr/idea/plugins/BaseTableModel.java create mode 100644 src/main/java/com/github/passerr/idea/plugins/ColumnIndex.java create mode 100644 src/main/java/com/github/passerr/idea/plugins/spring/web/BaseWebCopyAction.java rename src/main/{groovy/com/github/passerr/idea/plugins/spring/web/CopyMethodPathAction.groovy => java/com/github/passerr/idea/plugins/spring/web/CopyMethodPathAction.java} (41%) rename src/main/{groovy/com/github/passerr/idea/plugins/spring/web/PsiAnnotationMemberValueUtil.groovy => java/com/github/passerr/idea/plugins/spring/web/PsiAnnotationMemberValueUtil.java} (42%) rename src/main/{groovy/com/github/passerr/idea/plugins/spring/web/WebCopyConstants.groovy => java/com/github/passerr/idea/plugins/spring/web/WebCopyConstants.java} (58%) diff --git a/build.gradle b/build.gradle index bd756bd..e9d66ac 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ buildscript { } apply plugin: 'idea' -apply plugin: 'groovy' +apply plugin: 'java' apply plugin: 'org.jetbrains.intellij' repositories { @@ -36,7 +36,7 @@ tasks.withType(JavaCompile) { } dependencies { - compile group: 'org.codehaus.groovy', name: 'groovy-all', version: '2.4.6' + compileOnly group: 'org.projectlombok', name: 'lombok', version: '1.18.2' compile group: 'com.fifesoft', name: 'rsyntaxtextarea', version: '2.6.1' compile group: 'com.google.code.gson', name: 'gson', version: '2.8.5' } \ No newline at end of file diff --git a/src/main/groovy/com/github/passerr/idea/plugins/BaseTableModel.groovy b/src/main/groovy/com/github/passerr/idea/plugins/BaseTableModel.groovy deleted file mode 100644 index e4dbb73..0000000 --- a/src/main/groovy/com/github/passerr/idea/plugins/BaseTableModel.groovy +++ /dev/null @@ -1,64 +0,0 @@ -package com.github.passerr.idea.plugins - -import com.intellij.util.ui.ItemRemovable - -import javax.swing.table.AbstractTableModel - -/** - * @date 2021/06/30 11:03 - * @Copyright (c) wisewe co.,ltd - * @author xiehai - */ -class BaseTableModel extends AbstractTableModel implements ItemRemovable { - List data - List headers - - BaseTableModel(List headers, List data) { - this.headers = headers - this.data = data - } - - @Override - String getColumnName(int column) { - this.headers[column] - } - - @Override - void removeRow(int idx) { - if (idx >= 0 && idx < this.getRowCount()) { - this.data.remove(idx as int) - super.fireTableRowsDeleted(idx, idx) - } - } - - @Override - int getRowCount() { - this.data.size() - } - - @Override - int getColumnCount() { - this.isSelf() ? 1 : this.columns().size() - } - - @Override - Object getValueAt(int rowIndex, int columnIndex) { - this.isSelf() ? this.data[rowIndex] : this.data.properties[this.columns()[columnIndex]] - } - - /** - * 列属性 - * @return 列 - */ - List columns() { - [] - } - - /** - * 列表元素是否是列本身 - * @return true/false - */ - boolean isSelf() { - true - } -} diff --git a/src/main/groovy/com/github/passerr/idea/plugins/NotificationThread.groovy b/src/main/groovy/com/github/passerr/idea/plugins/NotificationThread.groovy deleted file mode 100644 index 7db64d7..0000000 --- a/src/main/groovy/com/github/passerr/idea/plugins/NotificationThread.groovy +++ /dev/null @@ -1,36 +0,0 @@ -package com.github.passerr.idea.plugins - -import com.intellij.notification.Notification -import com.intellij.notification.Notifications - -import java.util.concurrent.TimeUnit - -/** - * 消息通知 - * @author xiehai1 - * @date 2018/11/08 17:31 - * @Copyright ( c ) gome inc Gome Co.,LTD - */ -class NotificationThread extends Thread { - private Notification notification - private int sleepTime - - NotificationThread(Notification notification) { - // 默认4秒关闭弹窗 - this(notification, 4) - } - - NotificationThread(Notification notification, int sleepTime) { - assert sleepTime > 0 - this.notification = notification - // 默认4秒关闭弹窗 - this.sleepTime = sleepTime - } - - @Override - void run() { - Notifications.Bus.notify(this.notification) - TimeUnit.SECONDS.sleep(this.sleepTime) - this.notification.expire() - } -} diff --git a/src/main/groovy/com/github/passerr/idea/plugins/camel/ToggleCamelCase.groovy b/src/main/groovy/com/github/passerr/idea/plugins/camel/ToggleCamelCase.groovy deleted file mode 100644 index f6c49b0..0000000 --- a/src/main/groovy/com/github/passerr/idea/plugins/camel/ToggleCamelCase.groovy +++ /dev/null @@ -1,118 +0,0 @@ -package com.github.passerr.idea.plugins.camel - -import com.intellij.codeInsight.actions.MultiCaretCodeInsightAction -import com.intellij.codeInsight.actions.MultiCaretCodeInsightActionHandler -import com.intellij.openapi.actionSystem.ActionGroup -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.application.Result -import com.intellij.openapi.application.WriteAction -import com.intellij.openapi.command.CommandProcessor -import com.intellij.openapi.editor.Caret -import com.intellij.openapi.editor.Editor -import com.intellij.openapi.editor.EditorModificationUtil -import com.intellij.openapi.project.Project -import com.intellij.psi.PsiFile -import org.apache.commons.lang.StringUtils -import org.jetbrains.annotations.NotNull - -import java.util.stream.IntStream - -/** - * 驼峰命名切换 - * @author xiehai1 - * @date 2018/10/12 12:31 - * @Copyright tellyes tech. inc. co.,ltd - */ -class ToggleCamelCase extends MultiCaretCodeInsightAction { - private static final String UNDERSCORE = "_" - - @Override - @NotNull - protected MultiCaretCodeInsightActionHandler getHandler() { - new MultiCaretCodeInsightActionHandler() { - @Override - void invoke(@NotNull Project project, @NotNull Editor editor, @NotNull Caret caret, @NotNull PsiFile file) { - def text = caret.getEditor().getSelectionModel().getSelectedText() - if (StringUtils.isEmpty(text)) { - editor.getSelectionModel().selectWordAtCaret(true) - text = editor.getSelectionModel().getSelectedText() - } - assert Objects.nonNull(text) - - String newText - if (text == text.toLowerCase() && text.contains(UNDERSCORE)) { - // snake_case to SNAKE_CASE - newText = text.toUpperCase() - } else if (text == text.toUpperCase() && text.contains(UNDERSCORE)) { - // SNAKE_CASE to SnakeCase - newText = toCamelCase(text.toLowerCase()) - } else if (text != text.toUpperCase() - && text.substring(0, 1) == text.substring(0, 1).toUpperCase() - && !text.contains(UNDERSCORE)) { - // CamelCase to camelCase - newText = text.substring(0, 1).toLowerCase() + text.substring(1, text.length()) - } else { - // camelCase to snake_case - newText = toSnakeCase(text) - } - - ApplicationManager.getApplication().runWriteAction({ - CommandProcessor.getInstance().executeCommand( - project, - { - new WriteAction() { - @Override - protected void run(@NotNull Result result) throws Throwable { - int start = editor.getSelectionModel().getSelectionStart() - EditorModificationUtil.insertStringAtCaret(editor, newText) - editor.getSelectionModel().setSelection(start, start + newText.length()) - } - }.execute().throwException() - } as Runnable, - "CamelCase", - ActionGroup.EMPTY_GROUP - ) - } as Runnable) - } - } - } - - /** - * camelCase to snake_case - * @param text camelCase - * @return snake_case - */ - private static String toSnakeCase(String text) { - def result = new StringBuilder().append(Character.toLowerCase(text.charAt(0))) - IntStream.range(1, text.length()).forEach({ i -> - char c = text.charAt(i) - if (Character.isUpperCase(c)) { - result.append(UNDERSCORE) - .append(Character.toLowerCase(c)) - } else { - result.append(c) - } - }) - - result.toString() - } - - /** - * SNAKE_CASE to SnakeCase - * @param text SNAKE_CASE - * @return SnakeCase - */ - private static String toCamelCase(String text) { - def result = new StringBuilder() - Arrays.stream(text.split(UNDERSCORE)).forEach({ token -> - if (token.length() >= 1) { - result.append(token.substring(0, 1).toUpperCase()) - .append(token.substring(1, token.length())) - } else { - result.append(UNDERSCORE) - } - }) - - result.toString() - } -} diff --git a/src/main/groovy/com/github/passerr/idea/plugins/mybatis/LogConstants.groovy b/src/main/groovy/com/github/passerr/idea/plugins/mybatis/LogConstants.groovy deleted file mode 100644 index 0c245df..0000000 --- a/src/main/groovy/com/github/passerr/idea/plugins/mybatis/LogConstants.groovy +++ /dev/null @@ -1,58 +0,0 @@ -package com.github.passerr.idea.plugins.mybatis - -/** - * 日志常量 - * @author xiehai1 - * @date 2018/11/08 13:12 - * @Copyright ( c ) gome inc Gome Co.,LTD - */ -interface LogConstants { - /** - * sql语句前缀 - */ - String PREFIX_SQL = "Preparing: " - /** - * 参数占位符 - */ - String PARAM_PLACEHOLDER = "\\?" - /** - * 换行符 - */ - String BREAK_LINE = "\n" - /** - * 无sql参数前缀 - */ - String PREFIX_PARAMS_WITHOUT_SPACE = "Parameters:" - /** - * sql参数前缀 - */ - String PREFIX_PARAMS = PREFIX_PARAMS_WITHOUT_SPACE + SPACE - /** - * 参数值分隔符 - */ - String PARAM_SEPARATOR = ", " - /** - * 左括号 - */ - String LEFT_BRACKET = "(" - /** - * 右括号 - */ - String RIGHT_BRACKET = ")" - /** - * 空字符串 - */ - String EMPTY = "" - /** - * null值 - */ - String NULL = "null" - /** - * 空格 - */ - String SPACE = " " - /** - * 不需要加单引号的类型 - */ - List NON_QUOTED_TYPES = Arrays.asList("Integer", "Long", "Double", "Float", "Boolean") -} \ No newline at end of file diff --git a/src/main/groovy/com/github/passerr/idea/plugins/mybatis/LogParser.groovy b/src/main/groovy/com/github/passerr/idea/plugins/mybatis/LogParser.groovy deleted file mode 100644 index 0f98cb7..0000000 --- a/src/main/groovy/com/github/passerr/idea/plugins/mybatis/LogParser.groovy +++ /dev/null @@ -1,115 +0,0 @@ -package com.github.passerr.idea.plugins.mybatis - -import com.github.passerr.idea.plugins.NotificationThread -import com.intellij.notification.Notification -import com.intellij.notification.NotificationType - -import static com.github.passerr.idea.plugins.mybatis.LogConstants.* - -/** - * mybatis日志解析器 - * @author xiehai1 - * @date 2018/11/08 11:49 - * @Copyright ( c ) gome inc Gome Co.,LTD - */ -class LogParser { - private LogParser() { - - } - /** - * 日志解析为可执行的sql语句 - * @param log 日志内容 - * @return sql - */ - static String toSql(String log) { - String sqlLine = null, valueLine = null - for (String line : log.split(BREAK_LINE)) { - if (line.contains(PREFIX_SQL)) { - sqlLine = line - } else if (line.contains(PREFIX_PARAMS)) { - valueLine = line - } else if (line.contains(PREFIX_PARAMS_WITHOUT_SPACE)) { - // 没有参数的sql 自动补齐一个空格 - valueLine = line + SPACE - } - } - - // 是否找到包含sql及参数的日志 - if (Objects.isNull(sqlLine)) { - // 提示信息 - new NotificationThread( - new Notification( - "Copy As Executable Sql", - "Copy As Executable Sql", - "selected log without \"Preparing:\" line, nothing will send to clipboard!", - NotificationType.WARNING - ) - ).start() - - return EMPTY - } else if (Objects.isNull(valueLine)) { - // 提示信息 - new NotificationThread( - new Notification( - "Copy As Executable Sql", - "Copy As Executable Sql", - "selected log without \"Parameters:\" line, nothing will send to clipboard!", - NotificationType.WARNING - ) - ).start() - - return EMPTY - } - - // 带占位符的sql - int sqlPrefixIndex = sqlLine.indexOf(PREFIX_SQL) - String originSql = sqlLine.substring(sqlPrefixIndex + PREFIX_SQL.length(), sqlLine.length()) - // 参数列表 - int paramPrefixIndex = valueLine.indexOf(PREFIX_PARAMS) - String paramValues = valueLine.substring(paramPrefixIndex + PREFIX_PARAMS.length(), valueLine.length()) - - List originSqlSections = originSql.split(PARAM_PLACEHOLDER) - List paramValuesSections = paramValues.split(PARAM_SEPARATOR) - int i = 0 - StringBuilder sb = new StringBuilder() - while (originSqlSections.size() > i && paramValuesSections.size() > i) { - sb.append(originSqlSections.get(i)) - sb.append(parseParam(paramValuesSections.get(i))) - i++ - } - - while (originSqlSections.size() > i) { - sb.append(originSqlSections.get(i)) - i++ - } - - sb.toString() - } - - /** - * 解析参数值 - * @param paramValue 参数值字符串 - * @return 参数值 - */ - private static String parseParam(String paramValue) { - // 如果是空字符串直接返回 - if (paramValue.size() == 0) { - return EMPTY - } - - // 如果是null 直接返回null - if (paramValue.trim() == NULL) { - return NULL - } - - // 括号的索引 - int lastLeftBracketIndex = paramValue.lastIndexOf(LEFT_BRACKET) - int lastRightBracketIndex = paramValue.lastIndexOf(RIGHT_BRACKET) - // 参数值 - String param = paramValue.substring(0, lastLeftBracketIndex) - // 参数类型 - String type = paramValue.substring(lastLeftBracketIndex + 1, lastRightBracketIndex) - - return NON_QUOTED_TYPES.contains(type) ? param : String.format("'%s'", param) - } -} diff --git a/src/main/groovy/com/github/passerr/idea/plugins/mybatis/MybatisLog2SqlAction.groovy b/src/main/groovy/com/github/passerr/idea/plugins/mybatis/MybatisLog2SqlAction.groovy deleted file mode 100644 index 5d1f764..0000000 --- a/src/main/groovy/com/github/passerr/idea/plugins/mybatis/MybatisLog2SqlAction.groovy +++ /dev/null @@ -1,30 +0,0 @@ -package com.github.passerr.idea.plugins.mybatis - - -import com.intellij.openapi.actionSystem.AnAction -import com.intellij.openapi.actionSystem.AnActionEvent -import com.intellij.openapi.actionSystem.PlatformDataKeys -import com.intellij.openapi.ide.CopyPasteManager -import com.intellij.util.ui.TextTransferable -import org.apache.commons.lang.StringUtils -/** - * mybatis日志转可执行sql - * @date 2018/11/08 11:47 - * @Copyright (c) gome inc Gome Co.,LTD - * @author xiehai1 - */ -class MybatisLog2SqlAction extends AnAction { - @Override - void actionPerformed(AnActionEvent e) { - // 选中的日志内容 - def log = e.getData(PlatformDataKeys.EDITOR).getSelectionModel().getSelectedText() - if (StringUtils.isNotEmpty(log)) { - // 格式化后的sql - def sql = LogParser.toSql(log) - if (StringUtils.isNotEmpty(sql)) { - // 自动发送到剪贴板 - CopyPasteManager.getInstance().setContents(new TextTransferable(SqlFormatter.format(sql))) - } - } - } -} diff --git a/src/main/groovy/com/github/passerr/idea/plugins/mybatis/SqlFormatter.groovy b/src/main/groovy/com/github/passerr/idea/plugins/mybatis/SqlFormatter.groovy deleted file mode 100644 index 85b3588..0000000 --- a/src/main/groovy/com/github/passerr/idea/plugins/mybatis/SqlFormatter.groovy +++ /dev/null @@ -1,361 +0,0 @@ -package com.github.passerr.idea.plugins.mybatis -/* - * Original class is here: - * https://github.com/hibernate/hibernate-orm/blob/a30635f14ae272fd63a653f9a9e1a9aeb390fad4/hibernate-core/src/main - * /java/org/hibernate/engine/jdbc/internal/BasicFormatterImpl.java - * - * it imported org.hibernate.internal.util.StringHelper, but it doesn't really need that. - */ - -//import org.hibernate.internal.util.StringHelper; - -/** - * Performs formatting of basic SQL statements (DML + query). - * - * @author Gavin King - * @author Steve Ebersole - */ -class SqlFormatter { - - // MOD: from org.hibernate.internal.util.StringHelper - private static final String WHITESPACE = " \n\r\f\t" - - private static final Set BEGIN_CLAUSES = new HashSet() - private static final Set END_CLAUSES = new HashSet() - private static final Set LOGICAL = new HashSet() - private static final Set QUANTIFIERS = new HashSet() - private static final Set DML = new HashSet() - private static final Set MISC = new HashSet() - - static { - BEGIN_CLAUSES.add("left") - BEGIN_CLAUSES.add("right") - BEGIN_CLAUSES.add("inner") - BEGIN_CLAUSES.add("outer") - BEGIN_CLAUSES.add("group") - BEGIN_CLAUSES.add("order") - - END_CLAUSES.add("where") - END_CLAUSES.add("set") - END_CLAUSES.add("having") - END_CLAUSES.add("join") - END_CLAUSES.add("from") - END_CLAUSES.add("by") - END_CLAUSES.add("join") - END_CLAUSES.add("into") - END_CLAUSES.add("union") - - LOGICAL.add("and") - LOGICAL.add("or") - LOGICAL.add("when") - LOGICAL.add("else") - LOGICAL.add("end") - - QUANTIFIERS.add("in") - QUANTIFIERS.add("all") - QUANTIFIERS.add("exists") - QUANTIFIERS.add("some") - QUANTIFIERS.add("any") - - DML.add("insert") - DML.add("update") - DML.add("delete") - - MISC.add("select") - MISC.add("on") - } - - private static final String INDENT_STRING = " " - // MOD: Make initial indent zero - private static final String INITIAL = /*System.lineSeparator() + INDENT_STRING*/ "" - - static String format(String source) { - return new FormatProcess(source).perform() - } - - private static class FormatProcess { - boolean beginLine = true - boolean afterBeginBeforeEnd - boolean afterByOrSetOrFromOrSelect - boolean afterValues - boolean afterOn - boolean afterBetween - boolean afterInsert - int inFunction - int parensSinceSelect - private LinkedList parenCounts = new LinkedList() - private LinkedList afterByOrFromOrSelects = new LinkedList() - - // MOD: Make BOL indent zero - int indent = /*1*/ 0 - - StringBuilder result = new StringBuilder() - StringTokenizer tokens - String lastToken - String token - String lcToken - - FormatProcess(String sql) { - tokens = new StringTokenizer( - sql, - "()+*/-=<>'`\"[]," + /*StringHelper.*/WHITESPACE, - true - ) - } - - String perform() { - - result.append(INITIAL) - - while (tokens.hasMoreTokens()) { - token = tokens.nextToken() - lcToken = token.toLowerCase(Locale.ROOT) - - if ("'" == token) { - String t - // cannot handle single quotes - while ("'" != t && tokens.hasMoreTokens()){ - t = tokens.nextToken() - token += t - } - } else if ("\"" == token) { - String t - while ("\"" != t && tokens.hasMoreTokens()){ - t = tokens.nextToken() - token += t - } - } - // SQL Server uses "[" and "]" to escape reserved words - // see SQLServerDialect.openQuote and SQLServerDialect.closeQuote - else if ("[" == token) { - String t - while ("]" != t && tokens.hasMoreTokens()){ - t = tokens.nextToken() - token += t - } - } - - if (afterByOrSetOrFromOrSelect && "," == token) { - commaAfterByOrFromOrSelect() - } else if (afterOn && "," == token) { - commaAfterOn() - } else if ("(" == token) { - openParen() - } else if (")" == token) { - closeParen() - } else if (BEGIN_CLAUSES.contains(lcToken)) { - beginNewClause() - } else if (END_CLAUSES.contains(lcToken)) { - endNewClause() - } else if ("select" == lcToken) { - select() - } else if (DML.contains(lcToken)) { - updateOrInsertOrDelete() - } else if ("values" == lcToken) { - values() - } else if ("on" == lcToken) { - on() - } else if (afterBetween && lcToken == "and") { - misc() - afterBetween = false - } else if (LOGICAL.contains(lcToken)) { - logical() - } else if (isWhitespace(token)) { - white() - } else { - misc() - } - - if (!isWhitespace(token)) { - lastToken = lcToken - } - - } - return result.toString() - } - - private void commaAfterOn() { - out() - indent-- - newline() - afterOn = false - afterByOrSetOrFromOrSelect = true - } - - private void commaAfterByOrFromOrSelect() { - out() - newline() - } - - private void logical() { - if ("end" == lcToken) { - indent-- - } - newline() - out() - beginLine = false - } - - private void on() { - indent++ - afterOn = true - newline() - out() - beginLine = false - } - - private void misc() { - out() - if ("between" == lcToken) { - afterBetween = true - } - if (afterInsert) { - newline() - afterInsert = false - } else { - beginLine = false - if ("case" == lcToken) { - indent++ - } - } - } - - private void white() { - if (!beginLine) { - result.append(" ") - } - } - - private void updateOrInsertOrDelete() { - out() - indent++ - beginLine = false - if ("update" == lcToken) { - newline() - } - if ("insert" == lcToken) { - afterInsert = true - } - } - - private void select() { - out() - indent++ - newline() - parenCounts.addLast(parensSinceSelect) - afterByOrFromOrSelects.addLast(afterByOrSetOrFromOrSelect) - parensSinceSelect = 0 - afterByOrSetOrFromOrSelect = true - } - - private void out() { - result.append(token) - } - - private void endNewClause() { - if (!afterBeginBeforeEnd) { - indent-- - if (afterOn) { - indent-- - afterOn = false - } - newline() - } - out() - if ("union" != lcToken) { - indent++ - } - newline() - afterBeginBeforeEnd = false - afterByOrSetOrFromOrSelect = "by" == lcToken || "set" == lcToken || "from" == lcToken - } - - private void beginNewClause() { - if (!afterBeginBeforeEnd) { - if (afterOn) { - indent-- - afterOn = false - } - indent-- - newline() - } - out() - beginLine = false - afterBeginBeforeEnd = true - } - - private void values() { - indent-- - newline() - out() - indent++ - newline() - afterValues = true - } - - private void closeParen() { - parensSinceSelect-- - if (parensSinceSelect < 0) { - indent-- - parensSinceSelect = parenCounts.removeLast() - afterByOrSetOrFromOrSelect = afterByOrFromOrSelects.removeLast() - } - if (inFunction > 0) { - inFunction-- - out() - } else { - if (!afterByOrSetOrFromOrSelect) { - indent-- - newline() - } - out() - } - beginLine = false - } - - private void openParen() { - if (isFunctionName(lastToken) || inFunction > 0) { - inFunction++ - } - beginLine = false - if (inFunction > 0) { - out() - } else { - out() - if (!afterByOrSetOrFromOrSelect) { - indent++ - newline() - beginLine = true - } - } - parensSinceSelect++ - } - - private static boolean isFunctionName(String tok) { - if (tok == null || tok.length() == 0) { - return false - } - - final char begin = tok.charAt(0) - final boolean isIdentifier = Character.isJavaIdentifierStart(begin) || '"' as char == begin - return isIdentifier && - !LOGICAL.contains(tok) && - !END_CLAUSES.contains(tok) && - !QUANTIFIERS.contains(tok) && - !DML.contains(tok) && - !MISC.contains(tok) - } - - private static boolean isWhitespace(String token) { - return /*StringHelper.*/ WHITESPACE.contains(token) - } - - private void newline() { - result.append(System.lineSeparator()) - for (int i = 0; i < indent; i++) { - result.append(INDENT_STRING) - } - beginLine = true - } - } -} \ No newline at end of file diff --git a/src/main/groovy/com/github/passerr/idea/plugins/spring/web/ApiDocConfigView.groovy b/src/main/groovy/com/github/passerr/idea/plugins/spring/web/ApiDocConfigView.groovy deleted file mode 100644 index d3f52cc..0000000 --- a/src/main/groovy/com/github/passerr/idea/plugins/spring/web/ApiDocConfigView.groovy +++ /dev/null @@ -1,101 +0,0 @@ -package com.github.passerr.idea.plugins.spring.web - -import com.github.passerr.idea.plugins.BaseTableModel -import com.github.passerr.idea.plugins.spring.web.po.ApiDocSettingPo -import com.intellij.openapi.util.Pair -import com.intellij.ui.PanelWithButtons -import com.intellij.ui.ToolbarDecorator -import com.intellij.ui.table.JBTable - -import javax.swing.* -import java.awt.* - -/** - * @date 2021/06/30 17:22 - * @Copyright (c) wisewe co.,ltd - * @author xiehai - */ -class ApiDocConfigView { - private static JPanel queryParamPanel(ApiDocSettingPo setting) { - JPanel panel = new JPanel(new BorderLayout()) - PanelWithButtons top = new IdeaPanelWithButtons() { - @Override - protected String getLabelText() { - "忽略类型" - } - - @Override - protected JButton[] createButtons() { - new JButton[0] - } - - @Override - protected JComponent createMainComponent() { - def model = new BaseTableModel(["类型"], setting.queryParamIgnoreTypes) - JBTable table = new JBTable(model) - table.with { - getEmptyText().setText("暂无数据") - getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION) - } - - ToolbarDecorator.createDecorator(table) - .setAddAction({ it -> }) - .setAddActionName("新增") - .setEditAction({ it -> }) - .setEditActionName("编辑") - .setRemoveAction({ it -> model.removeRow(table.getSelectedRow()) }) - .setRemoveActionName("删除") - .disableUpDownActions() - .createPanel() - } - } - PanelWithButtons bottom = new IdeaPanelWithButtons() { - @Override - protected String getLabelText() { - "忽略注解" - } - - @Override - protected JButton[] createButtons() { - new JButton[0] - } - - @Override - protected JComponent createMainComponent() { - def model = new BaseTableModel(["注解"], setting.queryParamIgnoreAnnotations) - JBTable table = new JBTable(model) - table.with { - getEmptyText().setText("暂无数据") - getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION) - } - - ToolbarDecorator.createDecorator(table) - .setAddAction({ it -> }) - .setAddActionName("新增") - .setEditAction({ it -> }) - .setEditActionName("编辑") - .setRemoveAction({ it -> model.removeRow(table.getSelectedRow()) }) - .setRemoveActionName("删除") - .disableUpDownActions() - .createPanel() - } - } - panel.add(top, BorderLayout.NORTH) - panel.add(bottom, BorderLayout.CENTER) - panel - } - - static java.util.List> panels(ApiDocSettingPo setting) { - [ - Pair. pair("Api模版", new JPanel()), - Pair. pair("查询参数", queryParamPanel(setting)) - ] - } - - abstract class IdeaPanelWithButtons extends PanelWithButtons { - IdeaPanelWithButtons() { - super() - super.initPanel() - } - } -} diff --git a/src/main/groovy/com/github/passerr/idea/plugins/spring/web/ApiDocConfigurable.groovy b/src/main/groovy/com/github/passerr/idea/plugins/spring/web/ApiDocConfigurable.groovy deleted file mode 100644 index fc3af9c..0000000 --- a/src/main/groovy/com/github/passerr/idea/plugins/spring/web/ApiDocConfigurable.groovy +++ /dev/null @@ -1,91 +0,0 @@ -package com.github.passerr.idea.plugins.spring.web - - -import com.github.passerr.idea.plugins.spring.web.po.ApiDocSettingPo -import com.intellij.openapi.Disposable -import com.intellij.openapi.options.Configurable -import com.intellij.openapi.options.ConfigurationException -import com.intellij.openapi.util.Disposer -import com.intellij.ui.TabbedPaneWrapper -import com.intellij.ui.tabs.JBTabs - -import javax.swing.* -import javax.swing.event.ChangeEvent -import javax.swing.event.ChangeListener -import java.awt.* - -/** - * @date 2021/06/29 10:23 - * @Copyright (c) wisewe co.,ltd - * @author xiehai - */ -class ApiDocConfigurable implements Configurable { - JPanel mainPanel - Disposable disposable - ApiDocSettingPo source - ApiDocSettingPo copy - - ApiDocConfigurable() { - this.source = ApiDocStateComponent.getInstance().getState() - this.copy = ApiDocSettingPo.deepCopy(this.source) - } - - @Override - String getDisplayName() { - "Api Doc Setting" - } - - @Override - String getHelpTopic() { - "doc" - } - - @Override - JComponent createComponent() { - this.disposable = Disposer.newDisposable() - def tabbedPanel = new TabbedPaneWrapper(disposable) - def panels = ApiDocConfigView.panels(this.copy) - panels.each { it -> tabbedPanel.addTab(it.first, it.second) } - - tabbedPanel.addChangeListener(new ChangeListener() { - @Override - void stateChanged(ChangeEvent e) { - ApiDocConfigurable.this.with { - if (isModified()) { - apply() - } - panels.find { p -> p.first == ((JBTabs) e.getSource()).getSelectedInfo().getText() }.second.repaint() - } - } - }) - - - this.mainPanel = new JPanel(new BorderLayout()) - this.mainPanel.add(tabbedPanel.getComponent(), BorderLayout.NORTH) - - return this.mainPanel - } - - @Override - boolean isModified() { - this.source != this.copy - } - - @Override - void apply() throws ConfigurationException { - this.source.update(this.copy) - } - - @Override - void reset() { - this.copy = ApiDocSettingPo.deepCopy(this.source) - } - - @Override - void disposeUIResources() { - if (this.disposable) { - Disposer.dispose(this.disposable) - this.disposable = null - } - } -} diff --git a/src/main/groovy/com/github/passerr/idea/plugins/spring/web/ApiDocStateComponent.groovy b/src/main/groovy/com/github/passerr/idea/plugins/spring/web/ApiDocStateComponent.groovy deleted file mode 100644 index 98b6905..0000000 --- a/src/main/groovy/com/github/passerr/idea/plugins/spring/web/ApiDocStateComponent.groovy +++ /dev/null @@ -1,45 +0,0 @@ -package com.github.passerr.idea.plugins.spring.web - -import com.github.passerr.idea.plugins.spring.web.po.ApiDocSettingPo -import com.intellij.openapi.components.PersistentStateComponent -import com.intellij.openapi.components.ServiceManager -import com.intellij.openapi.components.State -import com.intellij.openapi.components.Storage -import com.intellij.util.xmlb.XmlSerializerUtil - -/** - * api文档设置 - * @date 2021/06/29 09:01 - * @Copyright (c) wisewe co.,ltd - * @author xiehai - */ -@State( - name = "com.github.passerr.idea.plugins.spring.web.ApiDocStateComponent", - storages = @Storage("com.github.passerr.idea.plugins.spring.web.ApiDocStateComponent.xml") -) -class ApiDocStateComponent implements PersistentStateComponent { - private ApiDocSettingPo apiDocSettingPo = new ApiDocSettingPo() - - @Override - ApiDocSettingPo getState() { - return this.apiDocSettingPo - } - - @Override - void loadState(ApiDocSettingPo state) { - XmlSerializerUtil.copyBean(state, this.apiDocSettingPo) - } - - /** - * 别名获取 - * @param type 类型全称 - * @return 别名 - */ - String alias(String type) { - this.apiDocSettingPo.aliases.find { it -> it.type == type }.alias ?: WebCopyConstants.DEFAULT_ALIAS - } - - static ApiDocStateComponent getInstance() { - ServiceManager.getService(ApiDocStateComponent) - } -} diff --git a/src/main/groovy/com/github/passerr/idea/plugins/spring/web/BaseWebCopyAction.groovy b/src/main/groovy/com/github/passerr/idea/plugins/spring/web/BaseWebCopyAction.groovy deleted file mode 100644 index a053194..0000000 --- a/src/main/groovy/com/github/passerr/idea/plugins/spring/web/BaseWebCopyAction.groovy +++ /dev/null @@ -1,71 +0,0 @@ -package com.github.passerr.idea.plugins.spring.web - - -import com.intellij.codeInsight.AnnotationUtil -import com.intellij.openapi.actionSystem.AnAction -import com.intellij.openapi.actionSystem.AnActionEvent -import com.intellij.openapi.actionSystem.CommonDataKeys -import com.intellij.openapi.actionSystem.DataContext -import com.intellij.psi.PsiAnnotation -import com.intellij.psi.PsiMethod - -/** - * spring web 复制基类 - * @date 2021/06/28 11:40 - * @Copyright (c) wisewe co.,ltd - * @author xiehai - */ -abstract class BaseWebCopyAction extends AnAction { - protected static final String REQUEST_MAPPING = "org.springframework.web.bind.annotation.RequestMapping" - protected static final Map MAPPINGS = [ - "org.springframework.web.bind.annotation.GetMapping" : "GET", - "org.springframework.web.bind.annotation.PostMapping" : "POST", - "org.springframework.web.bind.annotation.PutMapping" : "PUT", - "org.springframework.web.bind.annotation.DeleteMapping": "DELETE", - "org.springframework.web.bind.annotation.PatchMapping" : "PATCH" - ] - - static { - // requestMapping - MAPPINGS.put(REQUEST_MAPPING, null) - } - - @Override - void update(AnActionEvent e) { - DataContext dataContext = e.getDataContext() - def data = CommonDataKeys.PSI_ELEMENT.getData(dataContext) - if (data instanceof PsiMethod) { - e.getPresentation() - .setEnabled( - AnnotationUtil.findAnnotations(data, MAPPINGS.keySet()).length > 0 - ) - } else { - // 方法上未找到注解 - e.getPresentation().setEnabled(false) - } - - } - - protected static PsiMethod method(AnActionEvent e) { - DataContext dataContext = e.getDataContext() - // 肯定是PsiMethod - CommonDataKeys.PSI_ELEMENT.getData(dataContext) as PsiMethod - } - - protected static PsiAnnotation classAnnotation(PsiMethod method) { - AnnotationUtil.findAnnotation(method.getContainingClass(), REQUEST_MAPPING) - } - - protected static PsiAnnotation methodAnnotation(PsiMethod method) { - AnnotationUtil.findAnnotations(method, MAPPINGS.keySet())[0] - } - - protected static String url(PsiAnnotation classAnnotation, PsiAnnotation methodAnnotation) { - // RequestMapping前缀 - String prefix = PsiAnnotationMemberValueUtil.getArrayFirstValue(classAnnotation, "value") ?: "" - // 肯定存在一个注解满足条件 - String suffix = PsiAnnotationMemberValueUtil.getArrayFirstValue(methodAnnotation, "value") ?: "" - - prefix + suffix - } -} diff --git a/src/main/groovy/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.groovy b/src/main/groovy/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.groovy deleted file mode 100644 index a4d1113..0000000 --- a/src/main/groovy/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.groovy +++ /dev/null @@ -1,124 +0,0 @@ -package com.github.passerr.idea.plugins.spring.web - - -import com.intellij.codeInsight.AnnotationUtil -import com.intellij.openapi.actionSystem.AnActionEvent -import com.intellij.psi.PsiAnnotation -import com.intellij.psi.PsiMethod -import com.intellij.psi.javadoc.PsiDocTag -import com.intellij.psi.javadoc.PsiDocToken - -/** - * web方法接口文档复制 - * @date 2021/06/28 13:34 - * @Copyright (c) wisewe co.,ltd - * @author xiehai - */ -class CopyMethodApiDocAction extends BaseWebCopyAction { - @Override - void actionPerformed(AnActionEvent e) { - PsiMethod method = method(e) - PsiAnnotation classAnnotation = classAnnotation(method) - PsiAnnotation methodAnnotation = methodAnnotation(method) - String url = url(classAnnotation, methodAnnotation) - String httpMethod = getMethod(classAnnotation, methodAnnotation) - List pathVariables = pathVariables(method) - // 路径参数列表 - // 查询参数列表 - // body - // 请求示例 - // 应答示例 - } - - /** - * 获取http方法 - * @param classAnnotation 类上的注解 - * @param methodAnnotation 方法上的注解 - * @return http方法类型 - */ - private static String getMethod(PsiAnnotation classAnnotation, PsiAnnotation methodAnnotation) { - if (REQUEST_MAPPING != methodAnnotation.getQualifiedName()) { - return MAPPINGS[methodAnnotation.qualifiedName] - } - - Object methodOnMethod = PsiAnnotationMemberValueUtil.getArrayFirstValue(methodAnnotation, "method") - if (methodOnMethod) { - return methodOnMethod as String - } - - Object methodOnClass = PsiAnnotationMemberValueUtil.getArrayFirstValue(classAnnotation, "method") - if (methodOnClass) { - return methodOnClass as String - } - - return "UNKNOWN" - } - - /** - * 获取接口路径参数 - * @param method {@link PsiMethod} - * @return 路径参数列表 - */ - private static List pathVariables(PsiMethod method) { - Map comments = [:] - if (method.getDocComment()) { - // 方法注释tag列表 - method.getDocComment().getTags() - .findAll { it -> it instanceof PsiDocTag && it.getName() == "param" } - .collect { it -> it as PsiDocTag } - .each { it -> - comments.put( - it.getName(), - it.getDataElements() - .findAll { e -> - e instanceof PsiDocToken && e.getTokenType().toString() == "DOC_COMMENT_DATA" - } - .collect { e -> (e as PsiDocToken).getText().trim() } - .join("")) - } - } - - method.getParameterList() - .getParameters() - .collect { it -> - PsiAnnotation annotation = AnnotationUtil.findAnnotation(it, WebCopyConstants.PATH_VARIABLE_ANNOTATION) - if (annotation) { - String type = it.getType().getCanonicalText() - return new Var( - name: PsiAnnotationMemberValueUtil.value(annotation, "value") ?: it.getText(), - type: type, - alias: ApiDocStateComponent.getInstance().alias(type), - desc: comments.getOrDefault(it.getText(), it.getText()) - ) - } - - return null - }.findAll { it -> it != null } - } - - private static List queryParams(PsiMethod method) { - null - } - - /** - * 参数实体 - */ - private static class Var { - /** - * 参数名 - */ - String name - /** - * 类型 - */ - String type - /** - * 类型别名 - */ - String alias - /** - * 参数描述 - */ - String desc - } -} diff --git a/src/main/groovy/com/github/passerr/idea/plugins/spring/web/po/ApiDocAliasPairPo.groovy b/src/main/groovy/com/github/passerr/idea/plugins/spring/web/po/ApiDocAliasPairPo.groovy deleted file mode 100644 index b5f9148..0000000 --- a/src/main/groovy/com/github/passerr/idea/plugins/spring/web/po/ApiDocAliasPairPo.groovy +++ /dev/null @@ -1,37 +0,0 @@ -package com.github.passerr.idea.plugins.spring.web.po -/** - * 别名对po - * @date 2021/06/29 15:37 - * @Copyright (c) wisewe co.,ltd - * @author xiehai - */ -class ApiDocAliasPairPo { - String type - String alias - - static ApiDocAliasPairPo deepCopy(ApiDocAliasPairPo source) { - new ApiDocAliasPairPo( - type: source.type, - alias: source.alias - ) - } - - boolean equals(o) { - if (this.is(o)) return true - if (getClass() != o.class) return false - - ApiDocAliasPairPo that = (ApiDocAliasPairPo) o - - if (alias != that.alias) return false - if (type != that.type) return false - - return true - } - - int hashCode() { - int result - result = (type != null ? type.hashCode() : 0) - result = 31 * result + (alias != null ? alias.hashCode() : 0) - return result - } -} diff --git a/src/main/groovy/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.groovy b/src/main/groovy/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.groovy deleted file mode 100644 index acfe22a..0000000 --- a/src/main/groovy/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.groovy +++ /dev/null @@ -1,80 +0,0 @@ -package com.github.passerr.idea.plugins.spring.web.po - -import com.github.passerr.idea.plugins.spring.web.WebCopyConstants -import com.intellij.util.xmlb.annotations.AbstractCollection -import com.intellij.util.xmlb.annotations.Tag -/** - * api文档设置持久化po - * @date 2021/06/29 15:31 - * @Copyright (c) wisewe co.,ltd - * @author xiehai - */ -class ApiDocSettingPo { - @Tag("template") - String template - @Tag("all-ignore-types") - @AbstractCollection - List allIgnoreTypes - @Tag("query-param-ignore-types") - @AbstractCollection - List queryParamIgnoreTypes - @Tag("query-param-ignore-annotations") - @AbstractCollection - List queryParamIgnoreAnnotations - @Tag("aliases") - @AbstractCollection(elementTypes = ApiDocAliasPairPo.class) - List aliases - - ApiDocSettingPo(){ - this.template = WebCopyConstants.DEFAULT_TEMPLATE - this.allIgnoreTypes = WebCopyConstants.ALL_IGNORE_TYPES - this.queryParamIgnoreTypes = WebCopyConstants.QUERY_PARAM_IGNORE_TYPES - this.queryParamIgnoreAnnotations = WebCopyConstants.QUERY_PARAM_IGNORE_ANNOTATIONS - this.aliases = WebCopyConstants.DEFAULT_ALIAS_MAPPINGS.collect { key, value -> - new ApiDocAliasPairPo(type: key, alias: value) - } - } - - static ApiDocSettingPo deepCopy(ApiDocSettingPo source) { - new ApiDocSettingPo( - template: source.template, - allIgnoreTypes: new ArrayList(source.allIgnoreTypes), - queryParamIgnoreTypes: new ArrayList(source.queryParamIgnoreTypes), - queryParamIgnoreAnnotations: new ArrayList(source.queryParamIgnoreAnnotations), - aliases: source.aliases.collect { it -> ApiDocAliasPairPo.deepCopy(it) } - ) - } - - void update(ApiDocSettingPo target) { - this.template = target.template - this.allIgnoreTypes = target.allIgnoreTypes - this.queryParamIgnoreTypes = target.queryParamIgnoreTypes - this.queryParamIgnoreAnnotations = target.queryParamIgnoreAnnotations - this.aliases = target.aliases - } - - boolean equals(o) { - if (this.is(o)) return true - if (getClass() != o.class) return false - - ApiDocSettingPo that = (ApiDocSettingPo) o - - if (aliases != that.aliases) return false - if (allIgnoreTypes != that.allIgnoreTypes) return false - if (queryParamIgnoreAnnotations != that.queryParamIgnoreAnnotations) return false - if (queryParamIgnoreTypes != that.queryParamIgnoreTypes) return false - if (template != that.template) return false - - return true - } - - int hashCode() { - int result - result = (template != null ? template.hashCode() : 0) - result = 31 * result + (allIgnoreTypes != null ? allIgnoreTypes.hashCode() : 0) - result = 31 * result + (queryParamIgnoreTypes != null ? queryParamIgnoreTypes.hashCode() : 0) - result = 31 * result + (queryParamIgnoreAnnotations != null ? queryParamIgnoreAnnotations.hashCode() : 0) - result = 31 * result + (aliases != null ? aliases.hashCode() : 0) - return result - } -} diff --git a/src/main/groovy/com/github/passerr/idea/plugins/tool/ConvertType.groovy b/src/main/groovy/com/github/passerr/idea/plugins/tool/ConvertType.groovy deleted file mode 100644 index d4c5160..0000000 --- a/src/main/groovy/com/github/passerr/idea/plugins/tool/ConvertType.groovy +++ /dev/null @@ -1,111 +0,0 @@ -package com.github.passerr.idea.plugins.tool - -import com.github.passerr.idea.plugins.NotificationThread -import com.github.passerr.idea.plugins.mybatis.LogParser -import com.github.passerr.idea.plugins.mybatis.SqlFormatter -import com.google.gson.Gson -import com.google.gson.GsonBuilder -import com.google.gson.JsonElement -import com.google.gson.JsonParser -import com.google.gson.JsonSyntaxException -import com.intellij.notification.Notification -import org.apache.commons.lang.StringUtils -import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea -import org.fife.ui.rsyntaxtextarea.SyntaxConstants -import org.fife.ui.rtextarea.RTextArea -import org.fife.ui.rtextarea.ToolTipSupplier - -import java.awt.* -import java.awt.event.MouseEvent -import java.util.regex.Pattern - -import static com.intellij.notification.NotificationType.ERROR - -/** - * 转换类型 - * @author xiehai1* @date 2018/11/08 18:33 - * @Copyright (c) gome inc Gome Co.,LTD - */ -enum ConvertType { - /** - * 自动格式化json - */ - JSON(SyntaxConstants.SYNTAX_STYLE_JSON){ - @Override - void handle(RSyntaxTextArea input, RSyntaxTextArea output) { - try { - JsonParser jsonParser = new JsonParser() - // 可以同时解析数组或者Object - JsonElement element = jsonParser.parse(input.getText()) - Gson gson = new GsonBuilder().setPrettyPrinting().create() - output.setText(gson.toJson(element)) - // 格式化成功后定位到第一行 - output.setCaretPosition(0) - } catch (JsonSyntaxException ex) { - String msg = ex.getMessage() - def matcher = ERROR_PATTERN.matcher(msg) - if (matcher.find()) { - // 行索引从0开始 - int lineIndex = (matcher.group(1) as int) - 1 - // 设置错误行背景色 - input.addLineHighlight(lineIndex, Color.RED) - // 设置提示信息 - input.setToolTipSupplier({ RTextArea rt, MouseEvent me -> - def offset = input.getLineOfOffset(input.viewToModel(me.getPoint())) - offset == lineIndex ? msg : null - } as ToolTipSupplier) - // 定位到失败行 - input.setCaretPosition(lineIndex) - } - new NotificationThread(new Notification("Json Format", "Json Format", msg, ERROR)).start() - } - } - }, - /** - * mybatis日志转可执行sql - */ - SQL(SyntaxConstants.SYNTAX_STYLE_SQL){ - @Override - void handle(RSyntaxTextArea input, RSyntaxTextArea output) { - def sql = LogParser.toSql(input.getText()) - if (StringUtils.isEmpty(sql)) { - // 设置错误行背景色 - // 默认设置第一行 - input.addLineHighlight(0, Color.RED) - // 设置提示信息 - input.setToolTipSupplier({ RTextArea rt, MouseEvent me -> - def offset = input.getLineOfOffset(input.viewToModel(me.getPoint())) - offset == 0 ? "the log you input which without \"Preparing:\" or \"Parameters:\"" : null - } as ToolTipSupplier) - } else { - output.setText(SqlFormatter.format(sql)) - // 格式化成功后定位到第一行 - output.setCaretPosition(0) - } - } - }, - /** - * 文本 - */ - TEXT(SyntaxConstants.SYNTAX_STYLE_NONE) - - String style - - /** - * json错误信息正则匹配 - */ - private static final ERROR_PATTERN = Pattern.compile("line (\\w+) column") - - ConvertType(String style) { - this.style = style - } - - /** - * 对文本进行格式化 - * @param input 输入文本域 - * @param output 输出文本域 - */ - void handle(RSyntaxTextArea input, RSyntaxTextArea output) { - // 默认不做任何处理 - } -} diff --git a/src/main/groovy/com/github/passerr/idea/plugins/tool/TextFormatView.groovy b/src/main/groovy/com/github/passerr/idea/plugins/tool/TextFormatView.groovy deleted file mode 100644 index 917c254..0000000 --- a/src/main/groovy/com/github/passerr/idea/plugins/tool/TextFormatView.groovy +++ /dev/null @@ -1,224 +0,0 @@ -package com.github.passerr.idea.plugins.tool - -import com.intellij.openapi.project.Project -import org.apache.commons.lang.StringUtils -import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea -import org.fife.ui.rsyntaxtextarea.Theme -import org.fife.ui.rtextarea.RTextScrollPane - -import javax.swing.* -import java.awt.* -import java.awt.event.ActionEvent -import java.awt.event.ActionListener -import java.awt.event.InputEvent -import java.awt.event.ItemEvent -import java.awt.event.ItemListener -import java.awt.event.KeyEvent - -/** - * 格式化视图 - * @author xiehai1* @Copyright tellyes tech. inc. co.,ltd - * @date 2018/10/12 16:02 - */ -class TextFormatView extends JRootPane { - Project project - /** - * 菜单 - */ - ToolMenu menu - /** - * 输入文本域 - */ - RSyntaxTextArea inputTextArea = new RSyntaxTextArea(19, 0) - /** - * 输出文本域 - */ - RSyntaxTextArea outputTextArea = new RSyntaxTextArea(34, 0) - /** - * cache instance by project - */ - private static Map INSTANCES = new HashMap<>() - - private TextFormatView(Project project) { - this.project = project - super.getContentPane().setLayout(new BoxLayout(super.getContentPane(), BoxLayout.Y_AXIS)) - // 初始化输入文本域 - this.doInitInputTextArea() - // 初始化菜单 - this.doInitMenu() - // 初始输出化文本域 - this.doInitOutputTextArea() - // 设置高亮主题 - InputStream inputStream - try { - inputStream = this.getClass().getResourceAsStream("/org/fife/ui/rsyntaxtextarea/themes/idea.xml") - Theme theme = Theme.load(inputStream) - theme.apply(this.inputTextArea) - theme.apply(this.outputTextArea) - } catch (IOException ignore) { - } finally { - inputStream && inputStream.close() - } - } - - /** - * 输入文本域初始化 - */ - private void doInitInputTextArea() { - this.inputTextArea.with { - // 使用快捷键ctrl+enter格式化json - getInputMap(WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, InputEvent.CTRL_MASK), "format") - getActionMap().put("format", new AbstractAction() { - @Override - void actionPerformed(ActionEvent e) { - TextFormatView.this.doFormat() - } - }) - } - super.getContentPane().add(new RTextScrollPane(this.inputTextArea)) - } - - /** - * 输出文本域初始化 - */ - private void doInitOutputTextArea() { - this.outputTextArea.with { - setCodeFoldingEnabled(true) - setAutoIndentEnabled(true) - } - super.getContentPane().add(new RTextScrollPane(this.outputTextArea)) - } - - /** - * 菜单初始化 - */ - private void doInitMenu() { - def subMenuItemListener = new ItemListener() { - @Override - void itemStateChanged(ItemEvent e) { - if (e.getStateChange() == ItemEvent.SELECTED) { - switchMenu(e.getItem() as ToolMenu) - } - } - } - // 编/解码 - JComboBox encodeMenu = new JComboBox<>([ - ToolMenu.URL_ENCODE, - ToolMenu.URL_DECODE - ] as ToolMenu[]) - encodeMenu.addItemListener(subMenuItemListener) - encodeMenu.setVisible(false) - - // 加/解密 - JComboBox encryptMenu = new JComboBox<>([ - ToolMenu.MD5_ENCRYPTION, - ToolMenu.BASE64_ENCRYPTION, - ToolMenu.BASE64_DECRYPTION - ] as ToolMenu[]) - encryptMenu.addItemListener(subMenuItemListener) - encryptMenu.setVisible(false) - - // 主菜单 - JComboBox mainMenu = new JComboBox<>([ - ToolMenu.JSON, - ToolMenu.SQL, - ToolMenu.ENCODE, - ToolMenu.ENCRYPT - ] as ToolMenu[]) - - mainMenu.addItemListener(new ItemListener() { - @Override - void itemStateChanged(ItemEvent e) { - if (e.getStateChange() == ItemEvent.SELECTED) { - ToolMenu toolMenu = e.getItem() as ToolMenu - switch (toolMenu) { - case ToolMenu.ENCODE: - encryptMenu.setVisible(false) - encodeMenu.setVisible(true) - encodeMenu.setSelectedIndex(0) - switchMenu(encodeMenu.getSelectedItem() as ToolMenu) - break - case ToolMenu.ENCRYPT: - encodeMenu.setVisible(false) - encryptMenu.setVisible(true) - encryptMenu.setSelectedIndex(0) - switchMenu(encryptMenu.getSelectedItem() as ToolMenu) - break - default: - encodeMenu.setVisible(false) - encryptMenu.setVisible(false) - // 菜单切换 - switchMenu(toolMenu) - break - } - } - } - }) - // 默认选中第一个 - mainMenu.setSelectedItem(0) - this.switchMenu(mainMenu.getSelectedItem() as ToolMenu) - - // 转换按钮 - JButton format = new JButton("转换") - format.addActionListener(new ActionListener() { - @Override - void actionPerformed(ActionEvent e) { - doFormat() - } - }) - JPanel panel = new JPanel(new FlowLayout(FlowLayout.CENTER)) - panel.add(mainMenu) - panel.add(encodeMenu) - panel.add(encryptMenu) - panel.add(format) - - super.getContentPane().add(panel) - } - - /** - * 菜单切换操作 - * @param convertType 转换类型 - */ - private void switchMenu(ToolMenu menu) { - this.menu = menu - if (this.inputTextArea) { - this.inputTextArea.setToolTipSupplier(null) - this.inputTextArea.removeAllLineHighlights() - menu.type && this.outputTextArea.setSyntaxEditingStyle(menu.type.style) - } - } - - /** - * 格式化动作 - */ - private void doFormat() { - // 文本框为空 不进行格式化 - if (StringUtils.isEmpty(this.inputTextArea.getText())) { - return - } - try { - this.inputTextArea.setToolTipSupplier(null) - this.inputTextArea.removeAllLineHighlights() - this.menu.handle(this.inputTextArea, this.outputTextArea) - } finally { - this.getContentPane().repaint() - } - } - - /** - * 按照{@link Project}单例 - * @param project idea 项目 - * @return {@link TextFormatView} - */ - static TextFormatView getInstance(Project project) { - if (!INSTANCES.containsKey(project)) { - synchronized (TextFormatView.class) { - if (!INSTANCES.containsKey(project)) { - INSTANCES.putIfAbsent(project, new TextFormatView(project)) - } - } - } - - return INSTANCES.get(project) - } -} \ No newline at end of file diff --git a/src/main/groovy/com/github/passerr/idea/plugins/tool/TextHandlerToolWindow.groovy b/src/main/groovy/com/github/passerr/idea/plugins/tool/TextHandlerToolWindow.groovy deleted file mode 100644 index 6df9d12..0000000 --- a/src/main/groovy/com/github/passerr/idea/plugins/tool/TextHandlerToolWindow.groovy +++ /dev/null @@ -1,21 +0,0 @@ -package com.github.passerr.idea.plugins.tool - -import com.intellij.openapi.project.Project -import com.intellij.openapi.wm.ToolWindow -import com.intellij.openapi.wm.ToolWindowFactory -import org.jetbrains.annotations.NotNull - -/** - * 字符串处理窗口 - * @author xiehai1* @date 2018/10/12 15:55 - * @Copyright tellyes tech. inc. co.,ltd - */ -class TextHandlerToolWindow implements ToolWindowFactory { - @Override - void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) { - toolWindow.getContentManager().with { - removeAllContents(true) - addContent(getFactory().createContent(TextFormatView.getInstance(project), "", true)) - } - } -} diff --git a/src/main/groovy/com/github/passerr/idea/plugins/tool/ToolMenu.groovy b/src/main/groovy/com/github/passerr/idea/plugins/tool/ToolMenu.groovy deleted file mode 100644 index 7f921ce..0000000 --- a/src/main/groovy/com/github/passerr/idea/plugins/tool/ToolMenu.groovy +++ /dev/null @@ -1,84 +0,0 @@ -package com.github.passerr.idea.plugins.tool - -import org.apache.commons.codec.digest.DigestUtils -import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea - -import java.nio.charset.StandardCharsets - -/** - * 菜单 - * @Copyright (c)tellyes tech. inc. co.,ltd - * @date 2019/11/27 09:53 - * @author xiehai - */ -enum ToolMenu { - /** - * main menu - */ - JSON("Json格式化", ConvertType.JSON), - SQL("Mybatis日志转Sql", ConvertType.SQL), - ENCODE("编/解码", null), - ENCRYPT("加/解密", null), - - /** - * sub menu - */ - URL_DECODE("url解码", ConvertType.TEXT){ - @Override - void handle(RSyntaxTextArea input, RSyntaxTextArea output) { - output.setText(URLDecoder.decode(input.getText(), StandardCharsets.UTF_8.name())) - } - }, - URL_ENCODE("url编码", ConvertType.TEXT){ - @Override - void handle(RSyntaxTextArea input, RSyntaxTextArea output) { - output.setText(URLEncoder.encode(input.getText(), StandardCharsets.UTF_8.name())) - } - }, - MD5_ENCRYPTION("md5加密", ConvertType.TEXT){ - @Override - void handle(RSyntaxTextArea input, RSyntaxTextArea output) { - output.setText(DigestUtils.md5Hex(input.getText())) - } - }, - BASE64_DECRYPTION("base64解密", ConvertType.TEXT){ - @Override - void handle(RSyntaxTextArea input, RSyntaxTextArea output) { - try { - output.setText(new String(input.getText().decodeBase64())) - } catch (Exception ignore) { - - } - } - }, - BASE64_ENCRYPTION("base64加密", ConvertType.TEXT){ - @Override - void handle(RSyntaxTextArea input, RSyntaxTextArea output) { - output.setText(input.getText().bytes.encodeBase64().toString()) - } - } - - String name - ConvertType type - - ToolMenu(String name, ConvertType type) { - this.name = name - this.type = type - } - - - @Override - String toString() { - return this.name - } - - /** - * 默认文本域操作 - * @param input 输入文本域 - * @param output 输出文本域 - */ - void handle(RSyntaxTextArea input, RSyntaxTextArea output) { - // 默认对应类型格式化 - this.type.handle(input, output) - } -} \ No newline at end of file diff --git a/src/main/java/com/github/passerr/idea/plugins/BaseTableModel.java b/src/main/java/com/github/passerr/idea/plugins/BaseTableModel.java new file mode 100644 index 0000000..dac3d3c --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/BaseTableModel.java @@ -0,0 +1,67 @@ +package com.github.passerr.idea.plugins; + +import com.intellij.util.ui.ItemRemovable; +import lombok.AccessLevel; +import lombok.experimental.FieldDefaults; + +import javax.swing.table.AbstractTableModel; +import java.util.ArrayList; +import java.util.List; + +/** + * @author xiehai + * @date 2021/06/30 18:50 + * @Copyright(c) tellyes tech. inc. co.,ltd + */ +@FieldDefaults(level = AccessLevel.PRIVATE) +public class BaseTableModel extends AbstractTableModel implements ItemRemovable { + final List data; + final List headers; + + BaseTableModel(List headers, List data) { + this.headers = headers; + this.data = data; + } + + @Override + public String getColumnName(int column) { + return this.headers.get(column); + } + + @Override + public void removeRow(int idx) { + if (idx >= 0 && idx < this.getRowCount()) { + this.data.remove(idx); + super.fireTableRowsDeleted(idx, idx); + } + } + + @Override + public int getRowCount() { + return this.data.size(); + } + + @Override + public int getColumnCount() { + return this.columns().isEmpty() ? 1 : this.columns().size(); + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + Object value = this.data.get(rowIndex); + if (this.columns().isEmpty()) { + return value; + } + + // TODO + return null; + } + + /** + * 列属性 + * @return 列 + */ + List columns() { + return new ArrayList<>(); + } +} diff --git a/src/main/java/com/github/passerr/idea/plugins/ColumnIndex.java b/src/main/java/com/github/passerr/idea/plugins/ColumnIndex.java new file mode 100644 index 0000000..a509e03 --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/ColumnIndex.java @@ -0,0 +1,10 @@ +package com.github.passerr.idea.plugins; + +/** + * 列位置 + * @author xiehai + * @date 2021/06/30 18:55 + * @Copyright(c) tellyes tech. inc. co.,ltd + */ +public @interface ColumnIndex { +} diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/BaseWebCopyAction.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/BaseWebCopyAction.java new file mode 100644 index 0000000..521ecac --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/BaseWebCopyAction.java @@ -0,0 +1,75 @@ +package com.github.passerr.idea.plugins.spring.web; + +import com.intellij.codeInsight.AnnotationUtil; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.psi.PsiAnnotation; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiMethod; +import com.intellij.psi.PsiModifierListOwner; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +/** + * @author xiehai + * @date 2021/06/30 18:57 + * @Copyright(c) tellyes tech. inc. co.,ltd + */ +abstract class BaseWebCopyAction extends AnAction { + protected static final String REQUEST_MAPPING = "org.springframework.web.bind.annotation.RequestMapping"; + protected static final Map MAPPINGS = new HashMap<>(); + + static { + MAPPINGS.put("org.springframework.web.bind.annotation.GetMapping", "GET"); + MAPPINGS.put("org.springframework.web.bind.annotation.PostMapping", "POST"); + MAPPINGS.put("org.springframework.web.bind.annotation.PutMapping", "PUT"); + MAPPINGS.put("org.springframework.web.bind.annotation.DeleteMapping", "DELETE"); + MAPPINGS.put("org.springframework.web.bind.annotation.PatchMapping", "PATCH"); + + // requestMapping + MAPPINGS.put(REQUEST_MAPPING, null); + } + + @Override + public void update(AnActionEvent e) { + DataContext dataContext = e.getDataContext(); + PsiElement data = CommonDataKeys.PSI_ELEMENT.getData(dataContext); + if (data instanceof PsiMethod) { + e.getPresentation() + .setEnabled( + AnnotationUtil.findAnnotations((PsiModifierListOwner) data, MAPPINGS.keySet()).length > 0 + ); + } else { + // 方法上未找到注解 + e.getPresentation().setEnabled(false); + } + } + + protected static PsiMethod method(AnActionEvent e) { + DataContext dataContext = e.getDataContext(); + // 肯定是PsiMethod + return (PsiMethod) CommonDataKeys.PSI_ELEMENT.getData(dataContext); + } + + protected static PsiAnnotation classAnnotation(PsiMethod method) { + return AnnotationUtil.findAnnotation(method.getContainingClass(), REQUEST_MAPPING); + } + + protected static PsiAnnotation methodAnnotation(PsiMethod method) { + return AnnotationUtil.findAnnotations(method, MAPPINGS.keySet())[0]; + } + + protected static String url(PsiAnnotation classAnnotation, PsiAnnotation methodAnnotation) { + // RequestMapping前缀 + String prefix = Optional.ofNullable(PsiAnnotationMemberValueUtil.getArrayFirstValue(classAnnotation, "value")) + .map(String::valueOf).orElse(""); + // 肯定存在一个注解满足条件 + String suffix = Optional.ofNullable(PsiAnnotationMemberValueUtil.getArrayFirstValue(methodAnnotation, "value")) + .map(String::valueOf).orElse(""); + return prefix + suffix; + } +} diff --git a/src/main/groovy/com/github/passerr/idea/plugins/spring/web/CopyMethodPathAction.groovy b/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodPathAction.java similarity index 41% rename from src/main/groovy/com/github/passerr/idea/plugins/spring/web/CopyMethodPathAction.groovy rename to src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodPathAction.java index b81d477..a0ce072 100644 --- a/src/main/groovy/com/github/passerr/idea/plugins/spring/web/CopyMethodPathAction.groovy +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodPathAction.java @@ -1,20 +1,20 @@ -package com.github.passerr.idea.plugins.spring.web +package com.github.passerr.idea.plugins.spring.web; -import com.intellij.openapi.actionSystem.AnActionEvent -import com.intellij.openapi.ide.CopyPasteManager -import com.intellij.psi.PsiMethod -import com.intellij.util.ui.TextTransferable +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.ide.CopyPasteManager; +import com.intellij.psi.PsiMethod; +import com.intellij.util.ui.TextTransferable; /** * web路径copy - * @date 2021/06/25 16:03 - * @Copyright (c) wisewe co.,ltd * @author xiehai + * @date 2021/06/30 19:14 + * @Copyright(c) tellyes tech. inc. co.,ltd */ -class CopyMethodPathAction extends BaseWebCopyAction { +public class CopyMethodPathAction extends BaseWebCopyAction { @Override - void actionPerformed(AnActionEvent e) { - PsiMethod method = method(e) + public void actionPerformed(AnActionEvent e) { + PsiMethod method = method(e); CopyPasteManager.getInstance() .setContents( new TextTransferable( @@ -23,6 +23,6 @@ class CopyMethodPathAction extends BaseWebCopyAction { methodAnnotation(method) ) ) - ) + ); } -} +} \ No newline at end of file diff --git a/src/main/groovy/com/github/passerr/idea/plugins/spring/web/PsiAnnotationMemberValueUtil.groovy b/src/main/java/com/github/passerr/idea/plugins/spring/web/PsiAnnotationMemberValueUtil.java similarity index 42% rename from src/main/groovy/com/github/passerr/idea/plugins/spring/web/PsiAnnotationMemberValueUtil.groovy rename to src/main/java/com/github/passerr/idea/plugins/spring/web/PsiAnnotationMemberValueUtil.java index 15f87ef..df851df 100644 --- a/src/main/groovy/com/github/passerr/idea/plugins/spring/web/PsiAnnotationMemberValueUtil.groovy +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/PsiAnnotationMemberValueUtil.java @@ -1,18 +1,23 @@ -package com.github.passerr.idea.plugins.spring.web +package com.github.passerr.idea.plugins.spring.web; -import com.intellij.psi.PsiAnnotation -import com.intellij.psi.PsiAnnotationMemberValue -import com.intellij.psi.PsiArrayInitializerMemberValue -import com.intellij.psi.PsiExpression -import com.intellij.psi.PsiField -import com.intellij.psi.PsiLiteralExpression -import com.intellij.psi.PsiReferenceExpression +import com.intellij.psi.PsiAnnotation; +import com.intellij.psi.PsiAnnotationMemberValue; +import com.intellij.psi.PsiArrayInitializerMemberValue; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiExpression; +import com.intellij.psi.PsiField; +import com.intellij.psi.PsiLiteralExpression; +import com.intellij.psi.PsiReferenceExpression; + +import java.util.Arrays; +import java.util.Objects; +import java.util.Optional; /** * {@link com.intellij.psi.PsiAnnotationMemberValue}工具 + * @author xiehai * @date 2021/06/25 19:30 * @Copyright (c) wisewe co.,ltd - * @author xiehai */ class PsiAnnotationMemberValueUtil { private PsiAnnotationMemberValueUtil() { @@ -22,61 +27,65 @@ class PsiAnnotationMemberValueUtil { /** * 获取注解属性 * @param annotation 注解 - * @param attribute 属性名 + * @param attribute 属性名 * @return 属性值 */ static Object value(PsiAnnotation annotation, String attribute) { if (Objects.isNull(annotation)) { - return null + return null; } - return value(annotation.findAttributeValue(attribute)) + return value(annotation.findAttributeValue(attribute)); } static Object value(PsiAnnotationMemberValue v) { if (Objects.isNull(v)) { - return null + return null; } if (v instanceof PsiArrayInitializerMemberValue) { - return v.getInitializers().collect { it -> value(it) } as Object[] + return + Arrays.stream(((PsiArrayInitializerMemberValue) v).getInitializers()) + .map(PsiAnnotationMemberValueUtil::value) + .toArray(); } if (v instanceof PsiExpression) { - return value(v) + return value((PsiExpression) v); } - return v.text + return v.getText(); } static Object value(PsiExpression value) { if (value instanceof PsiLiteralExpression) { - return value.value + return ((PsiLiteralExpression) value).getValue(); } if (value instanceof PsiReferenceExpression) { - def resolve = value.resolve() + PsiElement resolve = ((PsiReferenceExpression) value).resolve(); if (resolve instanceof PsiField) { - return resolve.computeConstantValue() ?: resolve.text + return Optional.ofNullable(((PsiField) resolve).computeConstantValue()) + .orElseGet(resolve::getText); } - return value(resolve) + return value((PsiExpression) resolve); } - return value.text + return value.getText(); } static Object getArrayFirstValue(PsiAnnotation annotation, String attribute) { - Object value = value(annotation, attribute) - if (value.getClass().isArray()) { - Object[] array = value as Object[] + Object value = value(annotation, attribute); + if (Objects.nonNull(value) && value.getClass().isArray()) { + Object[] array = (Object[]) value; if (array.length > 0) { - return array[0] + return array[0]; } - return null + return null; } - return value + return value; } } diff --git a/src/main/groovy/com/github/passerr/idea/plugins/spring/web/WebCopyConstants.groovy b/src/main/java/com/github/passerr/idea/plugins/spring/web/WebCopyConstants.java similarity index 58% rename from src/main/groovy/com/github/passerr/idea/plugins/spring/web/WebCopyConstants.groovy rename to src/main/java/com/github/passerr/idea/plugins/spring/web/WebCopyConstants.java index 6314eb4..f78f807 100644 --- a/src/main/groovy/com/github/passerr/idea/plugins/spring/web/WebCopyConstants.groovy +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/WebCopyConstants.java @@ -1,108 +1,113 @@ -package com.github.passerr.idea.plugins.spring.web +package com.github.passerr.idea.plugins.spring.web; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** - * 复制相关常量 - * @date 2021/06/28 16:41 - * @Copyright (c) wisewe co.,ltd + * web常量 * @author xiehai + * @date 2021/06/30 19:07 + * @Copyright(c) tellyes tech. inc. co.,ltd */ -class WebCopyConstants { +public abstract class WebCopyConstants { /** * 均不支持的类型 */ - public static final List ALL_IGNORE_TYPES = [ - "java.lang.Object", - "java.util.Map" - ] + public static final List ALL_IGNORE_TYPES = Arrays.asList("java.lang.Object", "java.util.Map"); /** * 查询参数忽略类型 */ - public static final List QUERY_PARAM_IGNORE_TYPES = [ + public static final List QUERY_PARAM_IGNORE_TYPES = new ArrayList<>(Arrays.asList( "org.springframework.ui.Model", "javax.servlet.http.HttpServletRequest", "javax.servlet.http.HttpServletResponse", - "javax.servlet.http.HttpSession", + "javax.servlet.http.HttpSession" + )); - ] static { - QUERY_PARAM_IGNORE_TYPES.addAll(ALL_IGNORE_TYPES) + QUERY_PARAM_IGNORE_TYPES.addAll(ALL_IGNORE_TYPES); } /** * 路径参数注解 */ - public static final String PATH_VARIABLE_ANNOTATION = "org.springframework.web.bind.annotation.PathVariable" + public static final String PATH_VARIABLE_ANNOTATION = "org.springframework.web.bind.annotation.PathVariable"; /** * 查询参数注解 */ - public static final List QUERY_PARAM_ANNOTATIONS = [ + public static final List QUERY_PARAM_ANNOTATIONS = new ArrayList<>(Arrays.asList( "org.springframework.web.bind.annotation.RequestParam", "org.springframework.web.bind.annotation.RequestAttribute" - ] + )); /** * 查询参数忽略注解 */ - public static final List QUERY_PARAM_IGNORE_ANNOTATIONS = [ + public static final List QUERY_PARAM_IGNORE_ANNOTATIONS = Arrays.asList( "org.springframework.web.bind.annotation.CookieValue", "org.springframework.web.bind.annotation.RequestHeader", "org.springframework.web.bind.annotation.ResponseBody", "org.springframework.web.bind.annotation.SessionAttribute", "org.springframework.web.bind.annotation.SessionAttributes" - ] + ); + static { - QUERY_PARAM_IGNORE_ANNOTATIONS.add(PATH_VARIABLE_ANNOTATION) + QUERY_PARAM_IGNORE_ANNOTATIONS.add(PATH_VARIABLE_ANNOTATION); } /** * 默认数据类型别名 */ - public static final String DEFAULT_ALIAS = "object" + public static final String DEFAULT_ALIAS = "object"; /** * 布尔类型 */ - public static final String BOOLEAN_ALIAS = "boolean" + public static final String BOOLEAN_ALIAS = "boolean"; /** * 整型 */ - public static final String INT_ALIAS = "int" + public static final String INT_ALIAS = "int"; /** * 浮点型 */ - public static final String FLOAT_ALIAS = "float" + public static final String FLOAT_ALIAS = "float"; /** * 字符串 */ - public static final String STRING_ALIAS = "string" + public static final String STRING_ALIAS = "string"; /** * 所有类型别名 */ - public static final List TYPE_ALIASES = [ + public static final List TYPE_ALIASES = Arrays.asList( BOOLEAN_ALIAS, INT_ALIAS, FLOAT_ALIAS, STRING_ALIAS, DEFAULT_ALIAS - ] + ); /** * 默认类型映射 */ - public static final Map DEFAULT_ALIAS_MAPPINGS = [:] + public static final Map DEFAULT_ALIAS_MAPPINGS = new HashMap<>(); + static { - ["boolean", "java.lang.Boolean"].each { it -> DEFAULT_ALIAS_MAPPINGS.put(it, BOOLEAN_ALIAS) } + Arrays.asList("boolean", "java.lang.Boolean").forEach(it -> DEFAULT_ALIAS_MAPPINGS.put(it, BOOLEAN_ALIAS)); - [ + Arrays.asList( "byte", "java.lang.Byte", "short", "java.lang.Short", "char", "java.lang.Character", "int", "java.lang.Integer" - ].each { it -> DEFAULT_ALIAS_MAPPINGS.put(it, INT_ALIAS) } + ).forEach(it -> DEFAULT_ALIAS_MAPPINGS.put(it, INT_ALIAS)); - [ + Arrays.asList( "float", "java.lang.Float", "double", "java.lang.Double" - ].each { it -> DEFAULT_ALIAS_MAPPINGS.put(it, FLOAT_ALIAS) } + ).forEach(it -> DEFAULT_ALIAS_MAPPINGS.put(it, FLOAT_ALIAS)); - [ + Arrays.asList( "long", "java.lang.Long", "java.lang.String", @@ -112,16 +117,11 @@ class WebCopyConstants { "java.util.LocalDate", "java.util.LocalTime", "java.util.LocalDateTime" - ].each { it -> DEFAULT_ALIAS_MAPPINGS.put(it, STRING_ALIAS) } + ).forEach(it -> DEFAULT_ALIAS_MAPPINGS.put(it, STRING_ALIAS)); } /** * 默认文档模版 */ - public static final String DEFAULT_TEMPLATE = """Hello - World""" - - private WebCopyConstants() { - - } + public static final String DEFAULT_TEMPLATE = "Hello World"; } diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 2762a9b..f8c39ea 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -1,3 +1,4 @@ + com.github.passerr.idea.plugins PasseRR Idea Plugins @@ -55,39 +56,36 @@ - - - - + + + + + + + - - - + + + + + + - - - + + + + + + - - - - - - + + + + \ No newline at end of file -- Gitee From 1b7bf086ffbe7e8e77cd44d56ba37317e739cdd1 Mon Sep 17 00:00:00 2001 From: PasseRR Date: Thu, 1 Jul 2021 14:36:56 +0800 Subject: [PATCH 02/41] =?UTF-8?q?java=E6=94=B9=E5=86=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../passerr/idea/plugins/BaseTableModel.java | 20 ++- .../passerr/idea/plugins/ColumnIndex.java | 10 -- .../passerr/idea/plugins/IdeaJbTable.java | 28 ++++ .../idea/plugins/IdeaPanelWithButtons.java | 17 +++ .../plugins/spring/web/ApiDocConfigViews.java | 124 ++++++++++++++++++ .../spring/web/ApiDocConfigurable.java | 105 +++++++++++++++ .../spring/web/ApiDocStateComponent.java | 54 ++++++++ .../plugins/spring/web/BaseWebCopyAction.java | 1 + .../plugins/spring/web/WebCopyConstants.java | 4 +- .../spring/web/po/ApiDocAliasPairPo.java | 28 ++++ .../spring/web/po/ApiDocSettingPo.java | 70 ++++++++++ src/main/resources/META-INF/plugin.xml | 10 +- 12 files changed, 442 insertions(+), 29 deletions(-) delete mode 100644 src/main/java/com/github/passerr/idea/plugins/ColumnIndex.java create mode 100644 src/main/java/com/github/passerr/idea/plugins/IdeaJbTable.java create mode 100644 src/main/java/com/github/passerr/idea/plugins/IdeaPanelWithButtons.java create mode 100644 src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java create mode 100644 src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigurable.java create mode 100644 src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocStateComponent.java create mode 100644 src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocAliasPairPo.java create mode 100644 src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java diff --git a/src/main/java/com/github/passerr/idea/plugins/BaseTableModel.java b/src/main/java/com/github/passerr/idea/plugins/BaseTableModel.java index dac3d3c..7a1e641 100644 --- a/src/main/java/com/github/passerr/idea/plugins/BaseTableModel.java +++ b/src/main/java/com/github/passerr/idea/plugins/BaseTableModel.java @@ -5,10 +5,12 @@ import lombok.AccessLevel; import lombok.experimental.FieldDefaults; import javax.swing.table.AbstractTableModel; -import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.function.Function; /** + * {@link javax.swing.table.TableModel}双向绑定 * @author xiehai * @date 2021/06/30 18:50 * @Copyright(c) tellyes tech. inc. co.,ltd @@ -18,7 +20,7 @@ public class BaseTableModel extends AbstractTableModel implements ItemRemovab final List data; final List headers; - BaseTableModel(List headers, List data) { + public BaseTableModel(List headers, List data) { this.headers = headers; this.data = data; } @@ -43,25 +45,19 @@ public class BaseTableModel extends AbstractTableModel implements ItemRemovab @Override public int getColumnCount() { - return this.columns().isEmpty() ? 1 : this.columns().size(); + return this.columns().size(); } @Override public Object getValueAt(int rowIndex, int columnIndex) { - Object value = this.data.get(rowIndex); - if (this.columns().isEmpty()) { - return value; - } - - // TODO - return null; + return this.columns().get(columnIndex).apply(this.data.get(rowIndex)); } /** * 列属性 * @return 列 */ - List columns() { - return new ArrayList<>(); + List> columns() { + return Collections.singletonList(it -> it); } } diff --git a/src/main/java/com/github/passerr/idea/plugins/ColumnIndex.java b/src/main/java/com/github/passerr/idea/plugins/ColumnIndex.java deleted file mode 100644 index a509e03..0000000 --- a/src/main/java/com/github/passerr/idea/plugins/ColumnIndex.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.github.passerr.idea.plugins; - -/** - * 列位置 - * @author xiehai - * @date 2021/06/30 18:55 - * @Copyright(c) tellyes tech. inc. co.,ltd - */ -public @interface ColumnIndex { -} diff --git a/src/main/java/com/github/passerr/idea/plugins/IdeaJbTable.java b/src/main/java/com/github/passerr/idea/plugins/IdeaJbTable.java new file mode 100644 index 0000000..544c76f --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/IdeaJbTable.java @@ -0,0 +1,28 @@ +package com.github.passerr.idea.plugins; + +import com.intellij.ui.table.JBTable; + +import javax.swing.*; +import javax.swing.table.TableModel; + +/** + * {@link JBTable}重写 + * @author xiehai + * @date 2021/07/01 12:47 + * @Copyright(c) tellyes tech. inc. co.,ltd + */ +public class IdeaJbTable extends JBTable { + public IdeaJbTable(TableModel model) { + this(model, "暂无数据"); + } + + public IdeaJbTable(TableModel model, String empty) { + this(model, empty, ListSelectionModel.SINGLE_SELECTION); + } + + public IdeaJbTable(TableModel model, String empty, int selectModel) { + super(model); + super.getEmptyText().setText(empty); + super.getSelectionModel().setSelectionMode(selectModel); + } +} diff --git a/src/main/java/com/github/passerr/idea/plugins/IdeaPanelWithButtons.java b/src/main/java/com/github/passerr/idea/plugins/IdeaPanelWithButtons.java new file mode 100644 index 0000000..6c46b57 --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/IdeaPanelWithButtons.java @@ -0,0 +1,17 @@ +package com.github.passerr.idea.plugins; + +import com.intellij.ui.PanelWithButtons; + +/** + * {@link PanelWithButtons}重写 + * @author xiehai + * @date 2021/06/30 19:42 + * @Copyright(c) tellyes tech. inc. co.,ltd + * @see PanelWithButtons + */ +public abstract class IdeaPanelWithButtons extends PanelWithButtons { + public IdeaPanelWithButtons() { + super(); + super.initPanel(); + } +} diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java new file mode 100644 index 0000000..f7546e2 --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java @@ -0,0 +1,124 @@ +package com.github.passerr.idea.plugins.spring.web; + +import com.github.passerr.idea.plugins.BaseTableModel; +import com.github.passerr.idea.plugins.IdeaJbTable; +import com.github.passerr.idea.plugins.IdeaPanelWithButtons; +import com.github.passerr.idea.plugins.spring.web.po.ApiDocSettingPo; +import com.intellij.openapi.util.Pair; +import com.intellij.ui.PanelWithButtons; +import com.intellij.ui.ToolbarDecorator; +import com.intellij.ui.table.JBTable; +import com.intellij.util.ui.JBUI; + +import javax.swing.*; +import java.awt.*; +import java.util.Arrays; +import java.util.Collections; + +/** + * api文档配置视图 + * @author xiehai + * @date 2021/06/30 19:39 + * @Copyright(c) tellyes tech. inc. co.,ltd + */ +public abstract class ApiDocConfigViews { + /** + * 根据已有配置生成视图 + * @param setting {@link ApiDocSettingPo} + * @return {@link List} + */ + public static java.util.List> panels(ApiDocSettingPo setting) { + return + Arrays.asList( + Pair.pair("Api模版", new JPanel()), + Pair.pair("查询参数", queryParamPanel(setting)) + ); + } + + /** + * 查询参数视图 + * @param setting {@link ApiDocSettingPo} + * @return {@link JPanel} + */ + private static JPanel queryParamPanel(ApiDocSettingPo setting) { + JPanel panel = new JPanel(new GridBagLayout()); + PanelWithButtons top = new IdeaPanelWithButtons() { + @Override + protected String getLabelText() { + return "忽略类型"; + } + + @Override + protected JButton[] createButtons() { + return new JButton[0]; + } + + @Override + protected JComponent createMainComponent() { + BaseTableModel model = new BaseTableModel<>( + Collections.singletonList("类型"), setting.getQueryParamIgnoreTypes()); + JBTable table = new IdeaJbTable(model); + + return + ToolbarDecorator.createDecorator(table) + .setAddAction(it -> {}) + .setAddActionName("新增") + .setEditAction(it -> {}) + .setEditActionName("编辑") + .setRemoveAction(it -> model.removeRow(table.getSelectedRow())) + .setRemoveActionName("删除") + .disableUpDownActions() + .createPanel(); + } + }; + PanelWithButtons bottom = new IdeaPanelWithButtons() { + @Override + protected String getLabelText() { + return "忽略注解"; + } + + @Override + protected JButton[] createButtons() { + return new JButton[0]; + } + + @Override + protected JComponent createMainComponent() { + BaseTableModel model = new BaseTableModel<>( + Collections.singletonList("注解"), setting.getQueryParamIgnoreAnnotations()); + JBTable table = new IdeaJbTable(model); + + return + ToolbarDecorator.createDecorator(table) + .setAddAction(it -> {}) + .setAddActionName("新增") + .setEditAction(it -> {}) + .setEditActionName("编辑") + .setRemoveAction(it -> model.removeRow(table.getSelectedRow())) + .setRemoveActionName("删除") + .disableUpDownActions() + .createPanel(); + } + }; + panel.add( + top, + new GridBagConstraints( + 0, 0, 1, 1, 1, 1, + GridBagConstraints.NORTH, + GridBagConstraints.BOTH, + JBUI.emptyInsets(), 0, 0 + ) + ); + panel.add( + bottom, + new GridBagConstraints( + 0, 1, 1, 1, 1, 1, + GridBagConstraints.NORTH, + GridBagConstraints.BOTH, + JBUI.emptyInsets(), 0, 0 + ) + ); + + return panel; + } +} diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigurable.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigurable.java new file mode 100644 index 0000000..8a7c93e --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigurable.java @@ -0,0 +1,105 @@ +package com.github.passerr.idea.plugins.spring.web; + +import com.github.passerr.idea.plugins.spring.web.po.ApiDocSettingPo; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.options.Configurable; +import com.intellij.openapi.options.SearchableConfigurable; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.Pair; +import com.intellij.ui.TabbedPaneWrapper; +import com.intellij.ui.tabs.JBTabs; +import com.intellij.ui.tabs.TabInfo; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.JComponent; +import javax.swing.JPanel; +import java.util.List; +import java.util.Objects; + +/** + * api文档配置组件 + * @author xiehai + * @date 2021/06/30 19:37 + * @Copyright(c) tellyes tech. inc. co.,ltd + */ +public class ApiDocConfigurable implements SearchableConfigurable, Configurable.NoScroll { + Disposable disposable; + ApiDocSettingPo source; + ApiDocSettingPo copy; + + ApiDocConfigurable() { + this.source = ApiDocStateComponent.getInstance().getState(); + assert this.source != null; + this.copy = ApiDocSettingPo.deepCopy(this.source); + } + + @Override + public String getDisplayName() { + return "Api Doc Setting"; + } + + @Override + public String getHelpTopic() { + return "doc"; + } + + @Override + public JComponent createComponent() { + this.disposable = Disposer.newDisposable(); + TabbedPaneWrapper tabbedPanel = new TabbedPaneWrapper(disposable); + List> panels = ApiDocConfigViews.panels(this.copy); + panels.forEach(it -> tabbedPanel.addTab(it.getFirst(), it.getSecond())); + + tabbedPanel.addChangeListener(e -> { + TabInfo selectedInfo = ((JBTabs) e.getSource()).getSelectedInfo(); + if (Objects.isNull(selectedInfo)) { + return; + } + String tab = selectedInfo.getText(); + panels.stream() + .filter(it -> Objects.equals(it.getFirst(), tab)) + .findFirst() + .ifPresent(it -> it.getSecond().repaint()); + }); + + return tabbedPanel.getComponent(); + } + + @Override + public boolean isModified() { + return !Objects.equals(this.source, this.copy); + } + + @Override + public void apply() { + this.source.update(this.copy); + } + + @Override + public void reset() { + if (this.isModified()) { + this.copy = ApiDocSettingPo.deepCopy(this.source); + } + } + + @Override + public void disposeUIResources() { + if (this.disposable != null) { + Disposer.dispose(this.disposable); + this.disposable = null; + } + } + + @NotNull + @Override + public String getId() { + return Objects.requireNonNull(this.getHelpTopic()); + } + + @Nullable + @Override + public Runnable enableSearch(String option) { + return null; + } +} diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocStateComponent.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocStateComponent.java new file mode 100644 index 0000000..0c33cec --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocStateComponent.java @@ -0,0 +1,54 @@ +package com.github.passerr.idea.plugins.spring.web; + +import com.github.passerr.idea.plugins.spring.web.po.ApiDocAliasPairPo; +import com.github.passerr.idea.plugins.spring.web.po.ApiDocSettingPo; +import com.intellij.openapi.components.PersistentStateComponent; +import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.components.State; +import com.intellij.openapi.components.Storage; +import com.intellij.util.xmlb.XmlSerializerUtil; + +import java.util.Objects; + +/** + * 配置持久化组件 + * @author xiehai + * @date 2021/06/30 19:28 + * @Copyright(c) tellyes tech. inc. co.,ltd + */ +@State( + name = "com.github.passerr.idea.plugins.spring.web.ApiDocStateComponent", + storages = @Storage("com.github.passerr.idea.plugins.spring.web.ApiDocStateComponent.xml") +) +public class ApiDocStateComponent implements PersistentStateComponent { + private final ApiDocSettingPo apiDocSettingPo = new ApiDocSettingPo(); + + @Override + public ApiDocSettingPo getState() { + return this.apiDocSettingPo; + } + + @Override + public void loadState(ApiDocSettingPo state) { + XmlSerializerUtil.copyBean(state, this.apiDocSettingPo); + } + + /** + * 别名获取 + * @param type 类型全称 + * @return 别名 + */ + String alias(String type) { + return + this.apiDocSettingPo.getAliases() + .stream() + .filter(it -> Objects.equals(it.getType(), type)) + .map(ApiDocAliasPairPo::getAlias) + .findFirst() + .orElse(WebCopyConstants.DEFAULT_ALIAS); + } + + static ApiDocStateComponent getInstance() { + return ServiceManager.getService(ApiDocStateComponent.class); + } +} \ No newline at end of file diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/BaseWebCopyAction.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/BaseWebCopyAction.java index 521ecac..6efcc73 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/BaseWebCopyAction.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/BaseWebCopyAction.java @@ -15,6 +15,7 @@ import java.util.Map; import java.util.Optional; /** + * spring web基础action * @author xiehai * @date 2021/06/30 18:57 * @Copyright(c) tellyes tech. inc. co.,ltd diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/WebCopyConstants.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/WebCopyConstants.java index f78f807..68a29c9 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/WebCopyConstants.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/WebCopyConstants.java @@ -45,13 +45,13 @@ public abstract class WebCopyConstants { /** * 查询参数忽略注解 */ - public static final List QUERY_PARAM_IGNORE_ANNOTATIONS = Arrays.asList( + public static final List QUERY_PARAM_IGNORE_ANNOTATIONS = new ArrayList<>(Arrays.asList( "org.springframework.web.bind.annotation.CookieValue", "org.springframework.web.bind.annotation.RequestHeader", "org.springframework.web.bind.annotation.ResponseBody", "org.springframework.web.bind.annotation.SessionAttribute", "org.springframework.web.bind.annotation.SessionAttributes" - ); + )); static { QUERY_PARAM_IGNORE_ANNOTATIONS.add(PATH_VARIABLE_ANNOTATION); diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocAliasPairPo.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocAliasPairPo.java new file mode 100644 index 0000000..ffad9bd --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocAliasPairPo.java @@ -0,0 +1,28 @@ +package com.github.passerr.idea.plugins.spring.web.po; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.FieldDefaults; + +/** + * 别名对po + * @author xiehai + * @date 2021/06/30 19:29 + * @Copyright(c) tellyes tech. inc. co.,ltd + */ +@Data +@EqualsAndHashCode +@FieldDefaults(level = AccessLevel.PRIVATE) +@AllArgsConstructor +@NoArgsConstructor +public class ApiDocAliasPairPo { + String type; + String alias; + + public static ApiDocAliasPairPo deepCopy(ApiDocAliasPairPo source) { + return new ApiDocAliasPairPo(source.type, source.alias); + } +} diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java new file mode 100644 index 0000000..0bb7723 --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java @@ -0,0 +1,70 @@ +package com.github.passerr.idea.plugins.spring.web.po; + +import com.github.passerr.idea.plugins.spring.web.WebCopyConstants; +import com.intellij.util.xmlb.annotations.AbstractCollection; +import com.intellij.util.xmlb.annotations.Tag; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.FieldDefaults; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author xiehai + * @date 2021/06/30 19:30 + * @Copyright(c) tellyes tech. inc. co.,ltd + */ +@Data +@EqualsAndHashCode +@FieldDefaults(level = AccessLevel.PRIVATE) +@AllArgsConstructor +public class ApiDocSettingPo { + @Tag("template") + String template; + @Tag("all-ignore-types") + @AbstractCollection + List allIgnoreTypes; + @Tag("query-param-ignore-types") + @AbstractCollection + List queryParamIgnoreTypes; + @Tag("query-param-ignore-annotations") + @AbstractCollection + List queryParamIgnoreAnnotations; + @Tag("aliases") + @AbstractCollection(elementTypes = ApiDocAliasPairPo.class) + List aliases; + + public ApiDocSettingPo() { + this.template = WebCopyConstants.DEFAULT_TEMPLATE; + this.allIgnoreTypes = new ArrayList<>(WebCopyConstants.ALL_IGNORE_TYPES); + this.queryParamIgnoreTypes = new ArrayList<>(WebCopyConstants.QUERY_PARAM_IGNORE_TYPES); + this.queryParamIgnoreAnnotations = new ArrayList<>(WebCopyConstants.QUERY_PARAM_IGNORE_ANNOTATIONS); + this.aliases = WebCopyConstants.DEFAULT_ALIAS_MAPPINGS.entrySet() + .stream() + .map(it -> new ApiDocAliasPairPo(it.getKey(), it.getValue())) + .collect(Collectors.toList()); + } + + public static ApiDocSettingPo deepCopy(ApiDocSettingPo source) { + return + new ApiDocSettingPo( + source.template, + new ArrayList<>(source.allIgnoreTypes), + new ArrayList<>(source.queryParamIgnoreTypes), + new ArrayList<>(source.queryParamIgnoreAnnotations), + source.aliases.stream().map(ApiDocAliasPairPo::deepCopy).collect(Collectors.toList()) + ); + } + + public void update(ApiDocSettingPo target) { + this.template = target.template; + this.allIgnoreTypes = target.allIgnoreTypes; + this.queryParamIgnoreTypes = target.queryParamIgnoreTypes; + this.queryParamIgnoreAnnotations = target.queryParamIgnoreAnnotations; + this.aliases = target.aliases; + } +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index f8c39ea..7e74de5 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -74,7 +74,7 @@ - + @@ -84,8 +84,8 @@ - - - - + + + + \ No newline at end of file -- Gitee From 68c076dc060415d1ba7f801d2b7cca6f7a02538b Mon Sep 17 00:00:00 2001 From: PasseRR Date: Thu, 1 Jul 2021 14:43:34 +0800 Subject: [PATCH 03/41] =?UTF-8?q?=E9=A9=BC=E5=B3=B0=E8=BF=81=E7=A7=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../idea/plugins/NotificationThread.java | 42 ++++++ .../idea/plugins/camel/ToggleCamelCase.java | 124 ++++++++++++++++++ src/main/resources/META-INF/plugin.xml | 26 ++-- 3 files changed, 179 insertions(+), 13 deletions(-) create mode 100644 src/main/java/com/github/passerr/idea/plugins/NotificationThread.java create mode 100644 src/main/java/com/github/passerr/idea/plugins/camel/ToggleCamelCase.java diff --git a/src/main/java/com/github/passerr/idea/plugins/NotificationThread.java b/src/main/java/com/github/passerr/idea/plugins/NotificationThread.java new file mode 100644 index 0000000..cd777c2 --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/NotificationThread.java @@ -0,0 +1,42 @@ +package com.github.passerr.idea.plugins; + +import com.intellij.notification.Notification; +import com.intellij.notification.Notifications; +import lombok.AccessLevel; +import lombok.experimental.FieldDefaults; + +import java.util.concurrent.TimeUnit; + +/** + * 消息通知 + * @author xiehai1 + * @date 2018/11/08 17:31 + * @Copyright (c) gome inc Gome Co.,LTD + */ +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public class NotificationThread extends Thread { + Notification notification; + int sleepTime; + + NotificationThread(Notification notification) { + // 默认4秒关闭弹窗 + this(notification, 4); + } + + NotificationThread(Notification notification, int sleepTime) { + assert sleepTime > 0; + this.notification = notification; + // 默认4秒关闭弹窗 + this.sleepTime = sleepTime; + } + + @Override + public void run() { + Notifications.Bus.notify(this.notification); + try { + TimeUnit.SECONDS.sleep(this.sleepTime); + } catch (InterruptedException ignore) { + } + this.notification.expire(); + } +} diff --git a/src/main/java/com/github/passerr/idea/plugins/camel/ToggleCamelCase.java b/src/main/java/com/github/passerr/idea/plugins/camel/ToggleCamelCase.java new file mode 100644 index 0000000..61e2fd3 --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/camel/ToggleCamelCase.java @@ -0,0 +1,124 @@ +package com.github.passerr.idea.plugins.camel; + +import com.intellij.codeInsight.actions.MultiCaretCodeInsightAction; +import com.intellij.codeInsight.actions.MultiCaretCodeInsightActionHandler; +import com.intellij.openapi.actionSystem.ActionGroup; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.Result; +import com.intellij.openapi.application.WriteAction; +import com.intellij.openapi.command.CommandProcessor; +import com.intellij.openapi.editor.Caret; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.EditorModificationUtil; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiFile; +import org.apache.commons.lang.StringUtils; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.Objects; +import java.util.stream.IntStream; + +/** + * 驼峰命名切换 + * @author xiehai1 + * @date 2018/10/12 12:31 + * @Copyright tellyes tech. inc. co.,ltd + */ +public class ToggleCamelCase extends MultiCaretCodeInsightAction { + private static final String UNDERSCORE = "_"; + + @Override + @NotNull + protected MultiCaretCodeInsightActionHandler getHandler() { + return + new MultiCaretCodeInsightActionHandler() { + @Override + public void invoke(@NotNull Project project, @NotNull Editor editor, @NotNull Caret caret, + @NotNull PsiFile file) { + String text = caret.getEditor().getSelectionModel().getSelectedText(); + if (StringUtils.isEmpty(text)) { + editor.getSelectionModel().selectWordAtCaret(true); + text = editor.getSelectionModel().getSelectedText(); + } + assert Objects.nonNull(text); + + String newText; + if (Objects.equals(text, text.toLowerCase()) && text.contains(UNDERSCORE)) { + // snake_case to SNAKE_CASE + newText = text.toUpperCase(); + } else if (Objects.equals(text, text.toUpperCase()) && text.contains(UNDERSCORE)) { + // SNAKE_CASE to SnakeCase + newText = toCamelCase(text.toLowerCase()); + } else if (!Objects.equals(text, text.toUpperCase()) + && text.substring(0, 1).equals(text.substring(0, 1).toUpperCase()) + && !text.contains(UNDERSCORE)) { + // CamelCase to camelCase + newText = text.substring(0, 1).toLowerCase() + text.substring(1); + } else { + // camelCase to snake_case + newText = toSnakeCase(text); + } + + ApplicationManager.getApplication().runWriteAction(() -> + CommandProcessor.getInstance().executeCommand( + project, + () -> + new WriteAction() { + @Override + protected void run(@NotNull Result result) { + int start = editor.getSelectionModel().getSelectionStart(); + EditorModificationUtil.insertStringAtCaret(editor, newText); + editor.getSelectionModel().setSelection(start, start + newText.length()); + } + }.execute().throwException() + , + "CamelCase", + ActionGroup.EMPTY_GROUP + ) + ); + } + }; + } + + /** + * camelCase to snake_case + * @param text camelCase + * @return snake_case + */ + private static String toSnakeCase(String text) { + StringBuilder result = new StringBuilder().append(Character.toLowerCase(text.charAt(0))); + IntStream.range(1, text.length()) + .forEach(i -> { + char c = text.charAt(i); + if (Character.isUpperCase(c)) { + result.append(UNDERSCORE).append(Character.toLowerCase(c)); + } else { + result.append(c); + } + }); + + return result.toString(); + } + + /** + * SNAKE_CASE to SnakeCase + * @param text SNAKE_CASE + * @return SnakeCase + */ + private static String toCamelCase(String text) { + StringBuilder result = new StringBuilder(); + Arrays.stream(text.split(UNDERSCORE)) + .forEach(token -> { + if (token.length() >= 1) { + result.append(token.substring(0, 1).toUpperCase()) + .append(token.substring(1)); + } else { + result.append(UNDERSCORE); + } + }); + + return result.toString(); + } +} + diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 7e74de5..d175058 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -56,13 +56,13 @@ - - - - - - - + + + + @@ -76,12 +76,12 @@ text="Copy Method Rest Path"> - - - - - - + + + + + + -- Gitee From 9db4976b0e806434ff0f2229310567154ec46a1c Mon Sep 17 00:00:00 2001 From: PasseRR Date: Thu, 1 Jul 2021 14:58:54 +0800 Subject: [PATCH 04/41] =?UTF-8?q?mybatis=E6=97=A5=E5=BF=97=E5=A4=8D?= =?UTF-8?q?=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../idea/plugins/NotificationThread.java | 4 +- .../idea/plugins/mybatis/LogConstants.java | 61 +++ .../idea/plugins/mybatis/LogParser.java | 137 +++++++ .../plugins/mybatis/MybatisLog2SqlAction.java | 38 ++ .../idea/plugins/mybatis/SqlFormatter.java | 364 ++++++++++++++++++ src/main/resources/META-INF/plugin.xml | 12 +- 6 files changed, 608 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/github/passerr/idea/plugins/mybatis/LogConstants.java create mode 100644 src/main/java/com/github/passerr/idea/plugins/mybatis/LogParser.java create mode 100644 src/main/java/com/github/passerr/idea/plugins/mybatis/MybatisLog2SqlAction.java create mode 100644 src/main/java/com/github/passerr/idea/plugins/mybatis/SqlFormatter.java diff --git a/src/main/java/com/github/passerr/idea/plugins/NotificationThread.java b/src/main/java/com/github/passerr/idea/plugins/NotificationThread.java index cd777c2..5348f1c 100644 --- a/src/main/java/com/github/passerr/idea/plugins/NotificationThread.java +++ b/src/main/java/com/github/passerr/idea/plugins/NotificationThread.java @@ -18,12 +18,12 @@ public class NotificationThread extends Thread { Notification notification; int sleepTime; - NotificationThread(Notification notification) { + public NotificationThread(Notification notification) { // 默认4秒关闭弹窗 this(notification, 4); } - NotificationThread(Notification notification, int sleepTime) { + public NotificationThread(Notification notification, int sleepTime) { assert sleepTime > 0; this.notification = notification; // 默认4秒关闭弹窗 diff --git a/src/main/java/com/github/passerr/idea/plugins/mybatis/LogConstants.java b/src/main/java/com/github/passerr/idea/plugins/mybatis/LogConstants.java new file mode 100644 index 0000000..5bff7ab --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/mybatis/LogConstants.java @@ -0,0 +1,61 @@ +package com.github.passerr.idea.plugins.mybatis; + +import java.util.Arrays; +import java.util.List; + +/** + * 日志常量 + * @author xiehai1 + * @date 2018/11/08 13:12 + * @Copyright (c) gome inc Gome Co.,LTD + */ +interface LogConstants { + /** + * 空格 + */ + String SPACE = " "; + /** + * sql语句前缀 + */ + String PREFIX_SQL = "Preparing: "; + /** + * 参数占位符 + */ + String PARAM_PLACEHOLDER = "\\?"; + /** + * 换行符 + */ + String BREAK_LINE = "\n"; + /** + * 无sql参数前缀 + */ + String PREFIX_PARAMS_WITHOUT_SPACE = "Parameters:"; + /** + * sql参数前缀 + */ + String PREFIX_PARAMS = PREFIX_PARAMS_WITHOUT_SPACE + SPACE; + /** + * 参数值分隔符 + */ + String PARAM_SEPARATOR = ", "; + /** + * 左括号 + */ + String LEFT_BRACKET = "("; + /** + * 右括号 + */ + String RIGHT_BRACKET = ")"; + /** + * 空字符串 + */ + String EMPTY = ""; + /** + * null值 + */ + String NULL = "null"; + /** + * 不需要加单引号的类型 + */ + List NON_QUOTED_TYPES = Arrays.asList("Integer", "Long", "Double", "Float", "Boolean"); +} diff --git a/src/main/java/com/github/passerr/idea/plugins/mybatis/LogParser.java b/src/main/java/com/github/passerr/idea/plugins/mybatis/LogParser.java new file mode 100644 index 0000000..1ca264d --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/mybatis/LogParser.java @@ -0,0 +1,137 @@ +package com.github.passerr.idea.plugins.mybatis; + +import com.github.passerr.idea.plugins.NotificationThread; +import com.intellij.notification.Notification; +import com.intellij.notification.NotificationType; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +import static com.github.passerr.idea.plugins.mybatis.LogConstants.BREAK_LINE; +import static com.github.passerr.idea.plugins.mybatis.LogConstants.EMPTY; +import static com.github.passerr.idea.plugins.mybatis.LogConstants.LEFT_BRACKET; +import static com.github.passerr.idea.plugins.mybatis.LogConstants.NON_QUOTED_TYPES; +import static com.github.passerr.idea.plugins.mybatis.LogConstants.NULL; +import static com.github.passerr.idea.plugins.mybatis.LogConstants.PARAM_PLACEHOLDER; +import static com.github.passerr.idea.plugins.mybatis.LogConstants.PARAM_SEPARATOR; +import static com.github.passerr.idea.plugins.mybatis.LogConstants.PREFIX_PARAMS; +import static com.github.passerr.idea.plugins.mybatis.LogConstants.PREFIX_PARAMS_WITHOUT_SPACE; +import static com.github.passerr.idea.plugins.mybatis.LogConstants.PREFIX_SQL; +import static com.github.passerr.idea.plugins.mybatis.LogConstants.RIGHT_BRACKET; +import static com.github.passerr.idea.plugins.mybatis.LogConstants.SPACE; + +/** + * mybatis日志解析器 + * @author xiehai + * @date 2021/07/01 14:44 + * @Copyright(c) tellyes tech. inc. co.,ltd + */ +public class LogParser { + /** + * 日志解析为美化后的sql语句 + * @param log 日志内容 + * @return sql + */ + public static String toBeautifulSql(String log) { + return SqlFormatter.format(toSql(log)); + } + + /** + * 日志解析为可执行的sql语句 + * @param log 日志内容 + * @return sql + */ + public static String toSql(String log) { + String sqlLine = null, valueLine = null; + for (String line : log.split(BREAK_LINE)) { + if (line.contains(PREFIX_SQL)) { + sqlLine = line; + } else if (line.contains(PREFIX_PARAMS)) { + valueLine = line; + } else if (line.contains(PREFIX_PARAMS_WITHOUT_SPACE)) { + // 没有参数的sql 自动补齐一个空格 + valueLine = line + SPACE; + } + } + + // 是否找到包含sql及参数的日志 + if (Objects.isNull(sqlLine)) { + // 提示信息 + new NotificationThread( + new Notification( + "Copy As Executable Sql", + "Copy As Executable Sql", + "selected log without \"Preparing:\" line, nothing will send to clipboard!", + NotificationType.WARNING + ) + ).start(); + + return EMPTY; + } else if (Objects.isNull(valueLine)) { + // 提示信息 + new NotificationThread( + new Notification( + "Copy As Executable Sql", + "Copy As Executable Sql", + "selected log without \"Parameters:\" line, nothing will send to clipboard!", + NotificationType.WARNING + ) + ).start(); + + return EMPTY; + } + + // 带占位符的sql + int sqlPrefixIndex = sqlLine.indexOf(PREFIX_SQL); + String originSql = sqlLine.substring(sqlPrefixIndex + PREFIX_SQL.length()); + // 参数列表 + int paramPrefixIndex = valueLine.indexOf(PREFIX_PARAMS); + String paramValues = valueLine.substring(paramPrefixIndex + PREFIX_PARAMS.length()); + + List originSqlSections = new ArrayList<>(Arrays.asList(originSql.split(PARAM_PLACEHOLDER))); + List paramValuesSections = new ArrayList<>(Arrays.asList(paramValues.split(PARAM_SEPARATOR))); + int i = 0; + StringBuilder sb = new StringBuilder(); + while (originSqlSections.size() > i && paramValuesSections.size() > i) { + sb.append(originSqlSections.get(i)); + sb.append(parseParam(paramValuesSections.get(i))); + i++; + } + + while (originSqlSections.size() > i) { + sb.append(originSqlSections.get(i)); + i++; + } + + return sb.toString(); + } + + /** + * 解析参数值 + * @param paramValue 参数值字符串 + * @return 参数值 + */ + private static String parseParam(String paramValue) { + // 如果是空字符串直接返回 + if (paramValue.length() == 0) { + return EMPTY; + } + + // 如果是null 直接返回null + if (paramValue.trim().equals(NULL)) { + return NULL; + } + + // 括号的索引 + int lastLeftBracketIndex = paramValue.lastIndexOf(LEFT_BRACKET); + int lastRightBracketIndex = paramValue.lastIndexOf(RIGHT_BRACKET); + // 参数值 + String param = paramValue.substring(0, lastLeftBracketIndex); + // 参数类型 + String type = paramValue.substring(lastLeftBracketIndex + 1, lastRightBracketIndex); + + return NON_QUOTED_TYPES.contains(type) ? param : String.format("'%s'", param); + } +} diff --git a/src/main/java/com/github/passerr/idea/plugins/mybatis/MybatisLog2SqlAction.java b/src/main/java/com/github/passerr/idea/plugins/mybatis/MybatisLog2SqlAction.java new file mode 100644 index 0000000..80afe31 --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/mybatis/MybatisLog2SqlAction.java @@ -0,0 +1,38 @@ +package com.github.passerr.idea.plugins.mybatis; + +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.SelectionModel; +import com.intellij.openapi.ide.CopyPasteManager; +import com.intellij.util.ui.TextTransferable; +import org.apache.commons.lang.StringUtils; + +import java.util.Optional; + +/** + * mybatis日志转可执行sql + * @author xiehai1 + * @date 2018/11/08 11:47 + * @Copyright (c) gome inc Gome Co.,LTD + */ +public class MybatisLog2SqlAction extends AnAction { + @Override + public void actionPerformed(AnActionEvent e) { + // 选中的日志内容 + String log = + Optional.ofNullable(e.getData(PlatformDataKeys.EDITOR)) + .map(Editor::getSelectionModel) + .map(SelectionModel::getSelectedText) + .orElse(null); + if (StringUtils.isNotEmpty(log)) { + // 格式化后的sql + String sql = LogParser.toSql(log); + if (StringUtils.isNotEmpty(sql)) { + // 自动发送到剪贴板 + CopyPasteManager.getInstance().setContents(new TextTransferable(SqlFormatter.format(sql))); + } + } + } +} diff --git a/src/main/java/com/github/passerr/idea/plugins/mybatis/SqlFormatter.java b/src/main/java/com/github/passerr/idea/plugins/mybatis/SqlFormatter.java new file mode 100644 index 0000000..62f5b27 --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/mybatis/SqlFormatter.java @@ -0,0 +1,364 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package com.github.passerr.idea.plugins.mybatis; + +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Locale; +import java.util.Set; +import java.util.StringTokenizer; + +/** + * Performs formatting of basic SQL statements (DML + query). + * @author Gavin King + * @author Steve Ebersole + */ +public class SqlFormatter { + // MOD: from org.hibernate.internal.util.StringHelper + private static final String WHITESPACE = " \n\r\f\t"; + private static final Set BEGIN_CLAUSES = new HashSet(); + private static final Set END_CLAUSES = new HashSet(); + private static final Set LOGICAL = new HashSet(); + private static final Set QUANTIFIERS = new HashSet(); + private static final Set DML = new HashSet(); + private static final Set MISC = new HashSet(); + + static { + BEGIN_CLAUSES.add("left"); + BEGIN_CLAUSES.add("right"); + BEGIN_CLAUSES.add("inner"); + BEGIN_CLAUSES.add("outer"); + BEGIN_CLAUSES.add("group"); + BEGIN_CLAUSES.add("order"); + + END_CLAUSES.add("where"); + END_CLAUSES.add("set"); + END_CLAUSES.add("having"); + END_CLAUSES.add("join"); + END_CLAUSES.add("from"); + END_CLAUSES.add("by"); + END_CLAUSES.add("into"); + END_CLAUSES.add("union"); + + LOGICAL.add("and"); + LOGICAL.add("or"); + LOGICAL.add("when"); + LOGICAL.add("else"); + LOGICAL.add("end"); + + QUANTIFIERS.add("in"); + QUANTIFIERS.add("all"); + QUANTIFIERS.add("exists"); + QUANTIFIERS.add("some"); + QUANTIFIERS.add("any"); + + DML.add("insert"); + DML.add("update"); + DML.add("delete"); + + MISC.add("select"); + MISC.add("on"); + } + + private static final String INDENT_STRING = " "; + private static final String INITIAL = System.lineSeparator() + INDENT_STRING; + + public static String format(String source) { + return new FormatProcess(source).perform(); + } + + private static class FormatProcess { + boolean beginLine = true; + boolean afterBeginBeforeEnd; + boolean afterByOrSetOrFromOrSelect; + boolean afterValues; + boolean afterOn; + boolean afterBetween; + boolean afterInsert; + int inFunction; + int parensSinceSelect; + private LinkedList parenCounts = new LinkedList<>(); + private LinkedList afterByOrFromOrSelects = new LinkedList<>(); + + int indent = 1; + + StringBuilder result = new StringBuilder(); + StringTokenizer tokens; + String lastToken; + String token; + String lcToken; + + public FormatProcess(String sql) { + tokens = new StringTokenizer( + sql, + "()+*/-=<>'`\"[]," + WHITESPACE, + true + ); + } + + public String perform() { + + result.append(INITIAL); + + while (tokens.hasMoreTokens()) { + token = tokens.nextToken(); + lcToken = token.toLowerCase(Locale.ROOT); + + if ("'".equals(token)) { + String t; + do { + t = tokens.nextToken(); + token += t; + } + // cannot handle single quotes + while (!"'".equals(t) && tokens.hasMoreTokens()); + } else if ("\"".equals(token)) { + String t; + do { + t = tokens.nextToken(); + token += t; + } + while (!"\"".equals(t) && tokens.hasMoreTokens()); + } + // SQL Server uses "[" and "]" to escape reserved words + // see SQLServerDialect.openQuote and SQLServerDialect.closeQuote + else if ("[".equals(token)) { + String t; + do { + t = tokens.nextToken(); + token += t; + } + while (!"]".equals(t) && tokens.hasMoreTokens()); + } + + if (afterByOrSetOrFromOrSelect && ",".equals(token)) { + commaAfterByOrFromOrSelect(); + } else if (afterOn && ",".equals(token)) { + commaAfterOn(); + } else if ("(".equals(token)) { + openParen(); + } else if (")".equals(token)) { + closeParen(); + } else if (BEGIN_CLAUSES.contains(lcToken)) { + beginNewClause(); + } else if (END_CLAUSES.contains(lcToken)) { + endNewClause(); + } else if ("select".equals(lcToken)) { + select(); + } else if (DML.contains(lcToken)) { + updateOrInsertOrDelete(); + } else if ("values".equals(lcToken)) { + values(); + } else if ("on".equals(lcToken)) { + on(); + } else if (afterBetween && lcToken.equals("and")) { + misc(); + afterBetween = false; + } else if (LOGICAL.contains(lcToken)) { + logical(); + } else if (isWhitespace(token)) { + white(); + } else { + misc(); + } + + if (!isWhitespace(token)) { + lastToken = lcToken; + } + + } + return result.toString(); + } + + private void commaAfterOn() { + out(); + indent--; + newline(); + afterOn = false; + afterByOrSetOrFromOrSelect = true; + } + + private void commaAfterByOrFromOrSelect() { + out(); + newline(); + } + + private void logical() { + if ("end".equals(lcToken)) { + indent--; + } + newline(); + out(); + beginLine = false; + } + + private void on() { + indent++; + afterOn = true; + newline(); + out(); + beginLine = false; + } + + private void misc() { + out(); + if ("between".equals(lcToken)) { + afterBetween = true; + } + if (afterInsert) { + newline(); + afterInsert = false; + } else { + beginLine = false; + if ("case".equals(lcToken)) { + indent++; + } + } + } + + private void white() { + if (!beginLine) { + result.append(" "); + } + } + + private void updateOrInsertOrDelete() { + out(); + indent++; + beginLine = false; + if ("update".equals(lcToken)) { + newline(); + } + if ("insert".equals(lcToken)) { + afterInsert = true; + } + } + + private void select() { + out(); + indent++; + newline(); + parenCounts.addLast(parensSinceSelect); + afterByOrFromOrSelects.addLast(afterByOrSetOrFromOrSelect); + parensSinceSelect = 0; + afterByOrSetOrFromOrSelect = true; + } + + private void out() { + result.append(token); + } + + private void endNewClause() { + if (!afterBeginBeforeEnd) { + indent--; + if (afterOn) { + indent--; + afterOn = false; + } + newline(); + } + out(); + if (!"union".equals(lcToken)) { + indent++; + } + newline(); + afterBeginBeforeEnd = false; + afterByOrSetOrFromOrSelect = "by".equals(lcToken) + || "set".equals(lcToken) + || "from".equals(lcToken); + } + + private void beginNewClause() { + if (!afterBeginBeforeEnd) { + if (afterOn) { + indent--; + afterOn = false; + } + indent--; + newline(); + } + out(); + beginLine = false; + afterBeginBeforeEnd = true; + } + + private void values() { + indent--; + newline(); + out(); + indent++; + newline(); + afterValues = true; + } + + private void closeParen() { + parensSinceSelect--; + if (parensSinceSelect < 0) { + indent--; + parensSinceSelect = parenCounts.removeLast(); + afterByOrSetOrFromOrSelect = afterByOrFromOrSelects.removeLast(); + } + if (inFunction > 0) { + inFunction--; + out(); + } else { + if (!afterByOrSetOrFromOrSelect) { + indent--; + newline(); + } + out(); + } + beginLine = false; + } + + private void openParen() { + if (isFunctionName(lastToken) || inFunction > 0) { + inFunction++; + } + beginLine = false; + if (inFunction > 0) { + out(); + } else { + out(); + if (!afterByOrSetOrFromOrSelect) { + indent++; + newline(); + beginLine = true; + } + } + parensSinceSelect++; + } + + private static boolean isFunctionName(String tok) { + if (tok == null || tok.length() == 0) { + return false; + } + + final char begin = tok.charAt(0); + final boolean isIdentifier = Character.isJavaIdentifierStart(begin) || '"' == begin; + return isIdentifier && + !LOGICAL.contains(tok) && + !END_CLAUSES.contains(tok) && + !QUANTIFIERS.contains(tok) && + !DML.contains(tok) && + !MISC.contains(tok); + } + + private static boolean isWhitespace(String token) { + return WHITESPACE.contains(token); + } + + private void newline() { + result.append(System.lineSeparator()); + for (int i = 0; i < indent; i++) { + result.append(INDENT_STRING); + } + beginLine = true; + } + } + +} \ 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 d175058..cbdb6ba 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -64,12 +64,12 @@ - - - - - - + + + Date: Thu, 1 Jul 2021 15:21:22 +0800 Subject: [PATCH 05/41] =?UTF-8?q?=E5=B7=A5=E5=85=B7=E8=BF=81=E7=A7=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../idea/plugins/tool/ConvertType.java | 127 ++++++++++ .../idea/plugins/tool/TextFormatView.java | 223 ++++++++++++++++++ .../plugins/tool/TextHandlerToolWindow.java | 24 ++ .../passerr/idea/plugins/tool/ToolMenu.java | 90 +++++++ src/main/resources/META-INF/plugin.xml | 14 +- 5 files changed, 472 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/github/passerr/idea/plugins/tool/ConvertType.java create mode 100644 src/main/java/com/github/passerr/idea/plugins/tool/TextFormatView.java create mode 100644 src/main/java/com/github/passerr/idea/plugins/tool/TextHandlerToolWindow.java create mode 100644 src/main/java/com/github/passerr/idea/plugins/tool/ToolMenu.java diff --git a/src/main/java/com/github/passerr/idea/plugins/tool/ConvertType.java b/src/main/java/com/github/passerr/idea/plugins/tool/ConvertType.java new file mode 100644 index 0000000..55b8a3c --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/tool/ConvertType.java @@ -0,0 +1,127 @@ +package com.github.passerr.idea.plugins.tool; + +import com.github.passerr.idea.plugins.NotificationThread; +import com.github.passerr.idea.plugins.mybatis.LogParser; +import com.github.passerr.idea.plugins.mybatis.SqlFormatter; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import com.google.gson.JsonSyntaxException; +import com.intellij.notification.Notification; +import com.intellij.ui.JBColor; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; +import org.apache.commons.lang.StringUtils; +import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; +import org.fife.ui.rsyntaxtextarea.SyntaxConstants; +import org.fife.ui.rtextarea.RTextArea; + +import javax.swing.text.BadLocationException; +import java.awt.event.MouseEvent; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static com.intellij.notification.NotificationType.ERROR; + +/** + * 转换类型 + * @author xiehai1* @date 2018/11/08 18:33 + * @Copyright (c) gome inc Gome Co.,LTD + */ +@FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true) +@RequiredArgsConstructor +enum ConvertType { + /** + * 自动格式化json + */ + JSON(SyntaxConstants.SYNTAX_STYLE_JSON) { + @Override + void handle(RSyntaxTextArea input, RSyntaxTextArea output) { + try { + JsonParser jsonParser = new JsonParser(); + // 可以同时解析数组或者Object + JsonElement element = jsonParser.parse(input.getText()); + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + output.setText(gson.toJson(element)); + // 格式化成功后定位到第一行 + output.setCaretPosition(0); + } catch (JsonSyntaxException ex) { + String msg = ex.getMessage(); + Matcher matcher = ERROR_PATTERN.matcher(msg); + if (matcher.find()) { + // 行索引从0开始 + int lineIndex = (Integer.parseInt(matcher.group(1))) - 1; + // 设置错误行背景色 + try { + input.addLineHighlight(lineIndex, JBColor.RED); + } catch (BadLocationException ignore) { + } + // 设置提示信息 + input.setToolTipSupplier((RTextArea rt, MouseEvent me) -> { + int offset = 0; + try { + offset = input.getLineOfOffset(input.viewToModel(me.getPoint())); + } catch (BadLocationException ignore) { + } + return offset == lineIndex ? msg : null; + }); + // 定位到失败行 + input.setCaretPosition(lineIndex); + } + new NotificationThread(new Notification("Json Format", "Json Format", msg, ERROR)).start(); + } + } + }, + /** + * mybatis日志转可执行sql + */ + SQL(SyntaxConstants.SYNTAX_STYLE_SQL) { + @Override + void handle(RSyntaxTextArea input, RSyntaxTextArea output) { + String sql = LogParser.toSql(input.getText()); + if (StringUtils.isEmpty(sql)) { + // 设置错误行背景色 + // 默认设置第一行 + try { + input.addLineHighlight(0, JBColor.RED); + } catch (BadLocationException ignore) { + } + // 设置提示信息 + input.setToolTipSupplier((RTextArea rt, MouseEvent me) -> { + int offset = 0; + try { + offset = input.getLineOfOffset(input.viewToModel(me.getPoint())); + } catch (BadLocationException ignore) { + } + return offset == 0 ? "the log you input which without \"Preparing:\" or \"Parameters:\"" : null; + }); + } else { + output.setText(SqlFormatter.format(sql)); + // 格式化成功后定位到第一行 + output.setCaretPosition(0); + } + } + }, + /** + * 文本 + */ + TEXT(SyntaxConstants.SYNTAX_STYLE_NONE); + + String style; + + /** + * json错误信息正则匹配 + */ + private static final Pattern ERROR_PATTERN = Pattern.compile("line (\\w+) column"); + + /** + * 对文本进行格式化 + * @param input 输入文本域 + * @param output 输出文本域 + */ + void handle(RSyntaxTextArea input, RSyntaxTextArea output) { + // 默认不做任何处理 + } +} diff --git a/src/main/java/com/github/passerr/idea/plugins/tool/TextFormatView.java b/src/main/java/com/github/passerr/idea/plugins/tool/TextFormatView.java new file mode 100644 index 0000000..b47ceb6 --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/tool/TextFormatView.java @@ -0,0 +1,223 @@ +package com.github.passerr.idea.plugins.tool; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.ComboBox; +import org.apache.commons.lang.StringUtils; +import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; +import org.fife.ui.rsyntaxtextarea.Theme; +import org.fife.ui.rtextarea.RTextScrollPane; + +import javax.swing.AbstractAction; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JPanel; +import javax.swing.JRootPane; +import javax.swing.KeyStroke; +import java.awt.FlowLayout; +import java.awt.event.ActionEvent; +import java.awt.event.InputEvent; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.awt.event.KeyEvent; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * 格式化视图 + * @author xiehai1* @Copyright tellyes tech. inc. co.,ltd + * @date 2018/10/12 16:02 + */ +public class TextFormatView extends JRootPane { + Project project; + /** + * 菜单 + */ + ToolMenu menu; + /** + * 输入文本域 + */ + RSyntaxTextArea inputTextArea = new RSyntaxTextArea(19, 0); + /** + * 输出文本域 + */ + RSyntaxTextArea outputTextArea = new RSyntaxTextArea(34, 0); + /** + * cache instance by project + */ + private static final Map INSTANCES = new HashMap<>(); + + private TextFormatView(Project project) { + this.project = project; + super.getContentPane().setLayout(new BoxLayout(super.getContentPane(), BoxLayout.Y_AXIS)); + // 初始化输入文本域 + this.doInitInputTextArea(); + // 初始化菜单 + this.doInitMenu(); + // 初始输出化文本域 + this.doInitOutputTextArea(); + // 设置高亮主题 + try (InputStream inputStream = this.getClass().getResourceAsStream( + "/org/fife/ui/rsyntaxtextarea/themes/idea.xml")) { + Theme theme = Theme.load(inputStream); + theme.apply(this.inputTextArea); + theme.apply(this.outputTextArea); + } catch (IOException ignore) { + } + } + + /** + * 输入文本域初始化 + */ + private void doInitInputTextArea() { + this.inputTextArea.getInputMap(WHEN_FOCUSED) + .put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, InputEvent.CTRL_MASK), "format"); + this.inputTextArea.getActionMap() + .put( + "format", + new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + TextFormatView.this.doFormat(); + } + } + ); + super.getContentPane().add(new RTextScrollPane(this.inputTextArea)); + } + + /** + * 输出文本域初始化 + */ + private void doInitOutputTextArea() { + this.outputTextArea.setCodeFoldingEnabled(true); + this.outputTextArea.setAutoIndentEnabled(true); + super.getContentPane().add(new RTextScrollPane(this.outputTextArea)); + } + + /** + * 菜单初始化 + */ + private void doInitMenu() { + ItemListener subMenuItemListener = e -> { + if (e.getStateChange() == ItemEvent.SELECTED) { + switchMenu((ToolMenu) e.getItem()); + } + }; + // 编/解码 + JComboBox encodeMenu = new ComboBox<>(new ToolMenu[]{ToolMenu.URL_ENCODE, ToolMenu.URL_DECODE}); + encodeMenu.addItemListener(subMenuItemListener); + encodeMenu.setVisible(false); + + // 加/解密 + JComboBox encryptMenu = new ComboBox<>(new ToolMenu[]{ + ToolMenu.MD5_ENCRYPTION, + ToolMenu.BASE64_ENCRYPTION, + ToolMenu.BASE64_DECRYPTION + }); + encryptMenu.addItemListener(subMenuItemListener); + encryptMenu.setVisible(false); + + // 主菜单 + JComboBox mainMenu = new ComboBox<>(new ToolMenu[]{ + ToolMenu.JSON, + ToolMenu.SQL, + ToolMenu.ENCODE, + ToolMenu.ENCRYPT + }); + + mainMenu.addItemListener(e -> { + if (e.getStateChange() == ItemEvent.SELECTED) { + ToolMenu toolMenu = (ToolMenu) e.getItem(); + switch (toolMenu) { + case ENCODE: { + encryptMenu.setVisible(false); + encodeMenu.setVisible(true); + encodeMenu.setSelectedIndex(0); + switchMenu((ToolMenu) encodeMenu.getSelectedItem()); + break; + } + case ENCRYPT: { + encodeMenu.setVisible(false); + encryptMenu.setVisible(true); + encryptMenu.setSelectedIndex(0); + switchMenu((ToolMenu) encryptMenu.getSelectedItem()); + break; + } + default: { + encodeMenu.setVisible(false); + encryptMenu.setVisible(false); + // 菜单切换 + switchMenu(toolMenu); + break; + } + } + } + }); + // 默认选中第一个 + mainMenu.setSelectedItem(0); + this.switchMenu((ToolMenu) mainMenu.getSelectedItem()); + + // 转换按钮 + JButton format = new JButton("转换"); + format.addActionListener(e -> doFormat()); + JPanel panel = new JPanel(new FlowLayout(FlowLayout.CENTER)); + panel.add(mainMenu); + panel.add(encodeMenu); + panel.add(encryptMenu); + panel.add(format); + + super.getContentPane().add(panel); + } + + /** + * 菜单切换操作 + * @param menu 菜单 + */ + private void switchMenu(ToolMenu menu) { + this.menu = menu; + if (Objects.nonNull(this.inputTextArea)) { + this.inputTextArea.setToolTipSupplier(null); + this.inputTextArea.removeAllLineHighlights(); + if (Objects.nonNull(menu.type)) { + this.outputTextArea.setSyntaxEditingStyle(menu.type.style); + } + } + } + + /** + * 格式化动作 + */ + private void doFormat() { + // 文本框为空 不进行格式化 + if (StringUtils.isEmpty(this.inputTextArea.getText())) { + return; + } + try { + this.inputTextArea.setToolTipSupplier(null); + this.inputTextArea.removeAllLineHighlights(); + this.menu.handle(this.inputTextArea, this.outputTextArea); + } finally { + this.getContentPane().repaint(); + } + } + + /** + * 按照{@link Project}单例 + * @param project idea 项目 + * @return {@link TextFormatView} + */ + static TextFormatView getInstance(Project project) { + if (!INSTANCES.containsKey(project)) { + synchronized (TextFormatView.class) { + if (!INSTANCES.containsKey(project)) { + INSTANCES.putIfAbsent(project, new TextFormatView(project)); + } + } + } + + return INSTANCES.get(project); + } +} diff --git a/src/main/java/com/github/passerr/idea/plugins/tool/TextHandlerToolWindow.java b/src/main/java/com/github/passerr/idea/plugins/tool/TextHandlerToolWindow.java new file mode 100644 index 0000000..41276eb --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/tool/TextHandlerToolWindow.java @@ -0,0 +1,24 @@ +package com.github.passerr.idea.plugins.tool; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.wm.ToolWindow; +import com.intellij.openapi.wm.ToolWindowFactory; +import com.intellij.ui.content.ContentManager; +import org.jetbrains.annotations.NotNull; + +/** + * 字符串处理窗口 + * @author xiehai + * @date 2021/07/01 15:18 + * @Copyright(c) tellyes tech. inc. co.,ltd + */ +public class TextHandlerToolWindow implements ToolWindowFactory { + @Override + public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) { + ContentManager contentManager = toolWindow.getContentManager(); + contentManager.removeAllContents(true); + contentManager.addContent( + contentManager.getFactory().createContent(TextFormatView.getInstance(project), "", true) + ); + } +} diff --git a/src/main/java/com/github/passerr/idea/plugins/tool/ToolMenu.java b/src/main/java/com/github/passerr/idea/plugins/tool/ToolMenu.java new file mode 100644 index 0000000..a7fb211 --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/tool/ToolMenu.java @@ -0,0 +1,90 @@ +package com.github.passerr.idea.plugins.tool; + +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.codec.digest.DigestUtils; +import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + +/** + * 菜单 + * @author xiehai + * @Copyright (c)tellyes tech. inc. co.,ltd + * @date 2019/11/27 09:53 + */ +@FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true) +@RequiredArgsConstructor +enum ToolMenu { + /** + * main menu + */ + JSON("Json格式化", ConvertType.JSON), + SQL("Mybatis日志转Sql", ConvertType.SQL), + ENCODE("编/解码", null), + ENCRYPT("加/解密", null), + + /** + * sub menu + */ + URL_DECODE("url解码", ConvertType.TEXT) { + @Override + void handle(RSyntaxTextArea input, RSyntaxTextArea output) { + try { + output.setText(URLDecoder.decode(input.getText(), StandardCharsets.UTF_8.name())); + } catch (UnsupportedEncodingException ignore) { + } + } + }, + URL_ENCODE("url编码", ConvertType.TEXT) { + @Override + void handle(RSyntaxTextArea input, RSyntaxTextArea output) { + try { + output.setText(URLEncoder.encode(input.getText(), StandardCharsets.UTF_8.name())); + } catch (UnsupportedEncodingException ignore) { + } + } + }, + MD5_ENCRYPTION("md5加密", ConvertType.TEXT) { + @Override + void handle(RSyntaxTextArea input, RSyntaxTextArea output) { + output.setText(DigestUtils.md5Hex(input.getText())); + } + }, + BASE64_DECRYPTION("base64解密", ConvertType.TEXT) { + @Override + void handle(RSyntaxTextArea input, RSyntaxTextArea output) { + output.setText(new String(Base64.decodeBase64(input.getText()))); + } + }, + BASE64_ENCRYPTION("base64加密", ConvertType.TEXT) { + @Override + void handle(RSyntaxTextArea input, RSyntaxTextArea output) { + output.setText(new String(Base64.encodeBase64(input.getText().getBytes()))); + } + }; + + String name; + ConvertType type; + + + @Override + public String toString() { + return this.name; + } + + /** + * 默认文本域操作 + * @param input 输入文本域 + * @param output 输出文本域 + */ + void handle(RSyntaxTextArea input, RSyntaxTextArea output) { + // 默认对应类型格式化 + this.type.handle(input, output); + } +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index cbdb6ba..6ba2c3d 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -76,15 +76,17 @@ text="Copy Method Rest Path"> - - - - - - + + + + + + + -- Gitee From 2d26746ea7dc2d018bebe329f8a1a60f9da2700f Mon Sep 17 00:00:00 2001 From: PasseRR Date: Thu, 1 Jul 2021 15:37:40 +0800 Subject: [PATCH 06/41] =?UTF-8?q?api=E6=96=87=E6=A1=A3=E5=A4=8D=E5=88=B6?= =?UTF-8?q?=E5=8A=A8=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../spring/web/CopyMethodApiDocAction.java | 144 ++++++++++++++++++ src/main/resources/META-INF/plugin.xml | 12 +- 2 files changed, 150 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.java diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.java new file mode 100644 index 0000000..5f65c1b --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.java @@ -0,0 +1,144 @@ +package com.github.passerr.idea.plugins.spring.web; + +import com.intellij.codeInsight.AnnotationUtil; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.psi.PsiAnnotation; +import com.intellij.psi.PsiMethod; +import com.intellij.psi.javadoc.PsiDocComment; +import com.intellij.psi.javadoc.PsiDocToken; +import lombok.AllArgsConstructor; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * web方法接口文档复制动作 + * @author xiehai + * @date 2021/07/01 15:21 + * @Copyright(c) tellyes tech. inc. co.,ltd + */ +public class CopyMethodApiDocAction extends BaseWebCopyAction { + @Override + public void actionPerformed(AnActionEvent e) { + PsiMethod method = method(e); + PsiAnnotation classAnnotation = classAnnotation(method); + PsiAnnotation methodAnnotation = methodAnnotation(method); + String url = url(classAnnotation, methodAnnotation); + String httpMethod = getMethod(classAnnotation, methodAnnotation); + List pathVariables = pathVariables(method); + // 路径参数列表 + // 查询参数列表 + // body + // 请求示例 + // 应答示例 + } + + /** + * 获取http方法 + * @param classAnnotation 类上的注解 + * @param methodAnnotation 方法上的注解 + * @return http方法类型 + */ + private static String getMethod(PsiAnnotation classAnnotation, PsiAnnotation methodAnnotation) { + if (!REQUEST_MAPPING.equals(methodAnnotation.getQualifiedName())) { + return MAPPINGS.get(methodAnnotation.getQualifiedName()); + } + + Object methodOnMethod = PsiAnnotationMemberValueUtil.getArrayFirstValue(methodAnnotation, "method"); + if (Objects.nonNull(methodOnMethod)) { + return String.valueOf(methodOnMethod); + } + + Object methodOnClass = PsiAnnotationMemberValueUtil.getArrayFirstValue(classAnnotation, "method"); + if (Objects.nonNull(methodOnClass)) { + return String.valueOf(methodOnClass); + } + + return "UNKNOWN"; + } + + /** + * 获取接口路径参数 + * @param method {@link PsiMethod} + * @return 路径参数列表 + */ + private static List pathVariables(PsiMethod method) { + Map comments = new HashMap<>(4); + Optional.ofNullable(method.getDocComment()) + .map(PsiDocComment::getTags) + .filter(it -> it.length > 0) + .map(Arrays::asList) + // 方法注释tag列表 + .ifPresent(tags -> + tags.stream() + .filter(Objects::nonNull) + .filter(it -> "param".equals(it.getName())) + .forEach(it -> + comments.put( + it.getName(), + Arrays.stream(it.getDataElements()) + .filter(e -> e instanceof PsiDocToken) + .map(PsiDocToken.class::cast) + .filter(e -> "DOC_COMMENT_DATA".equals(e.getTokenType().toString())) + .map(e -> e.getText().trim()) + .collect(Collectors.joining("")) + ) + ) + ); + + return + Arrays.stream(method.getParameterList().getParameters()) + .map(it -> { + PsiAnnotation annotation = AnnotationUtil.findAnnotation( + it, WebCopyConstants.PATH_VARIABLE_ANNOTATION); + if (Objects.nonNull(annotation)) { + String type = it.getType().getCanonicalText(); + return + new Var( + Optional.ofNullable(PsiAnnotationMemberValueUtil.value(annotation, "value")) + .map(String::valueOf) + .orElseGet(it::getText), + type, + ApiDocStateComponent.getInstance().alias(type), + comments.getOrDefault(it.getText(), it.getText()) + ); + } + + return null; + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + private static List queryParams(PsiMethod method) { + return null; + } + + /** + * 参数实体 + */ + @AllArgsConstructor + private static class Var { + /** + * 参数名 + */ + String name; + /** + * 类型 + */ + String type; + /** + * 类型别名 + */ + String alias; + /** + * 参数描述 + */ + String desc; + } +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 6ba2c3d..7f074fd 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -76,12 +76,12 @@ text="Copy Method Rest Path"> - - - - - - + + + -- Gitee From f96435fb577435992741ca5fad83883714ab8c97 Mon Sep 17 00:00:00 2001 From: PasseRR Date: Thu, 1 Jul 2021 19:15:25 +0800 Subject: [PATCH 07/41] =?UTF-8?q?=E6=A8=A1=E7=89=88=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../idea/plugins/IdeaPanelWithButtons.java | 19 ++- .../plugins/spring/web/ApiDocConfigViews.java | 108 +++++++++++---- .../spring/web/ApiDocConfigurable.java | 6 + .../spring/web/po/ApiDocSettingPo.java | 1 + src/main/resources/api-doc-desc.html | 125 ++++++++++++++++++ 5 files changed, 234 insertions(+), 25 deletions(-) create mode 100644 src/main/resources/api-doc-desc.html diff --git a/src/main/java/com/github/passerr/idea/plugins/IdeaPanelWithButtons.java b/src/main/java/com/github/passerr/idea/plugins/IdeaPanelWithButtons.java index 6c46b57..f67a2d0 100644 --- a/src/main/java/com/github/passerr/idea/plugins/IdeaPanelWithButtons.java +++ b/src/main/java/com/github/passerr/idea/plugins/IdeaPanelWithButtons.java @@ -1,6 +1,9 @@ package com.github.passerr.idea.plugins; import com.intellij.ui.PanelWithButtons; +import org.jetbrains.annotations.Nullable; + +import javax.swing.JButton; /** * {@link PanelWithButtons}重写 @@ -10,8 +13,22 @@ import com.intellij.ui.PanelWithButtons; * @see PanelWithButtons */ public abstract class IdeaPanelWithButtons extends PanelWithButtons { - public IdeaPanelWithButtons() { + String title; + + public IdeaPanelWithButtons(String title) { super(); + this.title = title; super.initPanel(); } + + @Nullable + @Override + protected String getLabelText() { + return this.title; + } + + @Override + protected JButton[] createButtons() { + return new JButton[0]; + } } diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java index f7546e2..fe24775 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java @@ -4,16 +4,35 @@ import com.github.passerr.idea.plugins.BaseTableModel; import com.github.passerr.idea.plugins.IdeaJbTable; import com.github.passerr.idea.plugins.IdeaPanelWithButtons; import com.github.passerr.idea.plugins.spring.web.po.ApiDocSettingPo; +import com.google.common.io.CharStreams; +import com.intellij.codeInsight.template.TemplateContextType; +import com.intellij.codeInsight.template.impl.TemplateEditorUtil; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.EditorFactory; +import com.intellij.openapi.editor.EditorSettings; +import com.intellij.openapi.editor.event.DocumentAdapter; +import com.intellij.openapi.editor.event.DocumentEvent; import com.intellij.openapi.util.Pair; +import com.intellij.ui.BrowserHyperlinkListener; import com.intellij.ui.PanelWithButtons; import com.intellij.ui.ToolbarDecorator; import com.intellij.ui.table.JBTable; import com.intellij.util.ui.JBUI; +import com.intellij.util.ui.UIUtil; -import javax.swing.*; -import java.awt.*; +import javax.swing.JComponent; +import javax.swing.JEditorPane; +import javax.swing.JPanel; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collections; +import java.util.List; /** * api文档配置视图 @@ -27,32 +46,83 @@ public abstract class ApiDocConfigViews { * @param setting {@link ApiDocSettingPo} * @return {@link List} */ - public static java.util.List> panels(ApiDocSettingPo setting) { + public static List> panels(ApiDocSettingPo setting) { return Arrays.asList( - Pair.pair("Api模版", new JPanel()), + Pair.pair("Api模版", apiTemplatePanel(setting)), Pair.pair("查询参数", queryParamPanel(setting)) ); } /** - * 查询参数视图 + * api模版视图 * @param setting {@link ApiDocSettingPo} * @return {@link JPanel} */ - private static JPanel queryParamPanel(ApiDocSettingPo setting) { + private static JPanel apiTemplatePanel(ApiDocSettingPo setting) { JPanel panel = new JPanel(new GridBagLayout()); - PanelWithButtons top = new IdeaPanelWithButtons() { + EditorFactory editorFactory = EditorFactory.getInstance(); + Document document = editorFactory.createDocument(setting.getTemplate()); + document.addDocumentListener(new DocumentAdapter() { @Override - protected String getLabelText() { - return "忽略类型"; + public void documentChanged(DocumentEvent e) { + setting.setTemplate(e.getDocument().getText()); } + }); + Editor editor = editorFactory.createEditor(document); + TemplateEditorUtil.setHighlighter(editor, (TemplateContextType) null); + EditorSettings editorSettings = editor.getSettings(); + editorSettings.setVirtualSpace(false); + editorSettings.setLineMarkerAreaShown(false); + editorSettings.setIndentGuidesShown(true); + editorSettings.setLineNumbersShown(true); + editorSettings.setFoldingOutlineShown(false); + editorSettings.setAdditionalColumnsCount(3); + editorSettings.setAdditionalLinesCount(3); + editorSettings.setCaretRowShown(false); - @Override - protected JButton[] createButtons() { - return new JButton[0]; - } + JEditorPane desc = new JEditorPane(UIUtil.HTML_MIME, ""); + desc.setEditable(false); + desc.addHyperlinkListener(new BrowserHyperlinkListener()); + try (InputStream resourceAsStream = ApiDocConfigViews.class.getResourceAsStream("/api-doc-desc.html")) { + desc.setText(CharStreams.toString(new InputStreamReader(resourceAsStream, StandardCharsets.UTF_8))); + } catch (IOException ignore) { + } + desc.setCaretPosition(0); + + panel.add( + editor.getComponent(), + new GridBagConstraints( + 0, 0, 1, 1, 1, 1, + GridBagConstraints.NORTH, + GridBagConstraints.BOTH, + JBUI.emptyInsets(), 0, 0 + ) + ); + + panel.add( + desc, + new GridBagConstraints( + 0, 1, 1, 1, 1, 1, + GridBagConstraints.NORTH, + GridBagConstraints.BOTH, + JBUI.emptyInsets(), 0, 0 + ) + ); + + // 编辑模块 + // 描述模块 + return panel; + } + /** + * 查询参数视图 + * @param setting {@link ApiDocSettingPo} + * @return {@link JPanel} + */ + private static JPanel queryParamPanel(ApiDocSettingPo setting) { + JPanel panel = new JPanel(new GridBagLayout()); + PanelWithButtons top = new IdeaPanelWithButtons("忽略类型") { @Override protected JComponent createMainComponent() { BaseTableModel model = new BaseTableModel<>( @@ -71,17 +141,7 @@ public abstract class ApiDocConfigViews { .createPanel(); } }; - PanelWithButtons bottom = new IdeaPanelWithButtons() { - @Override - protected String getLabelText() { - return "忽略注解"; - } - - @Override - protected JButton[] createButtons() { - return new JButton[0]; - } - + PanelWithButtons bottom = new IdeaPanelWithButtons("忽略注解") { @Override protected JComponent createMainComponent() { BaseTableModel model = new BaseTableModel<>( diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigurable.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigurable.java index 8a7c93e..7dc96ff 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigurable.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigurable.java @@ -12,6 +12,7 @@ import com.intellij.ui.tabs.TabInfo; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import javax.annotation.Generated; import javax.swing.JComponent; import javax.swing.JPanel; import java.util.List; @@ -56,6 +57,10 @@ public class ApiDocConfigurable implements SearchableConfigurable, Configurable. if (Objects.isNull(selectedInfo)) { return; } + // 切换tab自动保存 + if (this.isModified()) { + this.apply(); + } String tab = selectedInfo.getText(); panels.stream() .filter(it -> Objects.equals(it.getFirst(), tab)) @@ -83,6 +88,7 @@ public class ApiDocConfigurable implements SearchableConfigurable, Configurable. } } + @Generated({}) @Override public void disposeUIResources() { if (this.disposable != null) { diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java index 0bb7723..2667fed 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java @@ -14,6 +14,7 @@ import java.util.List; import java.util.stream.Collectors; /** + * 配置持久化po * @author xiehai * @date 2021/06/30 19:30 * @Copyright(c) tellyes tech. inc. co.,ltd diff --git a/src/main/resources/api-doc-desc.html b/src/main/resources/api-doc-desc.html new file mode 100644 index 0000000..3b0a7c4 --- /dev/null +++ b/src/main/resources/api-doc-desc.html @@ -0,0 +1,125 @@ + + + + + + +
中文Along with static text, code and comments, you can also use + predefined variables (listed below) that will then be expanded like macros into the corresponding + values.
+ It is also possible to specify an arbitrary number of custom variables in the format + ${<VARIABLE_NAME>}. In this case, before the new + file is created, you will be prompted with a dialog where you can define particular values for all + custom variables.
+ Using the #parse directive, you can include templates from the Includes + tab, by specifying the full name of the desired template as a parameter in quotation marks. + For example:
+ #parse("File Header.java") +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Predefined variables will take the following values:
+ ${PACKAGE_NAME} +  name of the package in which the new file is + created
+ ${NAME} +  name of the new file specified by you in the New + <TEMPLATE_NAME> dialog
+ ${USER} +  current user system login name
+ ${DATE} +  current system date
+ ${TIME} +  current system time
+ ${YEAR} +  current year
+ ${MONTH} +  current month
+ ${MONTH_NAME_SHORT} +  first 3 letters of the current month name. Example: + Jan, Feb, etc.
+ ${MONTH_NAME_FULL} +  full name of the current month. Example: January, + February, etc.
+ ${DAY} +  current day of the month
+ ${HOUR} +  current hour
+ ${MINUTE} +  current minute
+ ${PROJECT_NAME} +  the name of the current project
+
+ + Apache Velocity template language is used + + + \ No newline at end of file -- Gitee From e8b6322dad918e2ad738e57e7f835e9d0d34e2b4 Mon Sep 17 00:00:00 2001 From: PasseRR Date: Fri, 2 Jul 2021 17:41:13 +0800 Subject: [PATCH 08/41] =?UTF-8?q?=E6=B7=BB=E5=8A=A0velocity=E8=AF=AD?= =?UTF-8?q?=E6=B3=95=E9=AB=98=E4=BA=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugins/spring/web/ApiDocConfigViews.java | 71 ++- .../spring/web/ApiDocConfigurable.java | 13 +- .../web/highlight/FileTemplateTextLexer.java | 511 ++++++++++++++++++ .../web/highlight/FileTemplateTokenType.java | 16 + .../web/highlight/TemplateHighlighter.java | 42 ++ .../spring/web/po/ApiDocAliasPairPo.java | 4 +- .../spring/web/po/ApiDocSettingPo.java | 61 ++- .../spring/web/po/StringBuilderConverter.java | 25 + 8 files changed, 703 insertions(+), 40 deletions(-) create mode 100644 src/main/java/com/github/passerr/idea/plugins/spring/web/highlight/FileTemplateTextLexer.java create mode 100644 src/main/java/com/github/passerr/idea/plugins/spring/web/highlight/FileTemplateTokenType.java create mode 100644 src/main/java/com/github/passerr/idea/plugins/spring/web/highlight/TemplateHighlighter.java create mode 100644 src/main/java/com/github/passerr/idea/plugins/spring/web/po/StringBuilderConverter.java diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java index fe24775..33dea26 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java @@ -3,19 +3,29 @@ package com.github.passerr.idea.plugins.spring.web; import com.github.passerr.idea.plugins.BaseTableModel; import com.github.passerr.idea.plugins.IdeaJbTable; import com.github.passerr.idea.plugins.IdeaPanelWithButtons; +import com.github.passerr.idea.plugins.spring.web.highlight.FileTemplateTokenType; +import com.github.passerr.idea.plugins.spring.web.highlight.TemplateHighlighter; import com.github.passerr.idea.plugins.spring.web.po.ApiDocSettingPo; import com.google.common.io.CharStreams; -import com.intellij.codeInsight.template.TemplateContextType; -import com.intellij.codeInsight.template.impl.TemplateEditorUtil; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.EditorFactory; import com.intellij.openapi.editor.EditorSettings; +import com.intellij.openapi.editor.colors.EditorColorsManager; +import com.intellij.openapi.editor.colors.EditorColorsScheme; import com.intellij.openapi.editor.event.DocumentAdapter; import com.intellij.openapi.editor.event.DocumentEvent; +import com.intellij.openapi.editor.ex.EditorEx; +import com.intellij.openapi.editor.ex.util.LayerDescriptor; +import com.intellij.openapi.editor.ex.util.LayeredLexerEditorHighlighter; +import com.intellij.openapi.fileTypes.FileTypes; +import com.intellij.openapi.fileTypes.SyntaxHighlighter; +import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory; import com.intellij.openapi.util.Pair; import com.intellij.ui.BrowserHyperlinkListener; import com.intellij.ui.PanelWithButtons; +import com.intellij.ui.ScrollPaneFactory; +import com.intellij.ui.SeparatorFactory; import com.intellij.ui.ToolbarDecorator; import com.intellij.ui.table.JBTable; import com.intellij.util.ui.JBUI; @@ -61,16 +71,17 @@ public abstract class ApiDocConfigViews { */ private static JPanel apiTemplatePanel(ApiDocSettingPo setting) { JPanel panel = new JPanel(new GridBagLayout()); + + // 编辑模块 EditorFactory editorFactory = EditorFactory.getInstance(); Document document = editorFactory.createDocument(setting.getTemplate()); document.addDocumentListener(new DocumentAdapter() { @Override public void documentChanged(DocumentEvent e) { - setting.setTemplate(e.getDocument().getText()); + setting.setStringTemplate(e.getDocument().getText()); } }); Editor editor = editorFactory.createEditor(document); - TemplateEditorUtil.setHighlighter(editor, (TemplateContextType) null); EditorSettings editorSettings = editor.getSettings(); editorSettings.setVirtualSpace(false); editorSettings.setLineMarkerAreaShown(false); @@ -80,7 +91,36 @@ public abstract class ApiDocConfigViews { editorSettings.setAdditionalColumnsCount(3); editorSettings.setAdditionalLinesCount(3); editorSettings.setCaretRowShown(false); + SyntaxHighlighter ohl = SyntaxHighlighterFactory.getSyntaxHighlighter(FileTypes.PLAIN_TEXT, null, null); + final EditorColorsScheme scheme = EditorColorsManager.getInstance().getGlobalScheme(); + LayeredLexerEditorHighlighter highlighter = + new LayeredLexerEditorHighlighter(new TemplateHighlighter(), scheme); + highlighter.registerLayer(FileTemplateTokenType.TEXT, new LayerDescriptor(ohl, "")); + ((EditorEx) editor).setHighlighter(highlighter); + JPanel templatePanel = new JPanel(new GridBagLayout()); + templatePanel.add( + SeparatorFactory.createSeparator("模版:", null), + new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, + JBUI.insetsBottom(2), 0, 0 + ) + ); + templatePanel.add( + editor.getComponent(), + new GridBagConstraints(0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, + JBUI.insetsTop(2), 0, 0 + ) + ); + panel.add( + templatePanel, + new GridBagConstraints( + 0, 0, 1, 1, 1, 1, + GridBagConstraints.NORTH, + GridBagConstraints.BOTH, + JBUI.emptyInsets(), 0, 0 + ) + ); + // 描述模块 JEditorPane desc = new JEditorPane(UIUtil.HTML_MIME, ""); desc.setEditable(false); desc.addHyperlinkListener(new BrowserHyperlinkListener()); @@ -90,18 +130,21 @@ public abstract class ApiDocConfigViews { } desc.setCaretPosition(0); - panel.add( - editor.getComponent(), - new GridBagConstraints( - 0, 0, 1, 1, 1, 1, - GridBagConstraints.NORTH, - GridBagConstraints.BOTH, - JBUI.emptyInsets(), 0, 0 + JPanel descriptionPanel = new JPanel(new GridBagLayout()); + descriptionPanel.add( + SeparatorFactory.createSeparator("描述:", null), + new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, + JBUI.insetsBottom(2), 0, 0 + ) + ); + descriptionPanel.add( + ScrollPaneFactory.createScrollPane(desc), + new GridBagConstraints(0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, + JBUI.insetsTop(2), 0, 0 ) ); - panel.add( - desc, + descriptionPanel, new GridBagConstraints( 0, 1, 1, 1, 1, 1, GridBagConstraints.NORTH, @@ -110,8 +153,6 @@ public abstract class ApiDocConfigViews { ) ); - // 编辑模块 - // 描述模块 return panel; } diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigurable.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigurable.java index 7dc96ff..171ab77 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigurable.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigurable.java @@ -32,7 +32,7 @@ public class ApiDocConfigurable implements SearchableConfigurable, Configurable. ApiDocConfigurable() { this.source = ApiDocStateComponent.getInstance().getState(); assert this.source != null; - this.copy = ApiDocSettingPo.deepCopy(this.source); + this.copy = this.source.deepCopy(); } @Override @@ -65,7 +65,10 @@ public class ApiDocConfigurable implements SearchableConfigurable, Configurable. panels.stream() .filter(it -> Objects.equals(it.getFirst(), tab)) .findFirst() - .ifPresent(it -> it.getSecond().repaint()); + .ifPresent(it -> { + it.getSecond().validate(); + it.getSecond().repaint(); + }); }); return tabbedPanel.getComponent(); @@ -78,14 +81,12 @@ public class ApiDocConfigurable implements SearchableConfigurable, Configurable. @Override public void apply() { - this.source.update(this.copy); + this.source.shallowCopy(this.copy); } @Override public void reset() { - if (this.isModified()) { - this.copy = ApiDocSettingPo.deepCopy(this.source); - } + this.copy.shallowCopy(this.source); } @Generated({}) diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/highlight/FileTemplateTextLexer.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/highlight/FileTemplateTextLexer.java new file mode 100644 index 0000000..fef0819 --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/highlight/FileTemplateTextLexer.java @@ -0,0 +1,511 @@ +package com.github.passerr.idea.plugins.spring.web.highlight; + +/* The following code was generated by JFlex 1.7.0-SNAPSHOT tweaked for IntelliJ platform */ + +/* It's an automatically generated code. Do not modify it. */ + +import com.intellij.lexer.FlexLexer; +import com.intellij.psi.tree.IElementType; + + +/** + * com.intellij.ide.fileTemplates.impl._FileTemplateTextLexer + * This class is a scanner generated by + * JFlex 1.7.0-SNAPSHOT + * from the specification file FileTemplateTextLexer.flex + */ +public class FileTemplateTextLexer implements FlexLexer { + + /** + * This character denotes the end of file + */ + public static final int YYEOF = -1; + + /** + * initial size of the lookahead buffer + */ + private static final int ZZ_BUFFERSIZE = 16384; + + /** + * lexical states + */ + public static final int YYINITIAL = 0; + + /** + * ZZ_LEXSTATE[l] is the state in the DFA for the lexical state l + * ZZ_LEXSTATE[l+1] is the state in the DFA for the lexical state l + * at the beginning of a line + * l is of the form l = 2*k, k a non negative integer + */ + private static final int ZZ_LEXSTATE[] = { + 0, 0 + }; + + /** + * Translates characters to character classes + * Chosen bits are [8, 6, 7] + * Total runtime size is 1040 bytes + */ + public static int ZZ_CMAP(int ch) { + return ZZ_CMAP_A[ZZ_CMAP_Y[ZZ_CMAP_Z[ch >> 13] | ((ch >> 7) & 0x3f)] | (ch & 0x7f)]; + } + + /* The ZZ_CMAP_Z table has 136 entries */ + static final char ZZ_CMAP_Z[] = zzUnpackCMap( + "\1\0\207\100"); + + /* The ZZ_CMAP_Y table has 128 entries */ + static final char ZZ_CMAP_Y[] = zzUnpackCMap( + "\1\0\177\200"); + + /* The ZZ_CMAP_A table has 256 entries */ + static final char ZZ_CMAP_A[] = zzUnpackCMap( + "\43\0\1\6\1\3\13\0\12\2\7\0\32\1\4\0\1\1\1\0\32\1\1\4\1\0\1\5\202\0"); + + /** + * Translates DFA states to action switch labels. + */ + private static final int[] ZZ_ACTION = zzUnpackAction(); + + private static final String ZZ_ACTION_PACKED_0 = + "\1\0\3\1\1\2\1\0\1\3\1\0\1\2"; + + private static int[] zzUnpackAction() { + int[] result = new int[9]; + int offset = 0; + offset = zzUnpackAction(ZZ_ACTION_PACKED_0, offset, result); + return result; + } + + private static int zzUnpackAction(String packed, int offset, int[] result) { + int i = 0; /* index in packed string */ + int j = offset; /* index in unpacked array */ + int l = packed.length(); + while (i < l) { + int count = packed.charAt(i++); + int value = packed.charAt(i++); + do { result[j++] = value; } while (--count > 0); + } + return j; + } + + + /** + * Translates a state to a row index in the transition table + */ + private static final int[] ZZ_ROWMAP = zzUnpackRowMap(); + + private static final String ZZ_ROWMAP_PACKED_0 = + "\0\0\0\7\0\16\0\25\0\34\0\43\0\25\0\52" + + "\0\7"; + + private static int[] zzUnpackRowMap() { + int[] result = new int[9]; + int offset = 0; + offset = zzUnpackRowMap(ZZ_ROWMAP_PACKED_0, offset, result); + return result; + } + + private static int zzUnpackRowMap(String packed, int offset, int[] result) { + int i = 0; /* index in packed string */ + int j = offset; /* index in unpacked array */ + int l = packed.length(); + while (i < l) { + int high = packed.charAt(i++) << 16; + result[j++] = high | packed.charAt(i++); + } + return j; + } + + /** + * The transition table of the DFA + */ + private static final int[] ZZ_TRANS = zzUnpackTrans(); + + private static final String ZZ_TRANS_PACKED_0 = + "\3\2\1\3\2\2\1\4\10\0\2\5\1\0\1\6" + + "\3\0\1\7\6\0\2\5\5\0\2\10\5\0\2\10" + + "\2\0\1\11\1\0"; + + private static int[] zzUnpackTrans() { + int[] result = new int[49]; + int offset = 0; + offset = zzUnpackTrans(ZZ_TRANS_PACKED_0, offset, result); + return result; + } + + private static int zzUnpackTrans(String packed, int offset, int[] result) { + int i = 0; /* index in packed string */ + int j = offset; /* index in unpacked array */ + int l = packed.length(); + while (i < l) { + int count = packed.charAt(i++); + int value = packed.charAt(i++); + value--; + do { result[j++] = value; } while (--count > 0); + } + return j; + } + + + /* error codes */ + private static final int ZZ_UNKNOWN_ERROR = 0; + private static final int ZZ_NO_MATCH = 1; + private static final int ZZ_PUSHBACK_2BIG = 2; + + /* error messages for the codes above */ + private static final String[] ZZ_ERROR_MSG = { + "Unknown internal scanner error", + "Error: could not match input", + "Error: pushback value was too large" + }; + + /** + * ZZ_ATTRIBUTE[aState] contains the attributes of state aState + */ + private static final int[] ZZ_ATTRIBUTE = zzUnpackAttribute(); + + private static final String ZZ_ATTRIBUTE_PACKED_0 = + "\1\0\1\11\3\1\1\0\1\1\1\0\1\11"; + + private static int[] zzUnpackAttribute() { + int[] result = new int[9]; + int offset = 0; + offset = zzUnpackAttribute(ZZ_ATTRIBUTE_PACKED_0, offset, result); + return result; + } + + private static int zzUnpackAttribute(String packed, int offset, int[] result) { + int i = 0; /* index in packed string */ + int j = offset; /* index in unpacked array */ + int l = packed.length(); + while (i < l) { + int count = packed.charAt(i++); + int value = packed.charAt(i++); + do { result[j++] = value; } while (--count > 0); + } + return j; + } + + /** + * the input device + */ + private java.io.Reader zzReader; + + /** + * the current state of the DFA + */ + private int zzState; + + /** + * the current lexical state + */ + private int zzLexicalState = YYINITIAL; + + /** + * this buffer contains the current text to be matched and is + * the source of the yytext() string + */ + private CharSequence zzBuffer = ""; + + /** + * the textposition at the last accepting state + */ + private int zzMarkedPos; + + /** + * the current text position in the buffer + */ + private int zzCurrentPos; + + /** + * startRead marks the beginning of the yytext() string in the buffer + */ + private int zzStartRead; + + /** + * endRead marks the last character in the buffer, that has been read + * from input + */ + private int zzEndRead; + + /** + * zzAtBOL == true <=> the scanner is currently at the beginning of a line + */ + private boolean zzAtBOL = true; + + /** + * zzAtEOF == true <=> the scanner is at the EOF + */ + private boolean zzAtEOF; + + /** + * denotes if the user-EOF-code has already been executed + */ + private boolean zzEOFDone; + + /* user code: */ + public FileTemplateTextLexer() { + this((java.io.Reader) null); + } + + + /** + * Creates a new scanner + * @param in the java.io.Reader to read input from. + */ + FileTemplateTextLexer(java.io.Reader in) { + this.zzReader = in; + } + + + /** + * Unpacks the compressed character translation table. + * @param packed the packed character translation table + * @return the unpacked character translation table + */ + private static char[] zzUnpackCMap(String packed) { + int size = 0; + for (int i = 0, length = packed.length(); i < length; i += 2) { + size += packed.charAt(i); + } + char[] map = new char[size]; + int i = 0; /* index in packed string */ + int j = 0; /* index in unpacked array */ + while (i < packed.length()) { + int count = packed.charAt(i++); + char value = packed.charAt(i++); + do { map[j++] = value; } while (--count > 0); + } + return map; + } + + @Override + public final int getTokenStart() { + return zzStartRead; + } + + @Override + public final int getTokenEnd() { + return getTokenStart() + yylength(); + } + + @Override + public void reset(CharSequence buffer, int start, int end, int initialState) { + zzBuffer = buffer; + zzCurrentPos = zzMarkedPos = zzStartRead = start; + zzAtEOF = false; + zzAtBOL = true; + zzEndRead = end; + yybegin(initialState); + } + + /** + * Refills the input buffer. + * @return false, iff there was new input. + * @throws java.io.IOException if any I/O-Error occurs + */ + private boolean zzRefill() throws java.io.IOException { + return true; + } + + + /** + * Returns the current lexical state. + */ + @Override + public final int yystate() { + return zzLexicalState; + } + + + /** + * Enters a new lexical state + * @param newState the new lexical state + */ + @Override + public final void yybegin(int newState) { + zzLexicalState = newState; + } + + + /** + * Returns the text matched by the current regular expression. + */ + public final CharSequence yytext() { + return zzBuffer.subSequence(zzStartRead, zzMarkedPos); + } + + + /** + * Returns the character at position pos from the + * matched text. + *

+ * It is equivalent to yytext().charAt(pos), but faster + * @param pos the position of the character to fetch. + * A value from 0 to yylength()-1. + * @return the character at position pos + */ + public final char yycharat(int pos) { + return zzBuffer.charAt(zzStartRead + pos); + } + + + /** + * Returns the length of the matched text region. + */ + public final int yylength() { + return zzMarkedPos - zzStartRead; + } + + + /** + * Reports an error that occured while scanning. + *

+ * In a wellformed scanner (no or only correct usage of + * yypushback(int) and a match-all fallback rule) this method + * will only be called with things that "Can't Possibly Happen". + * If this method is called, something is seriously wrong + * (e.g. a JFlex bug producing a faulty scanner etc.). + *

+ * Usual syntax/scanner level error handling should be done + * in error fallback rules. + * @param errorCode the code of the errormessage to display + */ + private void zzScanError(int errorCode) { + String message; + try { + message = ZZ_ERROR_MSG[errorCode]; + } catch (ArrayIndexOutOfBoundsException e) { + message = ZZ_ERROR_MSG[ZZ_UNKNOWN_ERROR]; + } + + throw new Error(message); + } + + + /** + * Pushes the specified amount of characters back into the input stream. + *

+ * They will be read again by then next call of the scanning method + * @param number the number of characters to be read again. + * This number must not be greater than yylength()! + */ + public void yypushback(int number) { + if (number > yylength()) { zzScanError(ZZ_PUSHBACK_2BIG); } + + zzMarkedPos -= number; + } + + + /** + * Resumes scanning until the next regular expression is matched, + * the end of input is encountered or an I/O-Error occurs. + * @return the next token + * @throws java.io.IOException if any I/O-Error occurs + */ + @Override + public IElementType advance() throws java.io.IOException { + int zzInput; + int zzAction; + + // cached fields: + int zzCurrentPosL; + int zzMarkedPosL; + int zzEndReadL = zzEndRead; + CharSequence zzBufferL = zzBuffer; + + int[] zzTransL = ZZ_TRANS; + int[] zzRowMapL = ZZ_ROWMAP; + int[] zzAttrL = ZZ_ATTRIBUTE; + + while (true) { + zzMarkedPosL = zzMarkedPos; + + zzAction = -1; + + zzCurrentPosL = zzCurrentPos = zzStartRead = zzMarkedPosL; + + zzState = ZZ_LEXSTATE[zzLexicalState]; + + // set up zzAction for empty match case: + int zzAttributes = zzAttrL[zzState]; + if ((zzAttributes & 1) == 1) { + zzAction = zzState; + } + + + zzForAction: + { + while (true) { + + if (zzCurrentPosL < zzEndReadL) { + zzInput = Character.codePointAt(zzBufferL, zzCurrentPosL/*, zzEndReadL*/); + zzCurrentPosL += Character.charCount(zzInput); + } else if (zzAtEOF) { + zzInput = YYEOF; + break zzForAction; + } else { + // store back cached positions + zzCurrentPos = zzCurrentPosL; + zzMarkedPos = zzMarkedPosL; + boolean eof = zzRefill(); + // get translated positions and possibly new buffer + zzCurrentPosL = zzCurrentPos; + zzMarkedPosL = zzMarkedPos; + zzBufferL = zzBuffer; + zzEndReadL = zzEndRead; + if (eof) { + zzInput = YYEOF; + break zzForAction; + } else { + zzInput = Character.codePointAt(zzBufferL, zzCurrentPosL/*, zzEndReadL*/); + zzCurrentPosL += Character.charCount(zzInput); + } + } + int zzNext = zzTransL[zzRowMapL[zzState] + ZZ_CMAP(zzInput)]; + if (zzNext == -1) { break zzForAction; } + zzState = zzNext; + + zzAttributes = zzAttrL[zzState]; + if ((zzAttributes & 1) == 1) { + zzAction = zzState; + zzMarkedPosL = zzCurrentPosL; + if ((zzAttributes & 8) == 8) { break zzForAction; } + } + + } + } + + // store back cached position + zzMarkedPos = zzMarkedPosL; + + if (zzInput == YYEOF && zzStartRead == zzCurrentPos) { + zzAtEOF = true; + return null; + } else { + switch (zzAction < 0 ? zzAction : ZZ_ACTION[zzAction]) { + case 1: { + return FileTemplateTokenType.TEXT; + } + case 4: + break; + case 2: { + return FileTemplateTokenType.MACRO; + } + case 5: + break; + case 3: { + return FileTemplateTokenType.DIRECTIVE; + } + case 6: + break; + default: + zzScanError(ZZ_NO_MATCH); + } + } + } + } + + +} diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/highlight/FileTemplateTokenType.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/highlight/FileTemplateTokenType.java new file mode 100644 index 0000000..9bb7eae --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/highlight/FileTemplateTokenType.java @@ -0,0 +1,16 @@ +package com.github.passerr.idea.plugins.spring.web.highlight; + +import com.intellij.lang.Language; +import com.intellij.psi.tree.IElementType; + +/** + * com.intellij.ide.fileTemplates.impl.FileTemplateTokenType + * @author xiehai + * @date 2021/07/02 17:28 + * @Copyright(c) tellyes tech. inc. co.,ltd + */ +public interface FileTemplateTokenType { + IElementType TEXT = new IElementType("TEXT", Language.ANY); + IElementType MACRO = new IElementType("MACRO", Language.ANY); + IElementType DIRECTIVE = new IElementType("DIRECTIVE", Language.ANY); +} diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/highlight/TemplateHighlighter.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/highlight/TemplateHighlighter.java new file mode 100644 index 0000000..85f3de8 --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/highlight/TemplateHighlighter.java @@ -0,0 +1,42 @@ +package com.github.passerr.idea.plugins.spring.web.highlight; + +import com.intellij.codeInsight.template.impl.TemplateColors; +import com.intellij.lexer.FlexAdapter; +import com.intellij.lexer.Lexer; +import com.intellij.lexer.MergingLexerAdapter; +import com.intellij.openapi.editor.colors.TextAttributesKey; +import com.intellij.openapi.fileTypes.SyntaxHighlighterBase; +import com.intellij.psi.tree.IElementType; +import com.intellij.psi.tree.TokenSet; +import org.jetbrains.annotations.NotNull; + +/** + * 模版语法高亮 + * @author xiehai + * @date 2021/07/02 17:29 + * @Copyright(c) tellyes tech. inc. co.,ltd + */ +public class TemplateHighlighter extends SyntaxHighlighterBase { + private final Lexer myLexer; + + public TemplateHighlighter() { + myLexer = new MergingLexerAdapter( + new FlexAdapter(new FileTemplateTextLexer()), TokenSet.create(FileTemplateTokenType.TEXT)); + } + + @NotNull + @Override + public Lexer getHighlightingLexer() { + return myLexer; + } + + @Override + @NotNull + public TextAttributesKey[] getTokenHighlights(IElementType tokenType) { + if (tokenType == FileTemplateTokenType.MACRO || tokenType == FileTemplateTokenType.DIRECTIVE) { + return pack(TemplateColors.TEMPLATE_VARIABLE_ATTRIBUTES); + } + + return EMPTY; + } +} \ No newline at end of file diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocAliasPairPo.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocAliasPairPo.java index ffad9bd..00f7207 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocAliasPairPo.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocAliasPairPo.java @@ -22,7 +22,7 @@ public class ApiDocAliasPairPo { String type; String alias; - public static ApiDocAliasPairPo deepCopy(ApiDocAliasPairPo source) { - return new ApiDocAliasPairPo(source.type, source.alias); + public ApiDocAliasPairPo deepCopy() { + return new ApiDocAliasPairPo(this.type, this.alias); } } diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java index 2667fed..bc6bcc0 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java @@ -2,15 +2,16 @@ package com.github.passerr.idea.plugins.spring.web.po; import com.github.passerr.idea.plugins.spring.web.WebCopyConstants; import com.intellij.util.xmlb.annotations.AbstractCollection; +import com.intellij.util.xmlb.annotations.OptionTag; import com.intellij.util.xmlb.annotations.Tag; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Data; -import lombok.EqualsAndHashCode; import lombok.experimental.FieldDefaults; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; /** @@ -20,12 +21,11 @@ import java.util.stream.Collectors; * @Copyright(c) tellyes tech. inc. co.,ltd */ @Data -@EqualsAndHashCode @FieldDefaults(level = AccessLevel.PRIVATE) @AllArgsConstructor public class ApiDocSettingPo { - @Tag("template") - String template; + @OptionTag(tag = "template", nameAttribute = "", converter = StringBuilderConverter.class) + StringBuilder template; @Tag("all-ignore-types") @AbstractCollection List allIgnoreTypes; @@ -40,7 +40,7 @@ public class ApiDocSettingPo { List aliases; public ApiDocSettingPo() { - this.template = WebCopyConstants.DEFAULT_TEMPLATE; + this.template = new StringBuilder(WebCopyConstants.DEFAULT_TEMPLATE); this.allIgnoreTypes = new ArrayList<>(WebCopyConstants.ALL_IGNORE_TYPES); this.queryParamIgnoreTypes = new ArrayList<>(WebCopyConstants.QUERY_PARAM_IGNORE_TYPES); this.queryParamIgnoreAnnotations = new ArrayList<>(WebCopyConstants.QUERY_PARAM_IGNORE_ANNOTATIONS); @@ -50,22 +50,49 @@ public class ApiDocSettingPo { .collect(Collectors.toList()); } - public static ApiDocSettingPo deepCopy(ApiDocSettingPo source) { + public ApiDocSettingPo deepCopy() { return new ApiDocSettingPo( - source.template, - new ArrayList<>(source.allIgnoreTypes), - new ArrayList<>(source.queryParamIgnoreTypes), - new ArrayList<>(source.queryParamIgnoreAnnotations), - source.aliases.stream().map(ApiDocAliasPairPo::deepCopy).collect(Collectors.toList()) + new StringBuilder(this.template), + new ArrayList<>(this.allIgnoreTypes), + new ArrayList<>(this.queryParamIgnoreTypes), + new ArrayList<>(this.queryParamIgnoreAnnotations), + this.aliases.stream().map(ApiDocAliasPairPo::deepCopy).collect(Collectors.toList()) ); } - public void update(ApiDocSettingPo target) { - this.template = target.template; - this.allIgnoreTypes = target.allIgnoreTypes; - this.queryParamIgnoreTypes = target.queryParamIgnoreTypes; - this.queryParamIgnoreAnnotations = target.queryParamIgnoreAnnotations; - this.aliases = target.aliases; + public void shallowCopy(ApiDocSettingPo source) { + this.template.setLength(0); + this.template.append(source.template); + this.allIgnoreTypes.clear(); + this.allIgnoreTypes.addAll(source.getAllIgnoreTypes()); + this.queryParamIgnoreTypes.clear(); + this.queryParamIgnoreTypes.addAll(source.getQueryParamIgnoreTypes()); + this.queryParamIgnoreAnnotations.clear(); + this.queryParamIgnoreAnnotations.addAll(source.getQueryParamIgnoreAnnotations()); + this.aliases.clear(); + this.aliases.addAll(source.getAliases()); + } + + public void setStringTemplate(String template) { + this.template.setLength(0); + this.template.append(template); + } + + @Override + public boolean equals(Object o) { + if (this == o) { return true; } + if (o == null || getClass() != o.getClass()) { return false; } + ApiDocSettingPo that = (ApiDocSettingPo) o; + return Objects.equals(template.toString(), that.template.toString()) && + Objects.equals(allIgnoreTypes, that.allIgnoreTypes) && + Objects.equals(queryParamIgnoreTypes, that.queryParamIgnoreTypes) && + Objects.equals(queryParamIgnoreAnnotations, that.queryParamIgnoreAnnotations) && + Objects.equals(aliases, that.aliases); + } + + @Override + public int hashCode() { + return Objects.hash(template, allIgnoreTypes, queryParamIgnoreTypes, queryParamIgnoreAnnotations, aliases); } } diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/po/StringBuilderConverter.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/po/StringBuilderConverter.java new file mode 100644 index 0000000..51c4e02 --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/po/StringBuilderConverter.java @@ -0,0 +1,25 @@ +package com.github.passerr.idea.plugins.spring.web.po; + +import com.intellij.util.xmlb.Converter; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * {@link StringBuilder}转换器 + * @author xiehai + * @date 2021/07/02 16:24 + * @Copyright(c) tellyes tech. inc. co.,ltd + */ +public class StringBuilderConverter extends Converter { + @Nullable + @Override + public StringBuilder fromString(@NotNull String value) { + return new StringBuilder(value); + } + + @NotNull + @Override + public String toString(@NotNull StringBuilder stringBuilder) { + return stringBuilder.toString(); + } +} \ No newline at end of file -- Gitee From 714fd9ccff368230f26409e53b2bab4b75442773 Mon Sep 17 00:00:00 2001 From: PasseRR Date: Fri, 2 Jul 2021 18:26:51 +0800 Subject: [PATCH 09/41] =?UTF-8?q?=E8=A7=86=E5=9B=BE=E8=A1=A5=E5=85=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../passerr/idea/plugins/BaseTableModel.java | 2 +- .../plugins/spring/web/ApiDocConfigViews.java | 112 ++++++++++++++++-- .../spring/web/po/ApiDocSettingPo.java | 18 +-- src/main/resources/META-INF/plugin.xml | 4 +- 4 files changed, 113 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/github/passerr/idea/plugins/BaseTableModel.java b/src/main/java/com/github/passerr/idea/plugins/BaseTableModel.java index 7a1e641..82853ea 100644 --- a/src/main/java/com/github/passerr/idea/plugins/BaseTableModel.java +++ b/src/main/java/com/github/passerr/idea/plugins/BaseTableModel.java @@ -57,7 +57,7 @@ public class BaseTableModel extends AbstractTableModel implements ItemRemovab * 列属性 * @return 列 */ - List> columns() { + protected List> columns() { return Collections.singletonList(it -> it); } } diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java index 33dea26..128dc44 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java @@ -5,6 +5,7 @@ import com.github.passerr.idea.plugins.IdeaJbTable; import com.github.passerr.idea.plugins.IdeaPanelWithButtons; import com.github.passerr.idea.plugins.spring.web.highlight.FileTemplateTokenType; import com.github.passerr.idea.plugins.spring.web.highlight.TemplateHighlighter; +import com.github.passerr.idea.plugins.spring.web.po.ApiDocAliasPairPo; import com.github.passerr.idea.plugins.spring.web.po.ApiDocSettingPo; import com.google.common.io.CharStreams; import com.intellij.openapi.editor.Document; @@ -43,6 +44,7 @@ import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.function.Function; /** * api文档配置视图 @@ -60,7 +62,10 @@ public abstract class ApiDocConfigViews { return Arrays.asList( Pair.pair("Api模版", apiTemplatePanel(setting)), - Pair.pair("查询参数", queryParamPanel(setting)) + Pair.pair("查询参数", queryParamPanel(setting)), + Pair.pair("报文参数", bodyParamPanel(setting)), + Pair.pair("别名定义", aliasPanel(setting)), + Pair.pair("序列化配置", serialPanel(setting)) ); } @@ -71,7 +76,6 @@ public abstract class ApiDocConfigViews { */ private static JPanel apiTemplatePanel(ApiDocSettingPo setting) { JPanel panel = new JPanel(new GridBagLayout()); - // 编辑模块 EditorFactory editorFactory = EditorFactory.getInstance(); Document document = editorFactory.createDocument(setting.getTemplate()); @@ -112,10 +116,7 @@ public abstract class ApiDocConfigViews { ); panel.add( templatePanel, - new GridBagConstraints( - 0, 0, 1, 1, 1, 1, - GridBagConstraints.NORTH, - GridBagConstraints.BOTH, + new GridBagConstraints(0, 0, 1, 1, 1, 1, GridBagConstraints.NORTH, GridBagConstraints.BOTH, JBUI.emptyInsets(), 0, 0 ) ); @@ -145,10 +146,7 @@ public abstract class ApiDocConfigViews { ); panel.add( descriptionPanel, - new GridBagConstraints( - 0, 1, 1, 1, 1, 1, - GridBagConstraints.NORTH, - GridBagConstraints.BOTH, + new GridBagConstraints(0, 1, 1, 1, 1, 1, GridBagConstraints.NORTH, GridBagConstraints.BOTH, JBUI.emptyInsets(), 0, 0 ) ); @@ -222,4 +220,98 @@ public abstract class ApiDocConfigViews { return panel; } + + /** + * 报文参数忽略类型设置 + * @param setting {@link ApiDocSettingPo} + * @return {@link JPanel} + */ + private static JPanel bodyParamPanel(ApiDocSettingPo setting) { + JPanel panel = new JPanel(new GridBagLayout()); + PanelWithButtons top = new IdeaPanelWithButtons("忽略类型") { + @Override + protected JComponent createMainComponent() { + BaseTableModel model = new BaseTableModel<>( + Collections.singletonList("类型"), setting.getBodyIgnoreTypes()); + JBTable table = new IdeaJbTable(model); + + return + ToolbarDecorator.createDecorator(table) + .setAddAction(it -> {}) + .setAddActionName("新增") + .setEditAction(it -> {}) + .setEditActionName("编辑") + .setRemoveAction(it -> model.removeRow(table.getSelectedRow())) + .setRemoveActionName("删除") + .disableUpDownActions() + .createPanel(); + } + }; + panel.add( + top, + new GridBagConstraints( + 0, 0, 1, 1, 1, 1, + GridBagConstraints.NORTH, + GridBagConstraints.BOTH, + JBUI.emptyInsets(), 0, 0 + ) + ); + + return panel; + } + + /** + * 报文参数忽略类型设置 + * @param setting {@link ApiDocSettingPo} + * @return {@link JPanel} + */ + private static JPanel aliasPanel(ApiDocSettingPo setting) { + JPanel panel = new JPanel(new GridBagLayout()); + PanelWithButtons top = new IdeaPanelWithButtons("") { + @Override + protected JComponent createMainComponent() { + BaseTableModel model = new BaseTableModel( + Arrays.asList("类型", "别名"), setting.getAliases()) { + @Override + protected List> columns() { + return Arrays.asList(ApiDocAliasPairPo::getType, ApiDocAliasPairPo::getAlias); + } + }; + JBTable table = new IdeaJbTable(model); + + return + ToolbarDecorator.createDecorator(table) + .setAddAction(it -> {}) + .setAddActionName("新增") + .setEditAction(it -> {}) + .setEditActionName("编辑") + .setRemoveAction(it -> model.removeRow(table.getSelectedRow())) + .setRemoveActionName("删除") + .disableUpDownActions() + .createPanel(); + } + + + }; + panel.add( + top, + new GridBagConstraints( + 0, 0, 1, 1, 1, 1, + GridBagConstraints.NORTH, + GridBagConstraints.BOTH, + JBUI.emptyInsets(), 0, 0 + ) + ); + + return panel; + } + + /** + * json序列化设置 + * @param setting {@link ApiDocSettingPo} + * @return {@link JPanel} + */ + private static JPanel serialPanel(ApiDocSettingPo setting) { + return new JPanel(); + } } diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java index bc6bcc0..c17e629 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java @@ -26,22 +26,22 @@ import java.util.stream.Collectors; public class ApiDocSettingPo { @OptionTag(tag = "template", nameAttribute = "", converter = StringBuilderConverter.class) StringBuilder template; - @Tag("all-ignore-types") - @AbstractCollection - List allIgnoreTypes; @Tag("query-param-ignore-types") @AbstractCollection List queryParamIgnoreTypes; @Tag("query-param-ignore-annotations") @AbstractCollection List queryParamIgnoreAnnotations; + @Tag("body-ignore-types") + @AbstractCollection + List bodyIgnoreTypes; @Tag("aliases") @AbstractCollection(elementTypes = ApiDocAliasPairPo.class) List aliases; public ApiDocSettingPo() { this.template = new StringBuilder(WebCopyConstants.DEFAULT_TEMPLATE); - this.allIgnoreTypes = new ArrayList<>(WebCopyConstants.ALL_IGNORE_TYPES); + this.bodyIgnoreTypes = new ArrayList<>(WebCopyConstants.ALL_IGNORE_TYPES); this.queryParamIgnoreTypes = new ArrayList<>(WebCopyConstants.QUERY_PARAM_IGNORE_TYPES); this.queryParamIgnoreAnnotations = new ArrayList<>(WebCopyConstants.QUERY_PARAM_IGNORE_ANNOTATIONS); this.aliases = WebCopyConstants.DEFAULT_ALIAS_MAPPINGS.entrySet() @@ -54,7 +54,7 @@ public class ApiDocSettingPo { return new ApiDocSettingPo( new StringBuilder(this.template), - new ArrayList<>(this.allIgnoreTypes), + new ArrayList<>(this.bodyIgnoreTypes), new ArrayList<>(this.queryParamIgnoreTypes), new ArrayList<>(this.queryParamIgnoreAnnotations), this.aliases.stream().map(ApiDocAliasPairPo::deepCopy).collect(Collectors.toList()) @@ -64,8 +64,8 @@ public class ApiDocSettingPo { public void shallowCopy(ApiDocSettingPo source) { this.template.setLength(0); this.template.append(source.template); - this.allIgnoreTypes.clear(); - this.allIgnoreTypes.addAll(source.getAllIgnoreTypes()); + this.bodyIgnoreTypes.clear(); + this.bodyIgnoreTypes.addAll(source.getBodyIgnoreTypes()); this.queryParamIgnoreTypes.clear(); this.queryParamIgnoreTypes.addAll(source.getQueryParamIgnoreTypes()); this.queryParamIgnoreAnnotations.clear(); @@ -85,7 +85,7 @@ public class ApiDocSettingPo { if (o == null || getClass() != o.getClass()) { return false; } ApiDocSettingPo that = (ApiDocSettingPo) o; return Objects.equals(template.toString(), that.template.toString()) && - Objects.equals(allIgnoreTypes, that.allIgnoreTypes) && + Objects.equals(bodyIgnoreTypes, that.bodyIgnoreTypes) && Objects.equals(queryParamIgnoreTypes, that.queryParamIgnoreTypes) && Objects.equals(queryParamIgnoreAnnotations, that.queryParamIgnoreAnnotations) && Objects.equals(aliases, that.aliases); @@ -93,6 +93,6 @@ public class ApiDocSettingPo { @Override public int hashCode() { - return Objects.hash(template, allIgnoreTypes, queryParamIgnoreTypes, queryParamIgnoreAnnotations, aliases); + return Objects.hash(template, bodyIgnoreTypes, queryParamIgnoreTypes, queryParamIgnoreAnnotations, aliases); } } diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 7f074fd..1c5cab7 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -7,9 +7,7 @@ - Switch easily between CamelCase, camelCase, snake_case and SNAKE_CASE. See Edit menu or use SHIFT + ALT + U. -

+ 变量命名切换、mybatis日志转可执行sql、常用的工具格式化、spring controller全路径复制、spring controller转api文档 ]]> -- Gitee From e460c1079d7a6423e8e46db4b9f25003c44dea94 Mon Sep 17 00:00:00 2001 From: PasseRR Date: Mon, 5 Jul 2021 10:45:12 +0800 Subject: [PATCH 10/41] =?UTF-8?q?=E5=BA=8F=E5=88=97=E5=8C=96=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../idea/plugins/spring/web/AliasType.java | 66 ++++++++++ .../plugins/spring/web/ApiDocConfigViews.java | 45 +++---- .../spring/web/ApiDocStateComponent.java | 9 +- .../idea/plugins/spring/web/ResourceUtil.java | 29 +++++ .../plugins/spring/web/WebCopyConstants.java | 78 +----------- .../spring/web/po/ApiDocAliasPairPo.java | 28 ----- .../spring/web/po/ApiDocObjectSerialPo.java | 113 ++++++++++++++++++ .../spring/web/po/ApiDocSettingPo.java | 24 ++-- src/main/resources/api-doc-template.vm | 1 + 9 files changed, 244 insertions(+), 149 deletions(-) create mode 100644 src/main/java/com/github/passerr/idea/plugins/spring/web/AliasType.java create mode 100644 src/main/java/com/github/passerr/idea/plugins/spring/web/ResourceUtil.java delete mode 100644 src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocAliasPairPo.java create mode 100644 src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocObjectSerialPo.java create mode 100644 src/main/resources/api-doc-template.vm diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/AliasType.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/AliasType.java new file mode 100644 index 0000000..1606308 --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/AliasType.java @@ -0,0 +1,66 @@ +package com.github.passerr.idea.plugins.spring.web; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; + +/** + * 别名类型 + * @author xiehai + * @date 2021/07/05 09:45 + * @Copyright(c) tellyes tech. inc. co.,ltd + */ +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +@RequiredArgsConstructor +@Getter +public enum AliasType { + /** + * 布尔类型 + */ + BOOLEAN("boolean") { + @Override + public Object deserialize(String value) { + return Boolean.valueOf(value); + } + }, + /** + * 整型 + */ + INT("int") { + @Override + public Object deserialize(String value) { + try { + return Integer.valueOf(value); + } catch (NumberFormatException e) { + return 1; + } + } + }, + /** + * 浮点型 + */ + FLOAT("float") { + @Override + public Object deserialize(String value) { + try { + return Double.valueOf(value); + } catch (NumberFormatException e) { + return 2.2; + } + } + }, + /** + * 字符串 + */ + STRING("string") { + @Override + public Object deserialize(String value) { + return value; + } + }; + + String type; + + public abstract Object deserialize(String value); +} diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java index 128dc44..4a73ff9 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java @@ -5,9 +5,8 @@ import com.github.passerr.idea.plugins.IdeaJbTable; import com.github.passerr.idea.plugins.IdeaPanelWithButtons; import com.github.passerr.idea.plugins.spring.web.highlight.FileTemplateTokenType; import com.github.passerr.idea.plugins.spring.web.highlight.TemplateHighlighter; -import com.github.passerr.idea.plugins.spring.web.po.ApiDocAliasPairPo; +import com.github.passerr.idea.plugins.spring.web.po.ApiDocObjectSerialPo; import com.github.passerr.idea.plugins.spring.web.po.ApiDocSettingPo; -import com.google.common.io.CharStreams; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.EditorFactory; @@ -37,10 +36,6 @@ import javax.swing.JEditorPane; import javax.swing.JPanel; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -64,7 +59,6 @@ public abstract class ApiDocConfigViews { Pair.pair("Api模版", apiTemplatePanel(setting)), Pair.pair("查询参数", queryParamPanel(setting)), Pair.pair("报文参数", bodyParamPanel(setting)), - Pair.pair("别名定义", aliasPanel(setting)), Pair.pair("序列化配置", serialPanel(setting)) ); } @@ -125,10 +119,7 @@ public abstract class ApiDocConfigViews { JEditorPane desc = new JEditorPane(UIUtil.HTML_MIME, ""); desc.setEditable(false); desc.addHyperlinkListener(new BrowserHyperlinkListener()); - try (InputStream resourceAsStream = ApiDocConfigViews.class.getResourceAsStream("/api-doc-desc.html")) { - desc.setText(CharStreams.toString(new InputStreamReader(resourceAsStream, StandardCharsets.UTF_8))); - } catch (IOException ignore) { - } + desc.setText(ResourceUtil.readAsString("/api-doc-desc.html")); desc.setCaretPosition(0); JPanel descriptionPanel = new JPanel(new GridBagLayout()); @@ -161,7 +152,7 @@ public abstract class ApiDocConfigViews { */ private static JPanel queryParamPanel(ApiDocSettingPo setting) { JPanel panel = new JPanel(new GridBagLayout()); - PanelWithButtons top = new IdeaPanelWithButtons("忽略类型") { + PanelWithButtons top = new IdeaPanelWithButtons("忽略类型:") { @Override protected JComponent createMainComponent() { BaseTableModel model = new BaseTableModel<>( @@ -180,7 +171,7 @@ public abstract class ApiDocConfigViews { .createPanel(); } }; - PanelWithButtons bottom = new IdeaPanelWithButtons("忽略注解") { + PanelWithButtons bottom = new IdeaPanelWithButtons("忽略注解:") { @Override protected JComponent createMainComponent() { BaseTableModel model = new BaseTableModel<>( @@ -228,7 +219,7 @@ public abstract class ApiDocConfigViews { */ private static JPanel bodyParamPanel(ApiDocSettingPo setting) { JPanel panel = new JPanel(new GridBagLayout()); - PanelWithButtons top = new IdeaPanelWithButtons("忽略类型") { + PanelWithButtons top = new IdeaPanelWithButtons("忽略类型:") { @Override protected JComponent createMainComponent() { BaseTableModel model = new BaseTableModel<>( @@ -261,20 +252,25 @@ public abstract class ApiDocConfigViews { } /** - * 报文参数忽略类型设置 + * 序列化配置 * @param setting {@link ApiDocSettingPo} * @return {@link JPanel} */ - private static JPanel aliasPanel(ApiDocSettingPo setting) { + private static JPanel serialPanel(ApiDocSettingPo setting) { JPanel panel = new JPanel(new GridBagLayout()); PanelWithButtons top = new IdeaPanelWithButtons("") { @Override protected JComponent createMainComponent() { - BaseTableModel model = new BaseTableModel( - Arrays.asList("类型", "别名"), setting.getAliases()) { + BaseTableModel model = new BaseTableModel( + Arrays.asList("类型", "别名", "序列化默认值"), setting.getObjects()) { @Override - protected List> columns() { - return Arrays.asList(ApiDocAliasPairPo::getType, ApiDocAliasPairPo::getAlias); + protected List> columns() { + return + Arrays.asList( + ApiDocObjectSerialPo::getType, + ApiDocObjectSerialPo::getAlias, + ApiDocObjectSerialPo::getValue + ); } }; JBTable table = new IdeaJbTable(model); @@ -305,13 +301,4 @@ public abstract class ApiDocConfigViews { return panel; } - - /** - * json序列化设置 - * @param setting {@link ApiDocSettingPo} - * @return {@link JPanel} - */ - private static JPanel serialPanel(ApiDocSettingPo setting) { - return new JPanel(); - } } diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocStateComponent.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocStateComponent.java index 0c33cec..0db4907 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocStateComponent.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocStateComponent.java @@ -1,6 +1,6 @@ package com.github.passerr.idea.plugins.spring.web; -import com.github.passerr.idea.plugins.spring.web.po.ApiDocAliasPairPo; +import com.github.passerr.idea.plugins.spring.web.po.ApiDocObjectSerialPo; import com.github.passerr.idea.plugins.spring.web.po.ApiDocSettingPo; import com.intellij.openapi.components.PersistentStateComponent; import com.intellij.openapi.components.ServiceManager; @@ -40,12 +40,13 @@ public class ApiDocStateComponent implements PersistentStateComponent Objects.equals(it.getType(), type)) - .map(ApiDocAliasPairPo::getAlias) + .map(ApiDocObjectSerialPo::getAlias) .findFirst() - .orElse(WebCopyConstants.DEFAULT_ALIAS); + // 默认设置为字符串 + .orElseGet(AliasType.STRING::getType); } static ApiDocStateComponent getInstance() { diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/ResourceUtil.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/ResourceUtil.java new file mode 100644 index 0000000..790ddcf --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/ResourceUtil.java @@ -0,0 +1,29 @@ +package com.github.passerr.idea.plugins.spring.web; + +import com.google.common.io.CharStreams; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; + +/** + * 资源文件读取 + * @author xiehai + * @date 2021/07/05 09:32 + * @Copyright(c) tellyes tech. inc. co.,ltd + */ +public interface ResourceUtil { + /** + * 读取文件为字符串 + * @param path 文件路径 + * @return 文件内容 + */ + static String readAsString(String path) { + try (InputStream resourceAsStream = ResourceUtil.class.getResourceAsStream(path)) { + return CharStreams.toString(new InputStreamReader(resourceAsStream, StandardCharsets.UTF_8)); + } catch (IOException ignore) { + return ""; + } + } +} diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/WebCopyConstants.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/WebCopyConstants.java index 68a29c9..e335337 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/WebCopyConstants.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/WebCopyConstants.java @@ -2,9 +2,7 @@ package com.github.passerr.idea.plugins.spring.web; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; import java.util.List; -import java.util.Map; /** * web常量 @@ -14,7 +12,7 @@ import java.util.Map; */ public abstract class WebCopyConstants { /** - * 均不支持的类型 + * 不支持的类型 */ public static final List ALL_IGNORE_TYPES = Arrays.asList("java.lang.Object", "java.util.Map"); /** @@ -38,10 +36,7 @@ public abstract class WebCopyConstants { /** * 查询参数注解 */ - public static final List QUERY_PARAM_ANNOTATIONS = new ArrayList<>(Arrays.asList( - "org.springframework.web.bind.annotation.RequestParam", - "org.springframework.web.bind.annotation.RequestAttribute" - )); + public static final String QUERY_PARAM_ANNOTATION = "org.springframework.web.bind.annotation.RequestParam"; /** * 查询参数忽略注解 */ @@ -49,6 +44,7 @@ public abstract class WebCopyConstants { "org.springframework.web.bind.annotation.CookieValue", "org.springframework.web.bind.annotation.RequestHeader", "org.springframework.web.bind.annotation.ResponseBody", + "org.springframework.web.bind.annotation.RequestAttribute", "org.springframework.web.bind.annotation.SessionAttribute", "org.springframework.web.bind.annotation.SessionAttributes" )); @@ -56,72 +52,4 @@ public abstract class WebCopyConstants { static { QUERY_PARAM_IGNORE_ANNOTATIONS.add(PATH_VARIABLE_ANNOTATION); } - - /** - * 默认数据类型别名 - */ - public static final String DEFAULT_ALIAS = "object"; - /** - * 布尔类型 - */ - public static final String BOOLEAN_ALIAS = "boolean"; - /** - * 整型 - */ - public static final String INT_ALIAS = "int"; - /** - * 浮点型 - */ - public static final String FLOAT_ALIAS = "float"; - /** - * 字符串 - */ - public static final String STRING_ALIAS = "string"; - /** - * 所有类型别名 - */ - public static final List TYPE_ALIASES = Arrays.asList( - BOOLEAN_ALIAS, - INT_ALIAS, - FLOAT_ALIAS, - STRING_ALIAS, - DEFAULT_ALIAS - ); - /** - * 默认类型映射 - */ - public static final Map DEFAULT_ALIAS_MAPPINGS = new HashMap<>(); - - static { - Arrays.asList("boolean", "java.lang.Boolean").forEach(it -> DEFAULT_ALIAS_MAPPINGS.put(it, BOOLEAN_ALIAS)); - - Arrays.asList( - "byte", "java.lang.Byte", - "short", "java.lang.Short", - "char", "java.lang.Character", - "int", "java.lang.Integer" - ).forEach(it -> DEFAULT_ALIAS_MAPPINGS.put(it, INT_ALIAS)); - - Arrays.asList( - "float", "java.lang.Float", - "double", "java.lang.Double" - ).forEach(it -> DEFAULT_ALIAS_MAPPINGS.put(it, FLOAT_ALIAS)); - - Arrays.asList( - "long", - "java.lang.Long", - "java.lang.String", - "java.math.BigInteger", - "java.math.BigDecimal", - "java.util.Date", - "java.util.LocalDate", - "java.util.LocalTime", - "java.util.LocalDateTime" - ).forEach(it -> DEFAULT_ALIAS_MAPPINGS.put(it, STRING_ALIAS)); - } - - /** - * 默认文档模版 - */ - public static final String DEFAULT_TEMPLATE = "Hello World"; } diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocAliasPairPo.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocAliasPairPo.java deleted file mode 100644 index 00f7207..0000000 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocAliasPairPo.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.github.passerr.idea.plugins.spring.web.po; - -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; -import lombok.experimental.FieldDefaults; - -/** - * 别名对po - * @author xiehai - * @date 2021/06/30 19:29 - * @Copyright(c) tellyes tech. inc. co.,ltd - */ -@Data -@EqualsAndHashCode -@FieldDefaults(level = AccessLevel.PRIVATE) -@AllArgsConstructor -@NoArgsConstructor -public class ApiDocAliasPairPo { - String type; - String alias; - - public ApiDocAliasPairPo deepCopy() { - return new ApiDocAliasPairPo(this.type, this.alias); - } -} diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocObjectSerialPo.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocObjectSerialPo.java new file mode 100644 index 0000000..8066c1a --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocObjectSerialPo.java @@ -0,0 +1,113 @@ +package com.github.passerr.idea.plugins.spring.web.po; + +import com.github.passerr.idea.plugins.spring.web.AliasType; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.FieldDefaults; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * 对象序列化 + * @author xiehai + * @date 2021/06/30 19:29 + * @Copyright(c) tellyes tech. inc. co.,ltd + */ +@Data +@EqualsAndHashCode +@FieldDefaults(level = AccessLevel.PRIVATE) +@AllArgsConstructor +@NoArgsConstructor +public class ApiDocObjectSerialPo { + /** + * 类型全名 + */ + String type; + /** + * 别名 + */ + String alias; + /** + * 默认值 + */ + String value; + + public ApiDocObjectSerialPo(ApiDocObjectSerialPo that) { + this.type = that.type; + this.alias = that.alias; + this.value = that.value; + } + + public ApiDocObjectSerialPo deepCopy() { + return new ApiDocObjectSerialPo(this.type, this.alias, this.value); + } + + /** + * 默认值设置 + * @return {@link List} + */ + static List defaultObjects() { + List objects = new ArrayList<>(); + Arrays.asList("boolean", "java.lang.Boolean") + .forEach(it -> objects.add(new ApiDocObjectSerialPo(it, AliasType.BOOLEAN.getType(), "true"))); + + Arrays.asList( + "byte", "java.lang.Byte", + "short", "java.lang.Short", + "char", "java.lang.Character", + "int", "java.lang.Integer" + ).forEach(it -> objects.add(new ApiDocObjectSerialPo(it, AliasType.INT.getType(), "1024"))); + + Arrays.asList( + "float", "java.lang.Float", + "double", "java.lang.Double" + ).forEach(it -> objects.add(new ApiDocObjectSerialPo(it, AliasType.FLOAT.getType(), "102.4"))); + + Arrays.asList( + "long", + "java.lang.Long", + "java.math.BigInteger" + ).forEach(it -> objects.add(new ApiDocObjectSerialPo(it, AliasType.STRING.getType(), "2048"))); + + Arrays.asList( + "java.util.Date", + "java.time.LocalDateTime" + ).forEach(it -> + objects.add( + new ApiDocObjectSerialPo( + it, + AliasType.STRING.getType(), + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + ) + ) + ); + objects.add( + new ApiDocObjectSerialPo( + "java.time.LocalDate", + AliasType.STRING.getType(), + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")) + ) + ); + objects.add( + new ApiDocObjectSerialPo( + "java.time.LocalTime", + AliasType.STRING.getType(), + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")) + ) + ); + objects.add(new ApiDocObjectSerialPo("java.math.BigDecimal", AliasType.STRING.getType(), "204.8")); + objects.add(new ApiDocObjectSerialPo("java.lang.String", AliasType.STRING.getType(), "String")); + + + return objects; + } +} diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java index c17e629..a08d016 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java @@ -1,5 +1,6 @@ package com.github.passerr.idea.plugins.spring.web.po; +import com.github.passerr.idea.plugins.spring.web.ResourceUtil; import com.github.passerr.idea.plugins.spring.web.WebCopyConstants; import com.intellij.util.xmlb.annotations.AbstractCollection; import com.intellij.util.xmlb.annotations.OptionTag; @@ -35,19 +36,16 @@ public class ApiDocSettingPo { @Tag("body-ignore-types") @AbstractCollection List bodyIgnoreTypes; - @Tag("aliases") - @AbstractCollection(elementTypes = ApiDocAliasPairPo.class) - List aliases; + @Tag("objects") + @AbstractCollection(elementTypes = ApiDocObjectSerialPo.class) + List objects; public ApiDocSettingPo() { - this.template = new StringBuilder(WebCopyConstants.DEFAULT_TEMPLATE); + this.template = new StringBuilder(ResourceUtil.readAsString("/api-doc-template.vm")); this.bodyIgnoreTypes = new ArrayList<>(WebCopyConstants.ALL_IGNORE_TYPES); this.queryParamIgnoreTypes = new ArrayList<>(WebCopyConstants.QUERY_PARAM_IGNORE_TYPES); this.queryParamIgnoreAnnotations = new ArrayList<>(WebCopyConstants.QUERY_PARAM_IGNORE_ANNOTATIONS); - this.aliases = WebCopyConstants.DEFAULT_ALIAS_MAPPINGS.entrySet() - .stream() - .map(it -> new ApiDocAliasPairPo(it.getKey(), it.getValue())) - .collect(Collectors.toList()); + this.objects = ApiDocObjectSerialPo.defaultObjects(); } public ApiDocSettingPo deepCopy() { @@ -57,7 +55,7 @@ public class ApiDocSettingPo { new ArrayList<>(this.bodyIgnoreTypes), new ArrayList<>(this.queryParamIgnoreTypes), new ArrayList<>(this.queryParamIgnoreAnnotations), - this.aliases.stream().map(ApiDocAliasPairPo::deepCopy).collect(Collectors.toList()) + this.objects.stream().map(ApiDocObjectSerialPo::deepCopy).collect(Collectors.toList()) ); } @@ -70,8 +68,8 @@ public class ApiDocSettingPo { this.queryParamIgnoreTypes.addAll(source.getQueryParamIgnoreTypes()); this.queryParamIgnoreAnnotations.clear(); this.queryParamIgnoreAnnotations.addAll(source.getQueryParamIgnoreAnnotations()); - this.aliases.clear(); - this.aliases.addAll(source.getAliases()); + this.objects.clear(); + this.objects.addAll(source.getObjects()); } public void setStringTemplate(String template) { @@ -88,11 +86,11 @@ public class ApiDocSettingPo { Objects.equals(bodyIgnoreTypes, that.bodyIgnoreTypes) && Objects.equals(queryParamIgnoreTypes, that.queryParamIgnoreTypes) && Objects.equals(queryParamIgnoreAnnotations, that.queryParamIgnoreAnnotations) && - Objects.equals(aliases, that.aliases); + Objects.equals(objects, that.objects); } @Override public int hashCode() { - return Objects.hash(template, bodyIgnoreTypes, queryParamIgnoreTypes, queryParamIgnoreAnnotations, aliases); + return Objects.hash(template, bodyIgnoreTypes, queryParamIgnoreTypes, queryParamIgnoreAnnotations, objects); } } diff --git a/src/main/resources/api-doc-template.vm b/src/main/resources/api-doc-template.vm new file mode 100644 index 0000000..719049c --- /dev/null +++ b/src/main/resources/api-doc-template.vm @@ -0,0 +1 @@ +## 默认api模版 \ No newline at end of file -- Gitee From d2e52a9322f380caee21954436f808939fb062f2 Mon Sep 17 00:00:00 2001 From: PasseRR Date: Mon, 5 Jul 2021 13:15:18 +0800 Subject: [PATCH 11/41] =?UTF-8?q?=E7=AA=97=E5=8F=A3=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../passerr/idea/plugins/BaseTableModel.java | 4 + .../passerr/idea/plugins/IdeaDialog.java | 86 +++++++++++++++++++ .../plugins/spring/web/ApiDocConfigViews.java | 56 +++++++++++- .../spring/web/ApiDocConfigurable.java | 2 + .../spring/web/po/ApiDocObjectSerialPo.java | 6 -- 5 files changed, 146 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/github/passerr/idea/plugins/IdeaDialog.java diff --git a/src/main/java/com/github/passerr/idea/plugins/BaseTableModel.java b/src/main/java/com/github/passerr/idea/plugins/BaseTableModel.java index 82853ea..815f4ea 100644 --- a/src/main/java/com/github/passerr/idea/plugins/BaseTableModel.java +++ b/src/main/java/com/github/passerr/idea/plugins/BaseTableModel.java @@ -53,6 +53,10 @@ public class BaseTableModel extends AbstractTableModel implements ItemRemovab return this.columns().get(columnIndex).apply(this.data.get(rowIndex)); } + public T getRow(int rowIndex) { + return this.data.get(rowIndex); + } + /** * 列属性 * @return 列 diff --git a/src/main/java/com/github/passerr/idea/plugins/IdeaDialog.java b/src/main/java/com/github/passerr/idea/plugins/IdeaDialog.java new file mode 100644 index 0000000..665bb7d --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/IdeaDialog.java @@ -0,0 +1,86 @@ +package com.github.passerr.idea.plugins; + +import com.intellij.openapi.ui.DialogWrapper; +import lombok.Getter; +import org.jetbrains.annotations.Nullable; + +import javax.swing.JComponent; +import java.awt.Component; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; + +/** + * idea弹出框 + * @author xiehai + * @date 2021/07/05 11:00 + * @Copyright(c) tellyes tech. inc. co.,ltd + */ +public class IdeaDialog extends DialogWrapper { + /** + * 初始参数 + */ + @Getter + protected T value; + protected Consumer action; + protected Predicate changePredicate; + protected Function, JComponent> componentFunction; + + /** + * 弹出框构造 + * @param parent 父容器 + */ + public IdeaDialog(Component parent) { + super(parent, true); + } + + public void onChange() { + if (Objects.nonNull(this.changePredicate)) { + super.setOKActionEnabled(this.changePredicate.test(this.value)); + } + } + + public IdeaDialog title(String title) { + super.setTitle(title); + return this; + } + + public IdeaDialog value(T value) { + this.value = value; + return this; + } + + + public IdeaDialog okAction(Consumer action) { + this.action = action; + return this; + } + + public IdeaDialog changePredicate(Predicate changePredicate) { + this.changePredicate = changePredicate; + return this; + } + + public IdeaDialog componentFunction(Function, JComponent> componentFunction) { + this.componentFunction = componentFunction; + return this; + } + + @Nullable + @Override + protected JComponent createCenterPanel() { + return this.componentFunction.apply(this); + } + + public IdeaDialog doInit() { + super.init(); + return this; + } + + @Override + protected void doOKAction() { + this.action.accept(this.value); + super.doOKAction(); + } +} diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java index 4a73ff9..fe5281d 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java @@ -1,6 +1,7 @@ package com.github.passerr.idea.plugins.spring.web; import com.github.passerr.idea.plugins.BaseTableModel; +import com.github.passerr.idea.plugins.IdeaDialog; import com.github.passerr.idea.plugins.IdeaJbTable; import com.github.passerr.idea.plugins.IdeaPanelWithButtons; import com.github.passerr.idea.plugins.spring.web.highlight.FileTemplateTokenType; @@ -33,12 +34,16 @@ import com.intellij.util.ui.UIUtil; import javax.swing.JComponent; import javax.swing.JEditorPane; +import javax.swing.JLabel; import javax.swing.JPanel; +import javax.swing.JTextField; +import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.function.BiFunction; import java.util.function.Function; /** @@ -225,12 +230,59 @@ public abstract class ApiDocConfigViews { BaseTableModel model = new BaseTableModel<>( Collections.singletonList("类型"), setting.getBodyIgnoreTypes()); JBTable table = new IdeaJbTable(model); + // 弹出层构建器 + BiFunction function = (s, r) -> { + JPanel p = new JPanel(new GridBagLayout()); + GridBagConstraints gb = new GridBagConstraints(0, 0, 1, 1, 0, 0, + GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, + JBUI.insets(0, 0, 5, 10), 0, 0 + ); + JLabel typeLabel = new JLabel("类型"); + p.add(typeLabel, gb); + JTextField textField = new JTextField(s.toString()); + textField.getDocument() + .addDocumentListener( + new com.intellij.ui.DocumentAdapter() { + @Override + protected void textChanged(javax.swing.event.DocumentEvent e) { + s.setLength(0); + s.append(textField.getText()); + r.run(); + } + }); + Dimension oldPreferredSize = textField.getPreferredSize(); + textField.setPreferredSize(new Dimension(300, oldPreferredSize.height)); + gb.gridx = 1; + gb.gridwidth = GridBagConstraints.REMAINDER; + gb.weightx = 1; + p.add(textField, gb); + r.run(); + return p; + }; return ToolbarDecorator.createDecorator(table) - .setAddAction(it -> {}) + .setAddAction(it -> + new IdeaDialog(panel) + .title("新增忽略类型") + .value(new StringBuilder()) + .okAction(t -> setting.getBodyIgnoreTypes().add(t.toString())) + .changePredicate(t -> t.length() > 0) + .componentFunction(t -> function.apply(t.getValue(), t::onChange)) + .doInit() + .showAndGet() + ) .setAddActionName("新增") - .setEditAction(it -> {}) + .setEditAction(it -> + new IdeaDialog(panel) + .title("编辑忽略类型") + .value(new StringBuilder(model.getRow(table.getSelectedRow()))) + .okAction(t -> setting.getBodyIgnoreTypes().set(table.getSelectedRow(), t.toString())) + .changePredicate(t -> t.length() > 0) + .componentFunction(t -> function.apply(t.getValue(), t::onChange)) + .doInit() + .showAndGet() + ) .setEditActionName("编辑") .setRemoveAction(it -> model.removeRow(table.getSelectedRow())) .setRemoveActionName("删除") diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigurable.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigurable.java index 171ab77..d4d1969 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigurable.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigurable.java @@ -71,6 +71,8 @@ public class ApiDocConfigurable implements SearchableConfigurable, Configurable. }); }); + tabbedPanel.setSelectedIndex(0); + return tabbedPanel.getComponent(); } diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocObjectSerialPo.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocObjectSerialPo.java index 8066c1a..03aa4d9 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocObjectSerialPo.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocObjectSerialPo.java @@ -41,12 +41,6 @@ public class ApiDocObjectSerialPo { */ String value; - public ApiDocObjectSerialPo(ApiDocObjectSerialPo that) { - this.type = that.type; - this.alias = that.alias; - this.value = that.value; - } - public ApiDocObjectSerialPo deepCopy() { return new ApiDocObjectSerialPo(this.type, this.alias, this.value); } -- Gitee From 0d2d06df73e3ff553c52521ddf2d0bc940af9d97 Mon Sep 17 00:00:00 2001 From: PasseRR Date: Mon, 5 Jul 2021 13:51:51 +0800 Subject: [PATCH 12/41] =?UTF-8?q?=E5=AE=8C=E6=88=90=E6=8C=81=E4=B9=85?= =?UTF-8?q?=E5=8C=96=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugins/spring/web/ApiDocConfigViews.java | 207 +++++++++++++++++- .../spring/web/po/ApiDocObjectSerialPo.java | 8 + 2 files changed, 209 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java index fe5281d..874387c 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java @@ -32,6 +32,7 @@ import com.intellij.ui.table.JBTable; import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; +import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JEditorPane; import javax.swing.JLabel; @@ -40,9 +41,11 @@ import javax.swing.JTextField; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; +import java.awt.event.ItemEvent; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Optional; import java.util.function.BiFunction; import java.util.function.Function; @@ -163,12 +166,61 @@ public abstract class ApiDocConfigViews { BaseTableModel model = new BaseTableModel<>( Collections.singletonList("类型"), setting.getQueryParamIgnoreTypes()); JBTable table = new IdeaJbTable(model); + // 弹出层构建器 + BiFunction function = (s, r) -> { + JPanel p = new JPanel(new GridBagLayout()); + GridBagConstraints gb = new GridBagConstraints(0, 0, 1, 1, 0, 0, + GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, + JBUI.insets(0, 0, 5, 10), 0, 0 + ); + JLabel typeLabel = new JLabel("类型"); + p.add(typeLabel, gb); + JTextField textField = new JTextField(s.toString()); + textField.getDocument() + .addDocumentListener( + new com.intellij.ui.DocumentAdapter() { + @Override + protected void textChanged(javax.swing.event.DocumentEvent e) { + s.setLength(0); + s.append(textField.getText()); + r.run(); + } + }); + Dimension oldPreferredSize = textField.getPreferredSize(); + textField.setPreferredSize(new Dimension(300, oldPreferredSize.height)); + gb.gridx = 1; + gb.gridwidth = GridBagConstraints.REMAINDER; + gb.weightx = 1; + p.add(textField, gb); + r.run(); + return p; + }; return ToolbarDecorator.createDecorator(table) - .setAddAction(it -> {}) + .setAddAction(it -> + new IdeaDialog(panel) + .title("新增忽略类型") + .value(new StringBuilder()) + .okAction(t -> setting.getQueryParamIgnoreTypes().add(t.toString())) + .changePredicate(t -> t.length() > 0) + .componentFunction(t -> function.apply(t.getValue(), t::onChange)) + .doInit() + .showAndGet() + ) .setAddActionName("新增") - .setEditAction(it -> {}) + .setEditAction(it -> + new IdeaDialog(panel) + .title("编辑忽略类型") + .value(new StringBuilder(model.getRow(table.getSelectedRow()))) + .okAction(t -> + setting.getQueryParamIgnoreTypes().set(table.getSelectedRow(), t.toString()) + ) + .changePredicate(t -> t.length() > 0) + .componentFunction(t -> function.apply(t.getValue(), t::onChange)) + .doInit() + .showAndGet() + ) .setEditActionName("编辑") .setRemoveAction(it -> model.removeRow(table.getSelectedRow())) .setRemoveActionName("删除") @@ -182,12 +234,61 @@ public abstract class ApiDocConfigViews { BaseTableModel model = new BaseTableModel<>( Collections.singletonList("注解"), setting.getQueryParamIgnoreAnnotations()); JBTable table = new IdeaJbTable(model); + // 弹出层构建器 + BiFunction function = (s, r) -> { + JPanel p = new JPanel(new GridBagLayout()); + GridBagConstraints gb = new GridBagConstraints(0, 0, 1, 1, 0, 0, + GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, + JBUI.insets(0, 0, 5, 10), 0, 0 + ); + JLabel typeLabel = new JLabel("注解"); + p.add(typeLabel, gb); + JTextField textField = new JTextField(s.toString()); + textField.getDocument() + .addDocumentListener( + new com.intellij.ui.DocumentAdapter() { + @Override + protected void textChanged(javax.swing.event.DocumentEvent e) { + s.setLength(0); + s.append(textField.getText()); + r.run(); + } + }); + Dimension oldPreferredSize = textField.getPreferredSize(); + textField.setPreferredSize(new Dimension(300, oldPreferredSize.height)); + gb.gridx = 1; + gb.gridwidth = GridBagConstraints.REMAINDER; + gb.weightx = 1; + p.add(textField, gb); + r.run(); + return p; + }; return ToolbarDecorator.createDecorator(table) - .setAddAction(it -> {}) + .setAddAction(it -> + new IdeaDialog(panel) + .title("新增忽略注解") + .value(new StringBuilder()) + .okAction(t -> setting.getQueryParamIgnoreAnnotations().add(t.toString())) + .changePredicate(t -> t.length() > 0) + .componentFunction(t -> function.apply(t.getValue(), t::onChange)) + .doInit() + .showAndGet() + ) .setAddActionName("新增") - .setEditAction(it -> {}) + .setEditAction(it -> + new IdeaDialog(panel) + .title("编辑忽略注解") + .value(new StringBuilder(model.getRow(table.getSelectedRow()))) + .okAction(t -> + setting.getQueryParamIgnoreAnnotations().set(table.getSelectedRow(), t.toString()) + ) + .changePredicate(t -> t.length() > 0) + .componentFunction(t -> function.apply(t.getValue(), t::onChange)) + .doInit() + .showAndGet() + ) .setEditActionName("编辑") .setRemoveAction(it -> model.removeRow(table.getSelectedRow())) .setRemoveActionName("删除") @@ -326,12 +427,106 @@ public abstract class ApiDocConfigViews { } }; JBTable table = new IdeaJbTable(model); + // 弹出层构建器 + BiFunction function = (s, r) -> { + JPanel p = new JPanel(new GridBagLayout()); + GridBagConstraints gb = new GridBagConstraints(0, 0, 1, 1, 0, 0, + GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, + JBUI.insets(0, 0, 5, 10), 0, 0 + ); + JLabel typeLabel = new JLabel("类型"); + p.add(typeLabel, gb); + JTextField textField = new JTextField(); + Optional.ofNullable(s.getType()).ifPresent(textField::setText); + textField.getDocument() + .addDocumentListener( + new com.intellij.ui.DocumentAdapter() { + @Override + protected void textChanged(javax.swing.event.DocumentEvent e) { + s.setType(textField.getText()); + r.run(); + } + }); + Dimension oldPreferredSize = textField.getPreferredSize(); + textField.setPreferredSize(new Dimension(300, oldPreferredSize.height)); + gb.gridx = 1; + gb.gridwidth = GridBagConstraints.REMAINDER; + gb.weightx = 1; + p.add(textField, gb); + + JLabel aliasLabel = new JLabel("别名"); + gb.gridy++; + gb.gridx = 0; + gb.gridwidth = 1; + gb.weightx = 0; + p.add(aliasLabel, gb); + JComboBox aliasCombobox = new JComboBox<>( + Arrays.stream(AliasType.values()) + .map(AliasType::getType) + .toArray(String[]::new) + ); + aliasCombobox.addItemListener(e -> { + if (e.getStateChange() == ItemEvent.SELECTED) { + s.setAlias((String) e.getItem()); + } + }); + Optional.ofNullable(s.getAlias()).ifPresent(aliasCombobox::setSelectedItem); + gb.gridx = 1; + gb.fill = GridBagConstraints.NONE; + gb.gridwidth = GridBagConstraints.REMAINDER; + gb.weightx = 0; + p.add(aliasCombobox, gb); + + JLabel valueLabel = new JLabel("序列化默认值"); + gb.gridy++; + gb.gridx = 0; + gb.gridwidth = 1; + gb.weightx = 0; + p.add(valueLabel, gb); + JTextField valueField = new JTextField(); + Optional.ofNullable(s.getValue()).ifPresent(valueField::setText); + valueField.getDocument() + .addDocumentListener( + new com.intellij.ui.DocumentAdapter() { + @Override + protected void textChanged(javax.swing.event.DocumentEvent e) { + s.setValue(valueField.getText()); + r.run(); + } + }); + valueField.setPreferredSize(new Dimension(300, oldPreferredSize.height)); + gb.gridx = 1; + gb.gridwidth = GridBagConstraints.REMAINDER; + gb.weightx = 1; + p.add(valueField, gb); + r.run(); + + return p; + }; return ToolbarDecorator.createDecorator(table) - .setAddAction(it -> {}) + .setAddAction(it -> + new IdeaDialog(panel) + .title("新增序列化") + .value(new ApiDocObjectSerialPo()) + .okAction(setting.getObjects()::add) + .changePredicate(ApiDocObjectSerialPo::isOk) + .componentFunction(t -> function.apply(t.getValue(), t::onChange)) + .doInit() + .showAndGet() + ) .setAddActionName("新增") - .setEditAction(it -> {}) + .setEditAction(it -> + new IdeaDialog(panel) + .title("编辑序列化") + .value(model.getRow(table.getSelectedRow())) + .okAction(t -> setting.getObjects().set(table.getSelectedRow(), t)) + .changePredicate(ApiDocObjectSerialPo::isOk) + .componentFunction(t -> function.apply(t.getValue(), t::onChange)) + .doInit() + .showAndGet() + ) .setEditActionName("编辑") .setRemoveAction(it -> model.removeRow(table.getSelectedRow())) .setRemoveActionName("删除") diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocObjectSerialPo.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocObjectSerialPo.java index 03aa4d9..48e0171 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocObjectSerialPo.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocObjectSerialPo.java @@ -15,6 +15,7 @@ import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Objects; /** * 对象序列化 @@ -104,4 +105,11 @@ public class ApiDocObjectSerialPo { return objects; } + + public boolean isOk() { + return + Objects.nonNull(this.type) && !this.type.isEmpty() + && Objects.nonNull(this.alias) && !this.alias.isEmpty() + && Objects.nonNull(this.value) && !this.value.isEmpty(); + } } -- Gitee From 39763792c9c8d2d1963b496c747cf67891e13ab7 Mon Sep 17 00:00:00 2001 From: PasseRR Date: Mon, 5 Jul 2021 18:06:17 +0800 Subject: [PATCH 13/41] =?UTF-8?q?api=E6=96=87=E6=A1=A3=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E8=8E=B7=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../idea/plugins/spring/web/AliasType.java | 5 + .../spring/web/CopyMethodApiDocAction.java | 140 ++++++++++++++++-- .../plugins/spring/web/WebCopyConstants.java | 4 + .../spring/web/po/ApiDocSettingPo.java | 17 +++ 4 files changed, 157 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/AliasType.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/AliasType.java index 1606308..ebd1237 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/AliasType.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/AliasType.java @@ -62,5 +62,10 @@ public enum AliasType { String type; + /** + * 位置别名类型 + */ + public static final String UNKNOWN_ALIAS = "object"; + public abstract Object deserialize(String value); } diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.java index 5f65c1b..4256f23 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.java @@ -1,13 +1,20 @@ package com.github.passerr.idea.plugins.spring.web; +import com.github.passerr.idea.plugins.spring.web.po.ApiDocSettingPo; import com.intellij.codeInsight.AnnotationUtil; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.psi.PsiAnnotation; +import com.intellij.psi.PsiClass; import com.intellij.psi.PsiMethod; +import com.intellij.psi.PsiModifier; +import com.intellij.psi.PsiModifierList; +import com.intellij.psi.PsiParameter; import com.intellij.psi.javadoc.PsiDocComment; import com.intellij.psi.javadoc.PsiDocToken; +import com.intellij.psi.util.PsiTypesUtil; import lombok.AllArgsConstructor; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -15,6 +22,9 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static com.github.passerr.idea.plugins.spring.web.AliasType.UNKNOWN_ALIAS; /** * web方法接口文档复制动作 @@ -28,11 +38,15 @@ public class CopyMethodApiDocAction extends BaseWebCopyAction { PsiMethod method = method(e); PsiAnnotation classAnnotation = classAnnotation(method); PsiAnnotation methodAnnotation = methodAnnotation(method); + ApiDocSettingPo state = ApiDocStateComponent.getInstance().getState(); String url = url(classAnnotation, methodAnnotation); String httpMethod = getMethod(classAnnotation, methodAnnotation); - List pathVariables = pathVariables(method); + // 方法参数注释缓存 + Map comments = comments(method); // 路径参数列表 + List pathVariables = pathVariables(comments, method, state); // 查询参数列表 + List queryVariables = queryParams(comments, method, state); // body // 请求示例 // 应答示例 @@ -63,11 +77,11 @@ public class CopyMethodApiDocAction extends BaseWebCopyAction { } /** - * 获取接口路径参数 + * 获得方法注释 * @param method {@link PsiMethod} - * @return 路径参数列表 + * @return {@link Map} */ - private static List pathVariables(PsiMethod method) { + private static Map comments(PsiMethod method) { Map comments = new HashMap<>(4); Optional.ofNullable(method.getDocComment()) .map(PsiDocComment::getTags) @@ -91,20 +105,33 @@ public class CopyMethodApiDocAction extends BaseWebCopyAction { ) ); + return comments; + } + + /** + * 获取接口路径参数 + * @param comments 方法注释 + * @param method {@link PsiMethod} + * @return 路径参数列表 + */ + private static List pathVariables(Map comments, PsiMethod method, ApiDocSettingPo state) { return Arrays.stream(method.getParameterList().getParameters()) + .filter(CopyMethodApiDocAction::isValidParamType) .map(it -> { PsiAnnotation annotation = AnnotationUtil.findAnnotation( it, WebCopyConstants.PATH_VARIABLE_ANNOTATION); - if (Objects.nonNull(annotation)) { - String type = it.getType().getCanonicalText(); + String type = it.getType().getCanonicalText(); + String alias = state.alias(type); + // 必须要@PathVariable注解存在且是有效别名 + if (Objects.nonNull(annotation) && !UNKNOWN_ALIAS.equals(alias)) { return new Var( Optional.ofNullable(PsiAnnotationMemberValueUtil.value(annotation, "value")) .map(String::valueOf) .orElseGet(it::getText), type, - ApiDocStateComponent.getInstance().alias(type), + alias, comments.getOrDefault(it.getText(), it.getText()) ); } @@ -115,8 +142,102 @@ public class CopyMethodApiDocAction extends BaseWebCopyAction { .collect(Collectors.toList()); } - private static List queryParams(PsiMethod method) { - return null; + /** + * 查询参数注解 + * @param comments 方法注释 + * @param method {@link PsiMethod} + * @param state 配置状态 + * @return {@link List} + */ + private static List queryParams(Map comments, PsiMethod method, ApiDocSettingPo state) { + return + Arrays.stream(method.getParameterList().getParameters()) + .filter(it -> + // 排除接口类型参数、忽略类型、带忽略类型注解的参数 + isValidParamType( + it, + state.getQueryParamIgnoreTypes(), + state.getQueryParamIgnoreAnnotations() + ) + ) + .flatMap(it -> { + // 查询参数注解 + PsiAnnotation annotation = AnnotationUtil.findAnnotation( + it, WebCopyConstants.QUERY_PARAM_ANNOTATION); + String type = it.getType().getCanonicalText(); + String alias = state.alias(type); + // 对象类型 + if (UNKNOWN_ALIAS.equals(alias)) { + PsiClass clazz = PsiTypesUtil.getPsiClass(it.getType()); + if (Objects.isNull(clazz)) { + return Stream.empty(); + } + + return + Arrays.stream(clazz.getAllFields()) + .filter(f -> { + PsiModifierList modifierList = f.getModifierList(); + if (Objects.isNull(modifierList)) { + return true; + } + + return !modifierList.hasExplicitModifier(PsiModifier.STATIC) + && !modifierList.hasExplicitModifier(PsiModifier.TRANSIENT); + }) + .filter(f -> !UNKNOWN_ALIAS.equals(state.alias(f.getType().getCanonicalText()))) + .map(f -> + new Var( + f.getName(), + f.getType().getCanonicalText(), + state.alias(f.getType().getCanonicalText()), + // TODO 获得字段注释 + comments.getOrDefault(it.getText(), it.getText()) + ) + ); + } else { + // 基础类型 + return + Stream.of( + new Var( + Optional.ofNullable(PsiAnnotationMemberValueUtil.value(annotation, "value")) + .map(String::valueOf) + .orElseGet(it::getText), + type, + alias, + comments.getOrDefault(it.getText(), it.getText()) + ) + ); + } + }) + .collect(Collectors.toList()); + } + + /** + * 是否是合法的参数类型 + * 非接口类型且满足排除类型 + * @param parameter {@link PsiParameter} + * @return true/false + */ + private static boolean isValidParamType(PsiParameter parameter) { + return isValidParamType(parameter, new ArrayList<>(), new ArrayList<>()); + } + + /** + * 是否是合法的参数类型 + * @param parameter {@link PsiParameter} + * @param excludeTypes 排除类型 + * @param excludeAnnotations 排除注解 + * @return true/false + */ + private static boolean isValidParamType(PsiParameter parameter, List excludeTypes, + List excludeAnnotations) { + PsiClass clazz = PsiTypesUtil.getPsiClass(parameter.getType()); + + return + Objects.nonNull(clazz) + && !clazz.isInterface() + && !excludeTypes.contains(clazz.getQualifiedName()) + && AnnotationUtil.findAnnotations(parameter, excludeAnnotations).length == 0; } /** @@ -140,5 +261,6 @@ public class CopyMethodApiDocAction extends BaseWebCopyAction { * 参数描述 */ String desc; + } } diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/WebCopyConstants.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/WebCopyConstants.java index e335337..73db5b1 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/WebCopyConstants.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/WebCopyConstants.java @@ -37,6 +37,10 @@ public abstract class WebCopyConstants { * 查询参数注解 */ public static final String QUERY_PARAM_ANNOTATION = "org.springframework.web.bind.annotation.RequestParam"; + /** + * 报文体参数注解 + */ + public static final String BODY_ANNOTATION = "org.springframework.web.bind.annotation.RequestBody"; /** * 查询参数忽略注解 */ diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java index a08d016..3f26c47 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java @@ -1,5 +1,6 @@ package com.github.passerr.idea.plugins.spring.web.po; +import com.github.passerr.idea.plugins.spring.web.AliasType; import com.github.passerr.idea.plugins.spring.web.ResourceUtil; import com.github.passerr.idea.plugins.spring.web.WebCopyConstants; import com.intellij.util.xmlb.annotations.AbstractCollection; @@ -77,6 +78,22 @@ public class ApiDocSettingPo { this.template.append(template); } + /** + * 别名获取 + * @param type 类型全称 + * @return 别名 + */ + public String alias(String type) { + return + this.getObjects() + .stream() + .filter(it -> Objects.equals(it.getType(), type)) + .map(ApiDocObjectSerialPo::getAlias) + .findFirst() + // 未知别名类型 + .orElse(AliasType.UNKNOWN_ALIAS); + } + @Override public boolean equals(Object o) { if (this == o) { return true; } -- Gitee From 24f187d0c2d0931a2d71f630ed667bdffed3c3c0 Mon Sep 17 00:00:00 2001 From: PasseRR Date: Mon, 5 Jul 2021 18:34:49 +0800 Subject: [PATCH 14/41] =?UTF-8?q?api=E6=96=87=E6=A1=A3=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E8=8E=B7=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugins/spring/web/ApiDocConfigViews.java | 78 +++++++++++++++++++ .../spring/web/CopyMethodApiDocAction.java | 34 +++++--- .../spring/web/po/ApiDocSettingPo.java | 9 ++- 3 files changed, 108 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java index 874387c..ed2abd8 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java @@ -391,6 +391,74 @@ public abstract class ApiDocConfigViews { .createPanel(); } }; + PanelWithButtons bottom = new IdeaPanelWithButtons("忽略注解(字段上的注解):") { + @Override + protected JComponent createMainComponent() { + BaseTableModel model = new BaseTableModel<>( + Collections.singletonList("注解"), setting.getBodyIgnoreAnnotations()); + JBTable table = new IdeaJbTable(model); + // 弹出层构建器 + BiFunction function = (s, r) -> { + JPanel p = new JPanel(new GridBagLayout()); + GridBagConstraints gb = new GridBagConstraints(0, 0, 1, 1, 0, 0, + GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, + JBUI.insets(0, 0, 5, 10), 0, 0 + ); + JLabel typeLabel = new JLabel("注解"); + p.add(typeLabel, gb); + JTextField textField = new JTextField(s.toString()); + textField.getDocument() + .addDocumentListener( + new com.intellij.ui.DocumentAdapter() { + @Override + protected void textChanged(javax.swing.event.DocumentEvent e) { + s.setLength(0); + s.append(textField.getText()); + r.run(); + } + }); + Dimension oldPreferredSize = textField.getPreferredSize(); + textField.setPreferredSize(new Dimension(300, oldPreferredSize.height)); + gb.gridx = 1; + gb.gridwidth = GridBagConstraints.REMAINDER; + gb.weightx = 1; + p.add(textField, gb); + r.run(); + + return p; + }; + return + ToolbarDecorator.createDecorator(table) + .setAddAction(it -> + new IdeaDialog(panel) + .title("新增忽略注解") + .value(new StringBuilder()) + .okAction(t -> setting.getBodyIgnoreAnnotations().add(t.toString())) + .changePredicate(t -> t.length() > 0) + .componentFunction(t -> function.apply(t.getValue(), t::onChange)) + .doInit() + .showAndGet() + ) + .setAddActionName("新增") + .setEditAction(it -> + new IdeaDialog(panel) + .title("编辑忽略注解") + .value(new StringBuilder(model.getRow(table.getSelectedRow()))) + .okAction(t -> + setting.getBodyIgnoreAnnotations().set(table.getSelectedRow(), t.toString()) + ) + .changePredicate(t -> t.length() > 0) + .componentFunction(t -> function.apply(t.getValue(), t::onChange)) + .doInit() + .showAndGet() + ) + .setEditActionName("编辑") + .setRemoveAction(it -> model.removeRow(table.getSelectedRow())) + .setRemoveActionName("删除") + .disableUpDownActions() + .createPanel(); + } + }; panel.add( top, new GridBagConstraints( @@ -401,6 +469,16 @@ public abstract class ApiDocConfigViews { ) ); + panel.add( + bottom, + new GridBagConstraints( + 0, 1, 1, 1, 1, 1, + GridBagConstraints.NORTH, + GridBagConstraints.BOTH, + JBUI.emptyInsets(), 0, 0 + ) + ); + return panel; } diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.java index 4256f23..3f38e6b 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.java @@ -5,6 +5,7 @@ import com.intellij.codeInsight.AnnotationUtil; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.psi.PsiAnnotation; import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiField; import com.intellij.psi.PsiMethod; import com.intellij.psi.PsiModifier; import com.intellij.psi.PsiModifierList; @@ -169,29 +170,25 @@ public class CopyMethodApiDocAction extends BaseWebCopyAction { // 对象类型 if (UNKNOWN_ALIAS.equals(alias)) { PsiClass clazz = PsiTypesUtil.getPsiClass(it.getType()); + // 未知类型 if (Objects.isNull(clazz)) { return Stream.empty(); } return Arrays.stream(clazz.getAllFields()) - .filter(f -> { - PsiModifierList modifierList = f.getModifierList(); - if (Objects.isNull(modifierList)) { - return true; - } - - return !modifierList.hasExplicitModifier(PsiModifier.STATIC) - && !modifierList.hasExplicitModifier(PsiModifier.TRANSIENT); - }) + .filter(CopyMethodApiDocAction::isValidFiled) + // 查询参数只遍历一层 且忽略掉这层的未知类型 .filter(f -> !UNKNOWN_ALIAS.equals(state.alias(f.getType().getCanonicalText()))) .map(f -> new Var( f.getName(), f.getType().getCanonicalText(), state.alias(f.getType().getCanonicalText()), - // TODO 获得字段注释 - comments.getOrDefault(it.getText(), it.getText()) + // TODO 获取字段注释 + Optional.ofNullable(f.getDocComment()) + .map(PsiDocComment::getText) + .orElse(f.getName()) ) ); } else { @@ -240,6 +237,21 @@ public class CopyMethodApiDocAction extends BaseWebCopyAction { && AnnotationUtil.findAnnotations(parameter, excludeAnnotations).length == 0; } + /** + * 是否是合法字段 + * @param field {@link PsiField} + * @return 非static、transient字段 + */ + private static boolean isValidFiled(PsiField field) { + PsiModifierList modifierList = field.getModifierList(); + if (Objects.isNull(modifierList)) { + return true; + } + + return !modifierList.hasExplicitModifier(PsiModifier.STATIC) + && !modifierList.hasExplicitModifier(PsiModifier.TRANSIENT); + } + /** * 参数实体 */ diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java index 3f26c47..c83c745 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java @@ -37,15 +37,19 @@ public class ApiDocSettingPo { @Tag("body-ignore-types") @AbstractCollection List bodyIgnoreTypes; + @Tag("body-ignore-annotations") + @AbstractCollection + List bodyIgnoreAnnotations; @Tag("objects") @AbstractCollection(elementTypes = ApiDocObjectSerialPo.class) List objects; public ApiDocSettingPo() { this.template = new StringBuilder(ResourceUtil.readAsString("/api-doc-template.vm")); - this.bodyIgnoreTypes = new ArrayList<>(WebCopyConstants.ALL_IGNORE_TYPES); this.queryParamIgnoreTypes = new ArrayList<>(WebCopyConstants.QUERY_PARAM_IGNORE_TYPES); this.queryParamIgnoreAnnotations = new ArrayList<>(WebCopyConstants.QUERY_PARAM_IGNORE_ANNOTATIONS); + this.bodyIgnoreTypes = new ArrayList<>(WebCopyConstants.ALL_IGNORE_TYPES); + this.bodyIgnoreAnnotations = new ArrayList<>(); this.objects = ApiDocObjectSerialPo.defaultObjects(); } @@ -53,9 +57,10 @@ public class ApiDocSettingPo { return new ApiDocSettingPo( new StringBuilder(this.template), - new ArrayList<>(this.bodyIgnoreTypes), new ArrayList<>(this.queryParamIgnoreTypes), new ArrayList<>(this.queryParamIgnoreAnnotations), + new ArrayList<>(this.bodyIgnoreTypes), + new ArrayList<>(this.bodyIgnoreAnnotations), this.objects.stream().map(ApiDocObjectSerialPo::deepCopy).collect(Collectors.toList()) ); } -- Gitee From bcfbbcba2ce21d91cdb83d7cfa6e25bd6f36ea6c Mon Sep 17 00:00:00 2001 From: PasseRR Date: Tue, 6 Jul 2021 14:20:30 +0800 Subject: [PATCH 15/41] =?UTF-8?q?controller=E6=9F=A5=E8=AF=A2=E5=8F=82?= =?UTF-8?q?=E6=95=B0=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 +- .../plugins/spring/web/ApiDocConfigViews.java | 22 +- .../spring/web/CopyMethodApiDocAction.java | 25 +- .../web/highlight/FileTemplateTextLexer.java | 918 +++++++++--------- .../web/highlight/FileTemplateTokenType.java | 1 + .../spring/web/po/ApiDocSettingPo.java | 24 +- 6 files changed, 520 insertions(+), 472 deletions(-) diff --git a/build.gradle b/build.gradle index e9d66ac..d2edccd 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,7 @@ repositories { intellij { // 不修改plugin.xml的idea-version - updateSinceUntilBuild false + updateSinceUntilBuild true //IntelliJ IDEA 2016.3 dependency; for a full list of IntelliJ IDEA releases please see https://www.jetbrains.com/intellij-repository/releases version '162.1628.40' } diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java index ed2abd8..49237d6 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java @@ -19,7 +19,9 @@ import com.intellij.openapi.editor.event.DocumentEvent; import com.intellij.openapi.editor.ex.EditorEx; import com.intellij.openapi.editor.ex.util.LayerDescriptor; import com.intellij.openapi.editor.ex.util.LayeredLexerEditorHighlighter; +import com.intellij.openapi.editor.highlighter.EditorHighlighter; import com.intellij.openapi.fileTypes.FileTypes; +import com.intellij.openapi.fileTypes.PlainSyntaxHighlighter; import com.intellij.openapi.fileTypes.SyntaxHighlighter; import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory; import com.intellij.openapi.util.Pair; @@ -97,12 +99,8 @@ public abstract class ApiDocConfigViews { editorSettings.setAdditionalColumnsCount(3); editorSettings.setAdditionalLinesCount(3); editorSettings.setCaretRowShown(false); - SyntaxHighlighter ohl = SyntaxHighlighterFactory.getSyntaxHighlighter(FileTypes.PLAIN_TEXT, null, null); - final EditorColorsScheme scheme = EditorColorsManager.getInstance().getGlobalScheme(); - LayeredLexerEditorHighlighter highlighter = - new LayeredLexerEditorHighlighter(new TemplateHighlighter(), scheme); - highlighter.registerLayer(FileTemplateTokenType.TEXT, new LayerDescriptor(ohl, "")); - ((EditorEx) editor).setHighlighter(highlighter); + ((EditorEx) editor).setHighlighter(createVelocityHighlight()); + JPanel templatePanel = new JPanel(new GridBagLayout()); templatePanel.add( SeparatorFactory.createSeparator("模版:", null), @@ -626,4 +624,16 @@ public abstract class ApiDocConfigViews { return panel; } + + private static EditorHighlighter createVelocityHighlight() { + SyntaxHighlighter ohl = + Optional.ofNullable(SyntaxHighlighterFactory.getSyntaxHighlighter(FileTypes.PLAIN_TEXT, null, null)) + .orElseGet(PlainSyntaxHighlighter::new); + final EditorColorsScheme scheme = EditorColorsManager.getInstance().getGlobalScheme(); + LayeredLexerEditorHighlighter highlighter = + new LayeredLexerEditorHighlighter(new TemplateHighlighter(), scheme); + highlighter.registerLayer(FileTemplateTokenType.TEXT, new LayerDescriptor(ohl, "")); + + return highlighter; + } } diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.java index 3f38e6b..d0934a4 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.java @@ -99,7 +99,7 @@ public class CopyMethodApiDocAction extends BaseWebCopyAction { Arrays.stream(it.getDataElements()) .filter(e -> e instanceof PsiDocToken) .map(PsiDocToken.class::cast) - .filter(e -> "DOC_COMMENT_DATA".equals(e.getTokenType().toString())) + .filter(CopyMethodApiDocAction::isDocCommentData) .map(e -> e.getText().trim()) .collect(Collectors.joining("")) ) @@ -185,10 +185,17 @@ public class CopyMethodApiDocAction extends BaseWebCopyAction { f.getName(), f.getType().getCanonicalText(), state.alias(f.getType().getCanonicalText()), - // TODO 获取字段注释 Optional.ofNullable(f.getDocComment()) - .map(PsiDocComment::getText) - .orElse(f.getName()) + .map(PsiDocComment::getDescriptionElements) + .map(els -> + Arrays.stream(els) + .filter(e -> e instanceof PsiDocToken) + .map(PsiDocToken.class::cast) + .filter(CopyMethodApiDocAction::isDocCommentData) + .map(e -> e.getText().trim()) + .collect(Collectors.joining("")) + ) + .orElseGet(f::getName) ) ); } else { @@ -252,6 +259,15 @@ public class CopyMethodApiDocAction extends BaseWebCopyAction { && !modifierList.hasExplicitModifier(PsiModifier.TRANSIENT); } + /** + * 是否是注释文本部分 + * @param token {@link PsiDocToken} + * @return true/false + */ + private static boolean isDocCommentData(PsiDocToken token) { + return "DOC_COMMENT_DATA".equals(token.getTokenType().toString()); + } + /** * 参数实体 */ @@ -273,6 +289,5 @@ public class CopyMethodApiDocAction extends BaseWebCopyAction { * 参数描述 */ String desc; - } } diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/highlight/FileTemplateTextLexer.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/highlight/FileTemplateTextLexer.java index fef0819..47777be 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/highlight/FileTemplateTextLexer.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/highlight/FileTemplateTextLexer.java @@ -1,511 +1,525 @@ -package com.github.passerr.idea.plugins.spring.web.highlight; - -/* The following code was generated by JFlex 1.7.0-SNAPSHOT tweaked for IntelliJ platform */ +/* The following code was generated by JFlex 1.7.0 tweaked for IntelliJ platform */ /* It's an automatically generated code. Do not modify it. */ +package com.github.passerr.idea.plugins.spring.web.highlight; import com.intellij.lexer.FlexLexer; import com.intellij.psi.tree.IElementType; /** - * com.intellij.ide.fileTemplates.impl._FileTemplateTextLexer * This class is a scanner generated by - * JFlex 1.7.0-SNAPSHOT + * JFlex 1.7.0 * from the specification file FileTemplateTextLexer.flex */ -public class FileTemplateTextLexer implements FlexLexer { - - /** - * This character denotes the end of file - */ - public static final int YYEOF = -1; - - /** - * initial size of the lookahead buffer - */ - private static final int ZZ_BUFFERSIZE = 16384; - - /** - * lexical states - */ - public static final int YYINITIAL = 0; - - /** - * ZZ_LEXSTATE[l] is the state in the DFA for the lexical state l - * ZZ_LEXSTATE[l+1] is the state in the DFA for the lexical state l - * at the beginning of a line - * l is of the form l = 2*k, k a non negative integer - */ - private static final int ZZ_LEXSTATE[] = { - 0, 0 - }; - - /** - * Translates characters to character classes - * Chosen bits are [8, 6, 7] - * Total runtime size is 1040 bytes - */ - public static int ZZ_CMAP(int ch) { - return ZZ_CMAP_A[ZZ_CMAP_Y[ZZ_CMAP_Z[ch >> 13] | ((ch >> 7) & 0x3f)] | (ch & 0x7f)]; +class FileTemplateTextLexer implements FlexLexer { + + /** This character denotes the end of file */ + public static final int YYEOF = -1; + + /** initial size of the lookahead buffer */ + private static final int ZZ_BUFFERSIZE = 16384; + + /** lexical states */ + public static final int YYINITIAL = 0; + + /** + * ZZ_LEXSTATE[l] is the state in the DFA for the lexical state l + * ZZ_LEXSTATE[l+1] is the state in the DFA for the lexical state l + * at the beginning of a line + * l is of the form l = 2*k, k a non negative integer + */ + private static final int ZZ_LEXSTATE[] = { + 0, 0 + }; + + /** + * Translates characters to character classes + * Chosen bits are [8, 6, 7] + * Total runtime size is 1040 bytes + */ + public static int ZZ_CMAP(int ch) { + return ZZ_CMAP_A[ZZ_CMAP_Y[ZZ_CMAP_Z[ch>>13]|((ch>>7)&0x3f)]|(ch&0x7f)]; + } + + /* The ZZ_CMAP_Z table has 136 entries */ + static final char ZZ_CMAP_Z[] = zzUnpackCMap( + "\1\0\207\100"); + + /* The ZZ_CMAP_Y table has 128 entries */ + static final char ZZ_CMAP_Y[] = zzUnpackCMap( + "\1\0\177\200"); + + /* The ZZ_CMAP_A table has 256 entries */ + static final char ZZ_CMAP_A[] = zzUnpackCMap( + "\43\0\1\6\1\3\13\0\12\2\7\0\32\1\1\10\1\7\1\11\1\0\1\1\1\0\32\1\1\4\1\0\1"+ + "\5\202\0"); + + /** + * Translates DFA states to action switch labels. + */ + private static final int [] ZZ_ACTION = zzUnpackAction(); + + private static final String ZZ_ACTION_PACKED_0 = + "\1\0\5\1\1\2\1\0\1\3\1\0\1\4\1\0"+ + "\1\2\1\0"; + + private static int [] zzUnpackAction() { + int [] result = new int[14]; + int offset = 0; + offset = zzUnpackAction(ZZ_ACTION_PACKED_0, offset, result); + return result; + } + + private static int zzUnpackAction(String packed, int offset, int [] result) { + int i = 0; /* index in packed string */ + int j = offset; /* index in unpacked array */ + int l = packed.length(); + while (i < l) { + int count = packed.charAt(i++); + int value = packed.charAt(i++); + do { + result[j++] = value; + } + while (--count > 0); } - - /* The ZZ_CMAP_Z table has 136 entries */ - static final char ZZ_CMAP_Z[] = zzUnpackCMap( - "\1\0\207\100"); - - /* The ZZ_CMAP_Y table has 128 entries */ - static final char ZZ_CMAP_Y[] = zzUnpackCMap( - "\1\0\177\200"); - - /* The ZZ_CMAP_A table has 256 entries */ - static final char ZZ_CMAP_A[] = zzUnpackCMap( - "\43\0\1\6\1\3\13\0\12\2\7\0\32\1\4\0\1\1\1\0\32\1\1\4\1\0\1\5\202\0"); - - /** - * Translates DFA states to action switch labels. - */ - private static final int[] ZZ_ACTION = zzUnpackAction(); - - private static final String ZZ_ACTION_PACKED_0 = - "\1\0\3\1\1\2\1\0\1\3\1\0\1\2"; - - private static int[] zzUnpackAction() { - int[] result = new int[9]; - int offset = 0; - offset = zzUnpackAction(ZZ_ACTION_PACKED_0, offset, result); - return result; + return j; + } + + + /** + * Translates a state to a row index in the transition table + */ + private static final int [] ZZ_ROWMAP = zzUnpackRowMap(); + + private static final String ZZ_ROWMAP_PACKED_0 = + "\0\0\0\12\0\24\0\36\0\50\0\62\0\74\0\106"+ + "\0\120\0\132\0\12\0\144\0\12\0\156"; + + private static int [] zzUnpackRowMap() { + int [] result = new int[14]; + int offset = 0; + offset = zzUnpackRowMap(ZZ_ROWMAP_PACKED_0, offset, result); + return result; + } + + private static int zzUnpackRowMap(String packed, int offset, int [] result) { + int i = 0; /* index in packed string */ + int j = offset; /* index in unpacked array */ + int l = packed.length(); + while (i < l) { + int high = packed.charAt(i++) << 16; + result[j++] = high | packed.charAt(i++); } - - private static int zzUnpackAction(String packed, int offset, int[] result) { - int i = 0; /* index in packed string */ - int j = offset; /* index in unpacked array */ - int l = packed.length(); - while (i < l) { - int count = packed.charAt(i++); - int value = packed.charAt(i++); - do { result[j++] = value; } while (--count > 0); - } - return j; + return j; + } + + /** + * The transition table of the DFA + */ + private static final int [] ZZ_TRANS = zzUnpackTrans(); + + private static final String ZZ_TRANS_PACKED_0 = + "\3\2\1\3\2\2\1\4\1\5\1\2\1\6\13\0"+ + "\2\7\1\0\1\10\6\0\1\11\6\0\1\12\4\0"+ + "\1\13\2\0\1\13\14\0\1\14\1\0\2\7\1\15"+ + "\7\0\2\16\10\0\1\11\20\0\1\13\7\0\1\13"+ + "\4\0\2\16\2\0\1\15\4\0"; + + private static int [] zzUnpackTrans() { + int [] result = new int[120]; + int offset = 0; + offset = zzUnpackTrans(ZZ_TRANS_PACKED_0, offset, result); + return result; + } + + private static int zzUnpackTrans(String packed, int offset, int [] result) { + int i = 0; /* index in packed string */ + int j = offset; /* index in unpacked array */ + int l = packed.length(); + while (i < l) { + int count = packed.charAt(i++); + int value = packed.charAt(i++); + value--; + do { + result[j++] = value; + } + while (--count > 0); } - - - /** - * Translates a state to a row index in the transition table - */ - private static final int[] ZZ_ROWMAP = zzUnpackRowMap(); - - private static final String ZZ_ROWMAP_PACKED_0 = - "\0\0\0\7\0\16\0\25\0\34\0\43\0\25\0\52" + - "\0\7"; - - private static int[] zzUnpackRowMap() { - int[] result = new int[9]; - int offset = 0; - offset = zzUnpackRowMap(ZZ_ROWMAP_PACKED_0, offset, result); - return result; + return j; + } + + + /* error codes */ + private static final int ZZ_UNKNOWN_ERROR = 0; + private static final int ZZ_NO_MATCH = 1; + private static final int ZZ_PUSHBACK_2BIG = 2; + + /* error messages for the codes above */ + private static final String[] ZZ_ERROR_MSG = { + "Unknown internal scanner error", + "Error: could not match input", + "Error: pushback value was too large" + }; + + /** + * ZZ_ATTRIBUTE[aState] contains the attributes of state aState + */ + private static final int [] ZZ_ATTRIBUTE = zzUnpackAttribute(); + + private static final String ZZ_ATTRIBUTE_PACKED_0 = + "\1\0\1\11\5\1\1\0\1\1\1\0\1\11\1\0"+ + "\1\11\1\0"; + + private static int [] zzUnpackAttribute() { + int [] result = new int[14]; + int offset = 0; + offset = zzUnpackAttribute(ZZ_ATTRIBUTE_PACKED_0, offset, result); + return result; + } + + private static int zzUnpackAttribute(String packed, int offset, int [] result) { + int i = 0; /* index in packed string */ + int j = offset; /* index in unpacked array */ + int l = packed.length(); + while (i < l) { + int count = packed.charAt(i++); + int value = packed.charAt(i++); + do { + result[j++] = value; + } + while (--count > 0); } + return j; + } - private static int zzUnpackRowMap(String packed, int offset, int[] result) { - int i = 0; /* index in packed string */ - int j = offset; /* index in unpacked array */ - int l = packed.length(); - while (i < l) { - int high = packed.charAt(i++) << 16; - result[j++] = high | packed.charAt(i++); - } - return j; - } + /** the input device */ + private java.io.Reader zzReader; - /** - * The transition table of the DFA - */ - private static final int[] ZZ_TRANS = zzUnpackTrans(); - - private static final String ZZ_TRANS_PACKED_0 = - "\3\2\1\3\2\2\1\4\10\0\2\5\1\0\1\6" + - "\3\0\1\7\6\0\2\5\5\0\2\10\5\0\2\10" + - "\2\0\1\11\1\0"; - - private static int[] zzUnpackTrans() { - int[] result = new int[49]; - int offset = 0; - offset = zzUnpackTrans(ZZ_TRANS_PACKED_0, offset, result); - return result; - } + /** the current state of the DFA */ + private int zzState; - private static int zzUnpackTrans(String packed, int offset, int[] result) { - int i = 0; /* index in packed string */ - int j = offset; /* index in unpacked array */ - int l = packed.length(); - while (i < l) { - int count = packed.charAt(i++); - int value = packed.charAt(i++); - value--; - do { result[j++] = value; } while (--count > 0); - } - return j; - } + /** the current lexical state */ + private int zzLexicalState = YYINITIAL; + /** this buffer contains the current text to be matched and is + the source of the yytext() string */ + private CharSequence zzBuffer = ""; - /* error codes */ - private static final int ZZ_UNKNOWN_ERROR = 0; - private static final int ZZ_NO_MATCH = 1; - private static final int ZZ_PUSHBACK_2BIG = 2; + /** the textposition at the last accepting state */ + private int zzMarkedPos; - /* error messages for the codes above */ - private static final String[] ZZ_ERROR_MSG = { - "Unknown internal scanner error", - "Error: could not match input", - "Error: pushback value was too large" - }; + /** the current text position in the buffer */ + private int zzCurrentPos; - /** - * ZZ_ATTRIBUTE[aState] contains the attributes of state aState - */ - private static final int[] ZZ_ATTRIBUTE = zzUnpackAttribute(); + /** startRead marks the beginning of the yytext() string in the buffer */ + private int zzStartRead; - private static final String ZZ_ATTRIBUTE_PACKED_0 = - "\1\0\1\11\3\1\1\0\1\1\1\0\1\11"; + /** endRead marks the last character in the buffer, that has been read + from input */ + private int zzEndRead; - private static int[] zzUnpackAttribute() { - int[] result = new int[9]; - int offset = 0; - offset = zzUnpackAttribute(ZZ_ATTRIBUTE_PACKED_0, offset, result); - return result; - } + /** + * zzAtBOL == true <=> the scanner is currently at the beginning of a line + */ + private boolean zzAtBOL = true; - private static int zzUnpackAttribute(String packed, int offset, int[] result) { - int i = 0; /* index in packed string */ - int j = offset; /* index in unpacked array */ - int l = packed.length(); - while (i < l) { - int count = packed.charAt(i++); - int value = packed.charAt(i++); - do { result[j++] = value; } while (--count > 0); - } - return j; - } + /** zzAtEOF == true <=> the scanner is at the EOF */ + private boolean zzAtEOF; - /** - * the input device - */ - private java.io.Reader zzReader; - - /** - * the current state of the DFA - */ - private int zzState; - - /** - * the current lexical state - */ - private int zzLexicalState = YYINITIAL; - - /** - * this buffer contains the current text to be matched and is - * the source of the yytext() string - */ - private CharSequence zzBuffer = ""; - - /** - * the textposition at the last accepting state - */ - private int zzMarkedPos; - - /** - * the current text position in the buffer - */ - private int zzCurrentPos; - - /** - * startRead marks the beginning of the yytext() string in the buffer - */ - private int zzStartRead; - - /** - * endRead marks the last character in the buffer, that has been read - * from input - */ - private int zzEndRead; - - /** - * zzAtBOL == true <=> the scanner is currently at the beginning of a line - */ - private boolean zzAtBOL = true; - - /** - * zzAtEOF == true <=> the scanner is at the EOF - */ - private boolean zzAtEOF; - - /** - * denotes if the user-EOF-code has already been executed - */ - private boolean zzEOFDone; - - /* user code: */ - public FileTemplateTextLexer() { - this((java.io.Reader) null); - } + /** denotes if the user-EOF-code has already been executed */ + private boolean zzEOFDone; + /* user code: */ + public FileTemplateTextLexer() { + this(null); + } - /** - * Creates a new scanner - * @param in the java.io.Reader to read input from. - */ - FileTemplateTextLexer(java.io.Reader in) { - this.zzReader = in; - } + /** + * Creates a new scanner + * + * @param in the java.io.Reader to read input from. + */ + FileTemplateTextLexer(java.io.Reader in) { + this.zzReader = in; + } - /** - * Unpacks the compressed character translation table. - * @param packed the packed character translation table - * @return the unpacked character translation table - */ - private static char[] zzUnpackCMap(String packed) { - int size = 0; - for (int i = 0, length = packed.length(); i < length; i += 2) { - size += packed.charAt(i); - } - char[] map = new char[size]; - int i = 0; /* index in packed string */ - int j = 0; /* index in unpacked array */ - while (i < packed.length()) { - int count = packed.charAt(i++); - char value = packed.charAt(i++); - do { map[j++] = value; } while (--count > 0); - } - return map; - } - @Override - public final int getTokenStart() { - return zzStartRead; + /** + * Unpacks the compressed character translation table. + * + * @param packed the packed character translation table + * @return the unpacked character translation table + */ + private static char [] zzUnpackCMap(String packed) { + int size = 0; + for (int i = 0, length = packed.length(); i < length; i += 2) { + size += packed.charAt(i); } - - @Override - public final int getTokenEnd() { - return getTokenStart() + yylength(); + char[] map = new char[size]; + int i = 0; /* index in packed string */ + int j = 0; /* index in unpacked array */ + while (i < packed.length()) { + int count = packed.charAt(i++); + char value = packed.charAt(i++); + do { + map[j++] = value; + } + while (--count > 0); } - - @Override - public void reset(CharSequence buffer, int start, int end, int initialState) { - zzBuffer = buffer; - zzCurrentPos = zzMarkedPos = zzStartRead = start; - zzAtEOF = false; - zzAtBOL = true; - zzEndRead = end; - yybegin(initialState); - } - - /** - * Refills the input buffer. - * @return false, iff there was new input. - * @throws java.io.IOException if any I/O-Error occurs - */ - private boolean zzRefill() throws java.io.IOException { - return true; - } - - - /** - * Returns the current lexical state. - */ - @Override - public final int yystate() { - return zzLexicalState; - } - - - /** - * Enters a new lexical state - * @param newState the new lexical state - */ - @Override - public final void yybegin(int newState) { - zzLexicalState = newState; + return map; + } + + @Override + public final int getTokenStart() { + return zzStartRead; + } + + @Override + public final int getTokenEnd() { + return getTokenStart() + yylength(); + } + + @Override + public void reset(CharSequence buffer, int start, int end, int initialState) { + zzBuffer = buffer; + zzCurrentPos = zzMarkedPos = zzStartRead = start; + zzAtEOF = false; + zzAtBOL = true; + zzEndRead = end; + yybegin(initialState); + } + + /** + * Refills the input buffer. + * + * @return {@code false}, iff there was new input. + * + * @exception java.io.IOException if any I/O-Error occurs + */ + private boolean zzRefill() throws java.io.IOException { + return true; + } + + + /** + * Returns the current lexical state. + */ + @Override + public final int yystate() { + return zzLexicalState; + } + + + /** + * Enters a new lexical state + * + * @param newState the new lexical state + */ + @Override + public final void yybegin(int newState) { + zzLexicalState = newState; + } + + + /** + * Returns the text matched by the current regular expression. + */ + public final CharSequence yytext() { + return zzBuffer.subSequence(zzStartRead, zzMarkedPos); + } + + + /** + * Returns the character at position {@code pos} from the + * matched text. + * + * It is equivalent to yytext().charAt(pos), but faster + * + * @param pos the position of the character to fetch. + * A value from 0 to yylength()-1. + * + * @return the character at position pos + */ + public final char yycharat(int pos) { + return zzBuffer.charAt(zzStartRead+pos); + } + + + /** + * Returns the length of the matched text region. + */ + public final int yylength() { + return zzMarkedPos-zzStartRead; + } + + + /** + * Reports an error that occurred while scanning. + * + * In a wellformed scanner (no or only correct usage of + * yypushback(int) and a match-all fallback rule) this method + * will only be called with things that "Can't Possibly Happen". + * If this method is called, something is seriously wrong + * (e.g. a JFlex bug producing a faulty scanner etc.). + * + * Usual syntax/scanner level error handling should be done + * in error fallback rules. + * + * @param errorCode the code of the errormessage to display + */ + private void zzScanError(int errorCode) { + String message; + try { + message = ZZ_ERROR_MSG[errorCode]; } - - - /** - * Returns the text matched by the current regular expression. - */ - public final CharSequence yytext() { - return zzBuffer.subSequence(zzStartRead, zzMarkedPos); + catch (ArrayIndexOutOfBoundsException e) { + message = ZZ_ERROR_MSG[ZZ_UNKNOWN_ERROR]; } - - /** - * Returns the character at position pos from the - * matched text. - *

- * It is equivalent to yytext().charAt(pos), but faster - * @param pos the position of the character to fetch. - * A value from 0 to yylength()-1. - * @return the character at position pos - */ - public final char yycharat(int pos) { - return zzBuffer.charAt(zzStartRead + pos); + throw new Error(message); + } + + + /** + * Pushes the specified amount of characters back into the input stream. + * + * They will be read again by then next call of the scanning method + * + * @param number the number of characters to be read again. + * This number must not be greater than yylength()! + */ + public void yypushback(int number) { + if ( number > yylength() ) { + zzScanError(ZZ_PUSHBACK_2BIG); } - - /** - * Returns the length of the matched text region. - */ - public final int yylength() { - return zzMarkedPos - zzStartRead; - } + zzMarkedPos -= number; + } - /** - * Reports an error that occured while scanning. - *

- * In a wellformed scanner (no or only correct usage of - * yypushback(int) and a match-all fallback rule) this method - * will only be called with things that "Can't Possibly Happen". - * If this method is called, something is seriously wrong - * (e.g. a JFlex bug producing a faulty scanner etc.). - *

- * Usual syntax/scanner level error handling should be done - * in error fallback rules. - * @param errorCode the code of the errormessage to display - */ - private void zzScanError(int errorCode) { - String message; - try { - message = ZZ_ERROR_MSG[errorCode]; - } catch (ArrayIndexOutOfBoundsException e) { - message = ZZ_ERROR_MSG[ZZ_UNKNOWN_ERROR]; - } + /** + * Resumes scanning until the next regular expression is matched, + * the end of input is encountered or an I/O-Error occurs. + * + * @return the next token + * @exception java.io.IOException if any I/O-Error occurs + */ + @Override + public IElementType advance() throws java.io.IOException { + int zzInput; + int zzAction; - throw new Error(message); - } + // cached fields: + int zzCurrentPosL; + int zzMarkedPosL; + int zzEndReadL = zzEndRead; + CharSequence zzBufferL = zzBuffer; + int [] zzTransL = ZZ_TRANS; + int [] zzRowMapL = ZZ_ROWMAP; + int [] zzAttrL = ZZ_ATTRIBUTE; - /** - * Pushes the specified amount of characters back into the input stream. - *

- * They will be read again by then next call of the scanning method - * @param number the number of characters to be read again. - * This number must not be greater than yylength()! - */ - public void yypushback(int number) { - if (number > yylength()) { zzScanError(ZZ_PUSHBACK_2BIG); } + while (true) { + zzMarkedPosL = zzMarkedPos; - zzMarkedPos -= number; - } + zzAction = -1; + zzCurrentPosL = zzCurrentPos = zzStartRead = zzMarkedPosL; - /** - * Resumes scanning until the next regular expression is matched, - * the end of input is encountered or an I/O-Error occurs. - * @return the next token - * @throws java.io.IOException if any I/O-Error occurs - */ - @Override - public IElementType advance() throws java.io.IOException { - int zzInput; - int zzAction; + zzState = ZZ_LEXSTATE[zzLexicalState]; - // cached fields: - int zzCurrentPosL; - int zzMarkedPosL; - int zzEndReadL = zzEndRead; - CharSequence zzBufferL = zzBuffer; + // set up zzAction for empty match case: + int zzAttributes = zzAttrL[zzState]; + if ( (zzAttributes & 1) == 1 ) { + zzAction = zzState; + } - int[] zzTransL = ZZ_TRANS; - int[] zzRowMapL = ZZ_ROWMAP; - int[] zzAttrL = ZZ_ATTRIBUTE; + zzForAction: { while (true) { - zzMarkedPosL = zzMarkedPos; - - zzAction = -1; - zzCurrentPosL = zzCurrentPos = zzStartRead = zzMarkedPosL; - - zzState = ZZ_LEXSTATE[zzLexicalState]; - - // set up zzAction for empty match case: - int zzAttributes = zzAttrL[zzState]; - if ((zzAttributes & 1) == 1) { - zzAction = zzState; + if (zzCurrentPosL < zzEndReadL) { + zzInput = Character.codePointAt(zzBufferL, zzCurrentPosL/*, zzEndReadL*/); + zzCurrentPosL += Character.charCount(zzInput); + } + else if (zzAtEOF) { + zzInput = YYEOF; + break zzForAction; + } + else { + // store back cached positions + zzCurrentPos = zzCurrentPosL; + zzMarkedPos = zzMarkedPosL; + boolean eof = zzRefill(); + // get translated positions and possibly new buffer + zzCurrentPosL = zzCurrentPos; + zzMarkedPosL = zzMarkedPos; + zzBufferL = zzBuffer; + zzEndReadL = zzEndRead; + if (eof) { + zzInput = YYEOF; + break zzForAction; } - - - zzForAction: - { - while (true) { - - if (zzCurrentPosL < zzEndReadL) { - zzInput = Character.codePointAt(zzBufferL, zzCurrentPosL/*, zzEndReadL*/); - zzCurrentPosL += Character.charCount(zzInput); - } else if (zzAtEOF) { - zzInput = YYEOF; - break zzForAction; - } else { - // store back cached positions - zzCurrentPos = zzCurrentPosL; - zzMarkedPos = zzMarkedPosL; - boolean eof = zzRefill(); - // get translated positions and possibly new buffer - zzCurrentPosL = zzCurrentPos; - zzMarkedPosL = zzMarkedPos; - zzBufferL = zzBuffer; - zzEndReadL = zzEndRead; - if (eof) { - zzInput = YYEOF; - break zzForAction; - } else { - zzInput = Character.codePointAt(zzBufferL, zzCurrentPosL/*, zzEndReadL*/); - zzCurrentPosL += Character.charCount(zzInput); - } - } - int zzNext = zzTransL[zzRowMapL[zzState] + ZZ_CMAP(zzInput)]; - if (zzNext == -1) { break zzForAction; } - zzState = zzNext; - - zzAttributes = zzAttrL[zzState]; - if ((zzAttributes & 1) == 1) { - zzAction = zzState; - zzMarkedPosL = zzCurrentPosL; - if ((zzAttributes & 8) == 8) { break zzForAction; } - } - - } + else { + zzInput = Character.codePointAt(zzBufferL, zzCurrentPosL/*, zzEndReadL*/); + zzCurrentPosL += Character.charCount(zzInput); } - - // store back cached position - zzMarkedPos = zzMarkedPosL; - - if (zzInput == YYEOF && zzStartRead == zzCurrentPos) { - zzAtEOF = true; - return null; - } else { - switch (zzAction < 0 ? zzAction : ZZ_ACTION[zzAction]) { - case 1: { - return FileTemplateTokenType.TEXT; - } - case 4: - break; - case 2: { - return FileTemplateTokenType.MACRO; - } - case 5: - break; - case 3: { - return FileTemplateTokenType.DIRECTIVE; - } - case 6: - break; - default: - zzScanError(ZZ_NO_MATCH); - } + } + int zzNext = zzTransL[ zzRowMapL[zzState] + ZZ_CMAP(zzInput) ]; + if (zzNext == -1) { + break zzForAction; + } + zzState = zzNext; + + zzAttributes = zzAttrL[zzState]; + if ( (zzAttributes & 1) == 1 ) { + zzAction = zzState; + zzMarkedPosL = zzCurrentPosL; + if ( (zzAttributes & 8) == 8 ) { + break zzForAction; } + } + } + } + + // store back cached position + zzMarkedPos = zzMarkedPosL; + + if (zzInput == YYEOF && zzStartRead == zzCurrentPos) { + zzAtEOF = true; + return null; + } + else { + switch (zzAction < 0 ? zzAction : ZZ_ACTION[zzAction]) { + case 1: + { return FileTemplateTokenType.TEXT; + } + // fall through + case 5: break; + case 2: + { return FileTemplateTokenType.MACRO; + } + // fall through + case 6: break; + case 3: + { return FileTemplateTokenType.DIRECTIVE; + } + // fall through + case 7: break; + case 4: + { return FileTemplateTokenType.ESCAPE; + } + // fall through + case 8: break; + default: + zzScanError(ZZ_NO_MATCH); + } + } } + } } diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/highlight/FileTemplateTokenType.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/highlight/FileTemplateTokenType.java index 9bb7eae..591fc8a 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/highlight/FileTemplateTokenType.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/highlight/FileTemplateTokenType.java @@ -10,6 +10,7 @@ import com.intellij.psi.tree.IElementType; * @Copyright(c) tellyes tech. inc. co.,ltd */ public interface FileTemplateTokenType { + IElementType ESCAPE = new IElementType("ESCAPE", Language.ANY); IElementType TEXT = new IElementType("TEXT", Language.ANY); IElementType MACRO = new IElementType("MACRO", Language.ANY); IElementType DIRECTIVE = new IElementType("DIRECTIVE", Language.ANY); diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java index c83c745..95a38f7 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java @@ -68,12 +68,14 @@ public class ApiDocSettingPo { public void shallowCopy(ApiDocSettingPo source) { this.template.setLength(0); this.template.append(source.template); - this.bodyIgnoreTypes.clear(); - this.bodyIgnoreTypes.addAll(source.getBodyIgnoreTypes()); this.queryParamIgnoreTypes.clear(); this.queryParamIgnoreTypes.addAll(source.getQueryParamIgnoreTypes()); this.queryParamIgnoreAnnotations.clear(); this.queryParamIgnoreAnnotations.addAll(source.getQueryParamIgnoreAnnotations()); + this.bodyIgnoreTypes.clear(); + this.bodyIgnoreTypes.addAll(source.getBodyIgnoreTypes()); + this.bodyIgnoreAnnotations.clear(); + this.bodyIgnoreAnnotations.addAll(source.getBodyIgnoreAnnotations()); this.objects.clear(); this.objects.addAll(source.getObjects()); } @@ -104,15 +106,21 @@ public class ApiDocSettingPo { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } ApiDocSettingPo that = (ApiDocSettingPo) o; - return Objects.equals(template.toString(), that.template.toString()) && - Objects.equals(bodyIgnoreTypes, that.bodyIgnoreTypes) && - Objects.equals(queryParamIgnoreTypes, that.queryParamIgnoreTypes) && - Objects.equals(queryParamIgnoreAnnotations, that.queryParamIgnoreAnnotations) && - Objects.equals(objects, that.objects); + return + Objects.equals(template.toString(), that.template.toString()) && + Objects.equals(bodyIgnoreTypes, that.bodyIgnoreTypes) && + Objects.equals(queryParamIgnoreTypes, that.queryParamIgnoreTypes) && + Objects.equals(queryParamIgnoreAnnotations, that.queryParamIgnoreAnnotations) && + Objects.equals(this.bodyIgnoreAnnotations, that.bodyIgnoreAnnotations) && + Objects.equals(objects, that.objects); } @Override public int hashCode() { - return Objects.hash(template, bodyIgnoreTypes, queryParamIgnoreTypes, queryParamIgnoreAnnotations, objects); + return + Objects.hash( + template, queryParamIgnoreTypes, queryParamIgnoreAnnotations, + bodyIgnoreTypes, bodyIgnoreAnnotations, objects + ); } } -- Gitee From e2a096ed97150bbd38be86fd59dbf37384bfde72 Mon Sep 17 00:00:00 2001 From: PasseRR Date: Fri, 9 Jul 2021 10:26:28 +0800 Subject: [PATCH 16/41] =?UTF-8?q?gradle=E5=8D=87=E7=BA=A7=E3=80=81json5?= =?UTF-8?q?=E5=B7=A5=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 27 +- gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 58910 bytes gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 185 +++++ gradlew.bat | 104 +++ .../spring/web/CopyMethodApiDocAction.java | 58 +- .../plugins/spring/web/json5/Json5Writer.java | 756 ++++++++++++++++++ .../plugins/spring/web/json5/JsonScope.java | 65 ++ .../spring/web/json5/package-info.java | 7 + src/main/resources/META-INF/plugin.xml | 3 - 10 files changed, 1182 insertions(+), 28 deletions(-) create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 src/main/java/com/github/passerr/idea/plugins/spring/web/json5/Json5Writer.java create mode 100644 src/main/java/com/github/passerr/idea/plugins/spring/web/json5/JsonScope.java create mode 100644 src/main/java/com/github/passerr/idea/plugins/spring/web/json5/package-info.java diff --git a/build.gradle b/build.gradle index d2edccd..d111757 100644 --- a/build.gradle +++ b/build.gradle @@ -1,34 +1,32 @@ group 'com.github.passerr' -version '1.2.0' +version '1.3.0' buildscript { repositories { maven { - url "https://plugins.gradle.org/m2/" + url 'https://maven.aliyun.com/repository/gradle-plugin' } } + dependencies { - classpath "gradle.plugin.org.jetbrains.intellij.plugins:gradle-intellij-plugin:0.3.9" + classpath "org.jetbrains.intellij.plugins:gradle-intellij-plugin:1.0" } } -apply plugin: 'idea' apply plugin: 'java' -apply plugin: 'org.jetbrains.intellij' +apply plugin: 'idea' +apply plugin: "org.jetbrains.intellij" repositories { + maven { + url 'https://maven.aliyun.com/repository/public' + } mavenCentral() } intellij { - // 不修改plugin.xml的idea-version - updateSinceUntilBuild true //IntelliJ IDEA 2016.3 dependency; for a full list of IntelliJ IDEA releases please see https://www.jetbrains.com/intellij-repository/releases - version '162.1628.40' -} - -tasks.withType(GroovyCompile) { - groovyOptions.encoding = "UTF-8" + version = '162.1628.40' } tasks.withType(JavaCompile) { @@ -37,6 +35,7 @@ tasks.withType(JavaCompile) { dependencies { compileOnly group: 'org.projectlombok', name: 'lombok', version: '1.18.2' - compile group: 'com.fifesoft', name: 'rsyntaxtextarea', version: '2.6.1' - compile group: 'com.google.code.gson', name: 'gson', version: '2.8.5' + annotationProcessor group: 'org.projectlombok', name: 'lombok', version: '1.18.2' + implementation group: 'com.fifesoft', name: 'rsyntaxtextarea', version: '2.6.1' + implementation group: 'blue.endless', name: 'jankson', version: '1.2.1' } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..62d4c053550b91381bbd28b1afc82d634bf73a8a GIT binary patch literal 58910 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 literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..622ab64 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..fbd7c51 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..5093609 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,104 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.java index d0934a4..886f4db 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.java @@ -1,5 +1,6 @@ package com.github.passerr.idea.plugins.spring.web; +import blue.endless.jankson.JsonElement; import com.github.passerr.idea.plugins.spring.web.po.ApiDocSettingPo; import com.intellij.codeInsight.AnnotationUtil; import com.intellij.openapi.actionSystem.AnActionEvent; @@ -36,19 +37,20 @@ import static com.github.passerr.idea.plugins.spring.web.AliasType.UNKNOWN_ALIAS public class CopyMethodApiDocAction extends BaseWebCopyAction { @Override public void actionPerformed(AnActionEvent e) { - PsiMethod method = method(e); - PsiAnnotation classAnnotation = classAnnotation(method); - PsiAnnotation methodAnnotation = methodAnnotation(method); + PsiMethod method = BaseWebCopyAction.method(e); + PsiAnnotation classAnnotation = BaseWebCopyAction.classAnnotation(method); + PsiAnnotation methodAnnotation = BaseWebCopyAction.methodAnnotation(method); ApiDocSettingPo state = ApiDocStateComponent.getInstance().getState(); - String url = url(classAnnotation, methodAnnotation); + String url = BaseWebCopyAction.url(classAnnotation, methodAnnotation); String httpMethod = getMethod(classAnnotation, methodAnnotation); // 方法参数注释缓存 Map comments = comments(method); // 路径参数列表 - List pathVariables = pathVariables(comments, method, state); + List pathVariables = pathVariables(method, comments, state); // 查询参数列表 - List queryVariables = queryParams(comments, method, state); + List queryVariables = queryParams(method, comments, state); // body + body(method, comments, state); // 请求示例 // 应答示例 } @@ -111,11 +113,12 @@ public class CopyMethodApiDocAction extends BaseWebCopyAction { /** * 获取接口路径参数 - * @param comments 方法注释 * @param method {@link PsiMethod} + * @param comments 方法注释 + * @param state 配置状态 * @return 路径参数列表 */ - private static List pathVariables(Map comments, PsiMethod method, ApiDocSettingPo state) { + private static List pathVariables(PsiMethod method, Map comments, ApiDocSettingPo state) { return Arrays.stream(method.getParameterList().getParameters()) .filter(CopyMethodApiDocAction::isValidParamType) @@ -145,12 +148,12 @@ public class CopyMethodApiDocAction extends BaseWebCopyAction { /** * 查询参数注解 - * @param comments 方法注释 * @param method {@link PsiMethod} + * @param comments 方法注释 * @param state 配置状态 * @return {@link List} */ - private static List queryParams(Map comments, PsiMethod method, ApiDocSettingPo state) { + private static List queryParams(PsiMethod method, Map comments, ApiDocSettingPo state) { return Arrays.stream(method.getParameterList().getParameters()) .filter(it -> @@ -216,6 +219,29 @@ public class CopyMethodApiDocAction extends BaseWebCopyAction { .collect(Collectors.toList()); } + /** + * 获得方法的报文参数 + * @param method {@link PsiMethod} + * @param comments 方法注释 + * @param state 配置状态 + * @return + */ + private static JsonElement body(PsiMethod method, Map comments, ApiDocSettingPo state) { + PsiParameter body = + Arrays.stream(method.getParameterList().getParameters()) + // 排除body参数类型为排除类型 即无法序列化的类型 如Object、Map等 + .filter(it -> isValidParamType(it, state.getBodyIgnoreTypes())) + // 方法参数中有@RequestBody注解的参数且只会取第一个 + .filter(it -> AnnotationUtil.findAnnotation(it, WebCopyConstants.BODY_ANNOTATION) != null) + .findFirst() + .orElse(null); + if (Objects.isNull(body)) { + return null; + } + + return null; + } + /** * 是否是合法的参数类型 * 非接口类型且满足排除类型 @@ -223,7 +249,17 @@ public class CopyMethodApiDocAction extends BaseWebCopyAction { * @return true/false */ private static boolean isValidParamType(PsiParameter parameter) { - return isValidParamType(parameter, new ArrayList<>(), new ArrayList<>()); + return isValidParamType(parameter, new ArrayList<>()); + } + + /** + * 是否是合法参数类型 + * @param parameter {@link PsiParameter} + * @param excludeTypes 排除类型 + * @return true/false + */ + private static boolean isValidParamType(PsiParameter parameter, List excludeTypes) { + return isValidParamType(parameter, excludeTypes, new ArrayList<>()); } /** diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/json5/Json5Writer.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/json5/Json5Writer.java new file mode 100644 index 0000000..577684e --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/json5/Json5Writer.java @@ -0,0 +1,756 @@ +package com.github.passerr.idea.plugins.spring.web.json5; + +import java.io.Closeable; +import java.io.Flushable; +import java.io.IOException; +import java.io.Writer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Objects; + +import static com.github.passerr.idea.plugins.spring.web.json5.JsonScope.DANGLING_NAME; +import static com.github.passerr.idea.plugins.spring.web.json5.JsonScope.EMPTY_ARRAY; +import static com.github.passerr.idea.plugins.spring.web.json5.JsonScope.EMPTY_DOCUMENT; +import static com.github.passerr.idea.plugins.spring.web.json5.JsonScope.EMPTY_OBJECT; +import static com.github.passerr.idea.plugins.spring.web.json5.JsonScope.NONEMPTY_ARRAY; +import static com.github.passerr.idea.plugins.spring.web.json5.JsonScope.NONEMPTY_DOCUMENT; +import static com.github.passerr.idea.plugins.spring.web.json5.JsonScope.NONEMPTY_OBJECT; + +/* + * Gson is Copyright (C) 2010 Google Inc, under the Apache License Version 2.0 (the same as in the header above). + * + * The following changes have bee applied: + * - The lenient mode has been replaced with a json5 mode, which only writes strict json5. + * - A strict JSON only mode has been added which will only write valid JSON. + * - Writer changes to handle JSON5 syntax extensions. + * - Add ability to write comments into the writer. + * - Repackaged. + * - Other minor code style changes. + * + * You may view the original, including its license header, here: + * https://github.com/google/gson/blob/530cb7447089ccc12dc2009c17f468ddf2cd61ca/gson/src/main/java/com/google/gson/stream/JsonReader.java + */ + +/** + * Writes a JSON5 or strict JSON (RFC 7159) + * encoded value to a stream, one token at a time. The stream includes both + * literal values (strings, numbers, booleans and nulls) as well as the begin + * and end delimiters of objects and arrays. + * + *

Encoding JSON

+ * To encode your data as JSON, create a new {@code JsonWriter}. Each JSON + * document must contain one top-level array or object. Call methods on the + * writer as you walk the structure's contents, nesting arrays and objects as + * necessary: + *
    + *
  • To write arrays, first call {@link #beginArray()}. + * Write each of the array's elements with the appropriate {@link #value} + * methods or by nesting other arrays and objects. Finally close the array + * using {@link #endArray()}. + *
  • To write objects, first call {@link #beginObject()}. + * Write each of the object's properties by alternating calls to + * {@link #name} with the property's value. Write property values with the + * appropriate {@link #value} method or by nesting other objects or arrays. + * Finally close the object using {@link #endObject()}. + *
+ * + *

Example

+ * Suppose we'd like to encode a stream of messages such as the following:
 {@code
+ * [
+ *   {
+ *     id: 912345678901,
+ *     text: "How do I stream JSON in Java?",
+ *     geo: null,
+ *     user: {
+ *       name: "json_newb",
+ *       "followers_count": 41
+ *      }
+ *   },
+ *   {
+ *     id: 912345678902,
+ *     text: "@json_newb just use JsonWriter!",
+ *     geo: [50.454722, -104.606667],
+ *     user: {
+ *       name: "jesse",
+ *       followers_count: 2
+ *     }
+ *   }
+ * ]}
+ * This code encodes the above structure:
   {@code
+ *   public void writeJsonStream(OutputStream out, List messages) throws IOException {
+ *     JsonWriter writer = new JsonWriter(new OutputStreamWriter(out, "UTF-8"));
+ *     writer.setIndent("    ");
+ *     writeMessagesArray(writer, messages);
+ *     writer.close();
+ *   }
+ *
+ *   public void writeMessagesArray(JsonWriter writer, List messages) throws IOException {
+ *     writer.beginArray();
+ *     for (Message message : messages) {
+ *       writeMessage(writer, message);
+ *     }
+ *     writer.endArray();
+ *   }
+ *
+ *   public void writeMessage(JsonWriter writer, Message message) throws IOException {
+ *     writer.beginObject();
+ *     writer.name("id").value(message.getId());
+ *     writer.name("text").value(message.getText());
+ *     if (message.getGeo() != null) {
+ *       writer.name("geo");
+ *       writeDoublesArray(writer, message.getGeo());
+ *     } else {
+ *       writer.name("geo").nullValue();
+ *     }
+ *     writer.name("user");
+ *     writeUser(writer, message.getUser());
+ *     writer.endObject();
+ *   }
+ *
+ *   public void writeUser(JsonWriter writer, User user) throws IOException {
+ *     writer.beginObject();
+ *     writer.name("name").value(user.getName());
+ *     writer.name("followers_count").value(user.getFollowersCount());
+ *     writer.endObject();
+ *   }
+ *
+ *   public void writeDoublesArray(JsonWriter writer, List doubles) throws IOException {
+ *     writer.beginArray();
+ *     for (Double value : doubles) {
+ *       writer.value(value);
+ *     }
+ *     writer.endArray();
+ *   }}
+ * + *

Each {@code JsonWriter} may be used to write a single JSON stream. + * Instances of this class are not thread safe. Calls that would result in a + * malformed JSON string will fail with an {@link IllegalStateException}. + */ +public final class Json5Writer implements Closeable, Flushable { + /** + * From RFC 7159, "All Unicode characters may be placed within the + * quotation marks except for the characters that must be escaped: + * quotation mark, reverse solidus, and the control characters + * (U+0000 through U+001F)." + *

+ * We also escape '\u2028' and '\u2029', which JavaScript interprets as + * newline characters. This prevents eval() from failing with a syntax + * error. http://code.google.com/p/google-gson/issues/detail?id=341 + */ + private static final String[] REPLACEMENT_CHARS; + private static final String[] HTML_SAFE_REPLACEMENT_CHARS; + + static { + REPLACEMENT_CHARS = new String[128]; + for (int i = 0; i <= 0x1f; i++) { + REPLACEMENT_CHARS[i] = String.format("\\u%04x", (int) i); + } + REPLACEMENT_CHARS['"'] = "\\\""; + REPLACEMENT_CHARS['\\'] = "\\\\"; + REPLACEMENT_CHARS['\t'] = "\\t"; + REPLACEMENT_CHARS['\b'] = "\\b"; + REPLACEMENT_CHARS['\n'] = "\\n"; + REPLACEMENT_CHARS['\r'] = "\\r"; + REPLACEMENT_CHARS['\f'] = "\\f"; + HTML_SAFE_REPLACEMENT_CHARS = REPLACEMENT_CHARS.clone(); + HTML_SAFE_REPLACEMENT_CHARS['<'] = "\\u003c"; + HTML_SAFE_REPLACEMENT_CHARS['>'] = "\\u003e"; + HTML_SAFE_REPLACEMENT_CHARS['&'] = "\\u0026"; + HTML_SAFE_REPLACEMENT_CHARS['='] = "\\u003d"; + HTML_SAFE_REPLACEMENT_CHARS['\''] = "\\u0027"; + } + + /** + * The output data, containing at most one top-level array or object. + */ + private final Writer out; + + private int[] stack = new int[32]; + private int stackSize = 0; + + { + push(EMPTY_DOCUMENT); + } + + /** + * A string containing a full set of spaces for a single level of + * indentation, or null for no pretty printing. + */ + private String indent = "\t"; + + /** + * The name/value separator; either ":" or ": ". + */ + private String separator = ": "; + + private boolean htmlSafe; + + private String deferredName; + + private String deferredComment; + boolean inlineWaited = false; + private boolean strict = false; + + private boolean compact = false; + + private boolean serializeNulls = true; + + // API methods + + /** + * Creates a new instance that writes a JSON5-encoded stream. + */ + public static Json5Writer json5(Path out) throws IOException { + return json5(Files.newBufferedWriter(Objects.requireNonNull(out, "Path cannot be null"))); + } + + /** + * Creates a new instance that writes a JSON5-encoded stream to {@code out}. + * For best performance, ensure {@link Writer} is buffered; wrapping in + * {@link java.io.BufferedWriter BufferedWriter} if necessary. + */ + public static Json5Writer json5(Writer out) { + return new Json5Writer(out); + } + + /** + * Creates a new instance that writes a strictly JSON-encoded stream. + * This disables NaN, (+/-)Infinity, and comments, and enables quotes around keys. + */ + public static Json5Writer json(Path out) throws IOException { + return json5(out).setStrictJson(); + } + + /** + * Creates a new instance that writes a strictly JSON-encoded stream to {@code out}. + * This disables NaN, (+/-)Infinity, and comments, and enables quotes around keys. + * For best performance, ensure {@link Writer} is buffered; wrapping in + * {@link java.io.BufferedWriter BufferedWriter} if necessary. + */ + public static Json5Writer json(Writer out) { + return json5(out).setStrictJson(); + } + + private Json5Writer(Writer out) { + if (out == null) { + throw new NullPointerException("out == null"); + } + this.out = out; + } + + /** + * Sets the indentation string to be repeated for each level of indentation + * in the encoded document. If {@code indent.isEmpty()} the encoded document + * will be compact. Otherwise the encoded document will be more + * human-readable. + * @param indent a string containing only whitespace. + */ + public void setIndent(String indent) { + if (indent.length() == 0) { + this.compact = true; + this.indent = null; + this.separator = ":"; + } else { + this.compact = false; + this.indent = indent; + this.separator = ": "; + } + } + + /** + * Configure if the output must be strict JSON, instead of strict JSON5. This flag disables NaN, (+/-)Infinity, comments, and enables quotes around keys. + */ + public Json5Writer setStrictJson() { + this.strict = true; + return this; + } + + /** + * Returns true if the output must be strict JSON, instead of strict JSON5. The default is false. + */ + public boolean isStrictJson() { + return strict; + } + + /** + * Shortcut for {@code setIndent("")} that makes the encoded document significantly more compact. + */ + public void setCompact() { + setIndent(""); + } + + /** + * Returns true if the output will be compact (entirely one line) and false if it will be human-readable with newlines and indentation. + * The default is false. + */ + public boolean isCompact() { + return indent == null; + } + + /** + * Configure this writer to emit JSON that's safe for direct inclusion in HTML + * and XML documents. This escapes the HTML characters {@code <}, {@code >}, + * {@code &} and {@code =} before writing them to the stream. Without this + * setting, your XML/HTML encoder should replace these characters with the + * corresponding escape sequences. + */ + public final void setHtmlSafe(boolean htmlSafe) { + this.htmlSafe = htmlSafe; + } + + /** + * Returns true if this writer writes JSON that's safe for inclusion in HTML + * and XML documents. + */ + public final boolean isHtmlSafe() { + return htmlSafe; + } + + /** + * Sets whether object members are serialized when their value is null. + * This has no impact on array elements. The default is true. + */ + public void setSerializeNulls(boolean serializeNulls) { + this.serializeNulls = serializeNulls; + } + + /** + * Returns true if object members are serialized when their value is null. + * This has no impact on array elements. The default is true. + */ + public boolean getSerializeNulls() { + return serializeNulls; + } + + /** + * Encodes the property name. + * @param name the name of the forthcoming value. May not be null. + * @return this writer. + */ + public Json5Writer name(String name) throws IOException { + if (name == null) { + throw new NullPointerException("name == null"); + } + if (deferredName != null) { + throw new IllegalStateException(); + } + if (stackSize == 0) { + throw new IllegalStateException("JsonWriter is closed."); + } + deferredName = String.format("\"%s\"", name); + return this; + } + + /** + * Begins encoding a new object. Each call to this method must be paired + * with a call to {@link #endObject}. + * @return this writer. + */ + public Json5Writer beginObject() throws IOException { + writeDeferredName(); + return open(EMPTY_OBJECT, '{'); + } + + /** + * Ends encoding the current object. + * @return this writer. + */ + public Json5Writer endObject() throws IOException { + return close(EMPTY_OBJECT, NONEMPTY_OBJECT, '}'); + } + + /** + * Begins encoding a new array. Each call to this method must be paired with + * a call to {@link #endArray}. + * @return this writer. + */ + public Json5Writer beginArray() throws IOException { + writeDeferredName(); + return open(EMPTY_ARRAY, '['); + } + + /** + * Ends encoding the current array. + * @return this writer. + */ + public Json5Writer endArray() throws IOException { + return close(EMPTY_ARRAY, NONEMPTY_ARRAY, ']'); + } + + /** + * Encodes {@code value}. + * @param value the literal string value, or null to encode a null literal. + * @return this writer. + */ + public Json5Writer value(String value) throws IOException { + if (value == null) { + return nullValue(); + } + writeDeferredName(); + beforeValue(); + string(value, true, true); + return this; + } + + /** + * Encodes {@code value}. + * @return this writer. + */ + public Json5Writer value(boolean value) throws IOException { + writeDeferredName(); + beforeValue(); + out.write(value ? "true" : "false"); + return this; + } + + /** + * Encodes {@code value}. + * @return this writer. + */ + public Json5Writer value(Boolean value) throws IOException { + if (value == null) { + return nullValue(); + } + writeDeferredName(); + beforeValue(); + out.write(value ? "true" : "false"); + return this; + } + + /** + * Encodes {@code value}. + * @param value a finite value. May not be {@link Double#isNaN() NaNs} or + * {@link Double#isInfinite() infinities}. + * @return this writer. + */ + public Json5Writer value(Number value) throws IOException { + if (value == null) { + return nullValue(); + } + + writeDeferredName(); + String string = value.toString(); + if (strict && (string.equals("-Infinity") || string.equals("Infinity") || string.equals("NaN"))) { + throw new IllegalArgumentException("Numeric values must be finite, but was " + value); + } + beforeValue(); + out.append(string); + return this; + } + + /** + * Encodes {@code value}. + * @param value a finite value. May not be {@link Double#isNaN() NaNs} or + * {@link Double#isInfinite() infinities}. + * @return this writer. + */ + public Json5Writer value(double value) throws IOException { + writeDeferredName(); + if (strict && (Double.isNaN(value) || Double.isInfinite(value))) { + throw new IllegalArgumentException("Numeric values must be finite, but was " + value); + } + beforeValue(); + out.append(Double.toString(value)); + return this; + } + + /** + * Encodes {@code value}. + * @return this writer. + */ + public Json5Writer value(long value) throws IOException { + writeDeferredName(); + beforeValue(); + out.write(Long.toString(value)); + return this; + } + + /** + * Encodes {@code null}. + * @return this writer. + */ + public Json5Writer nullValue() throws IOException { + if (deferredName != null) { + if (serializeNulls) { + writeDeferredName(); + } else { + deferredName = null; + return this; // skip the name and the value + } + } + beforeValue(); + out.write("null"); + return this; + } + + /** + * Writes {@code value} directly to the writer without quoting or + * escaping. + * @param value the literal string value, or null to encode a null literal. + * @return this writer. + */ + public Json5Writer jsonValue(String value) throws IOException { + if (value == null) { + return nullValue(); + } + writeDeferredName(); + beforeValue(); + out.append(value); + return this; + } + + /** + * Encodes a comment, handling newlines and HTML safety gracefully. + * Silently does nothing when strict JSON mode is enabled. + * @param comment the comment to write, or null to encode nothing. + */ + public Json5Writer comment(String comment) throws IOException { + if (compact || strict || comment == null) { + return this; + } + + if (deferredComment == null) { + deferredComment = comment; + } else { + deferredComment += "\n" + comment; + } + + // Be aggressive about writing comments if we are at the end of the document + if (stackSize == 1 && peek() == NONEMPTY_DOCUMENT) { + out.append('\n'); + writeDeferredComment(); + } + + return this; + } + + /** + * This has not been implemented yet. + */ + public Json5Writer blockComment(String comment) throws IOException { + // TODO implement me! + return comment(comment); + } + + /** + * Ensures all buffered data is written to the underlying {@link Writer} + * and flushes that writer. + */ + @Override + public void flush() throws IOException { + if (stackSize == 0) { + throw new IllegalStateException("JsonWriter is closed."); + } + out.flush(); + } + + /** + * Flushes and closes this writer and the underlying {@link Writer}. + * @throws IOException if the JSON document is incomplete. + */ + @Override + public void close() throws IOException { + out.close(); + + int size = stackSize; + if (size > 1 || size == 1 && stack[size - 1] != NONEMPTY_DOCUMENT) { + throw new IOException("Incomplete document"); + } + stackSize = 0; + } + + // Implementation methods + // Everything below here should be package-private or private + + /** + * Enters a new scope by appending any necessary whitespace and the given + * bracket. + */ + private Json5Writer open(int empty, char openBracket) throws IOException { + beforeValue(); + push(empty); + out.write(openBracket); + return this; + } + + /** + * Closes the current scope by appending any necessary whitespace and the + * given bracket. + */ + private Json5Writer close(int empty, int nonempty, char closeBracket) + throws IOException { + int context = peek(); + if (context != nonempty && context != empty) { + throw new IllegalStateException("Nesting problem."); + } + if (deferredName != null) { + throw new IllegalStateException("Dangling name: " + deferredName); + } + + stackSize--; + if (context == nonempty) { + commentAndNewline(); + } + out.write(closeBracket); + return this; + } + + private void push(int newTop) { + if (stackSize == stack.length) { + stack = Arrays.copyOf(stack, stackSize * 2); + } + stack[stackSize++] = newTop; + } + + /** + * Returns the value on the top of the stack. + */ + private int peek() { + if (stackSize == 0) { + throw new IllegalStateException("JsonWriter is closed."); + } + return stack[stackSize - 1]; + } + + /** + * Replace the value on the top of the stack with the given value. + */ + private void replaceTop(int topOfStack) { + stack[stackSize - 1] = topOfStack; + } + + private void writeDeferredName() throws IOException { + if (deferredName != null) { + beforeName(); + string(deferredName, strict, true); + deferredName = null; + } + } + + private void writeDeferredComment() throws IOException { + if (deferredComment == null) { + return; + } + + for (String s : deferredComment.split("\n")) { + for (int i = 1, size = stackSize; i < size; i++) { + out.write(indent); + } + out.write("// "); + string(s, false, false); + out.write("\n"); + } + + deferredComment = null; + } + + private void string(String value, boolean quotes, boolean escapeQuotes) throws IOException { + String[] replacements = htmlSafe ? HTML_SAFE_REPLACEMENT_CHARS : REPLACEMENT_CHARS; + if (quotes) { + out.write('\"'); + } + + if (!escapeQuotes) { + replacements['\"'] = null; + } + + int last = 0; + int length = value.length(); + + for (int i = 0; i < length; i++) { + char c = value.charAt(i); + String replacement; + if (c < 128) { + replacement = replacements[c]; + if (replacement == null) { + continue; + } + } else if (c == '\u2028') { + replacement = "\\u2028"; + } else if (c == '\u2029') { + replacement = "\\u2029"; + } else { + continue; + } + if (last < i) { + out.write(value, last, i - last); + } + out.write(replacement); + last = i + 1; + } + if (last < length) { + out.write(value, last, length - last); + } + + if (quotes) { + out.write('\"'); + } + } + + private void commentAndNewline() throws IOException { + if (indent == null) { + return; + } + + out.write('\n'); + writeDeferredComment(); + + for (int i = 1, size = stackSize; i < size; i++) { + out.write(indent); + } + } + + /** + * Inserts any necessary separators and whitespace before a name. Also + * adjusts the stack to expect the name's value. + */ + private void beforeName() throws IOException { + int context = peek(); + if (context == NONEMPTY_OBJECT) { // first in object + out.write(','); + } else if (context != EMPTY_OBJECT) { // not in an object! + throw new IllegalStateException("Nesting problem."); + } + commentAndNewline(); + replaceTop(DANGLING_NAME); + } + + /** + * Inserts any necessary comments, separators, and whitespace before a literal value, + * inline array, or inline object. Also adjusts the stack to expect either a + * closing bracket or another element. + */ + @SuppressWarnings("fallthrough") + private void beforeValue() throws IOException { + switch (peek()) { + case NONEMPTY_DOCUMENT: + // TODO: This isn't a JSON5 feature, right? + throw new IllegalStateException( + "JSON must have only one top-level value."); + // fall-through + case EMPTY_DOCUMENT: // first in document + writeDeferredComment(); + replaceTop(NONEMPTY_DOCUMENT); + break; + + case EMPTY_ARRAY: // first in array + replaceTop(NONEMPTY_ARRAY); + commentAndNewline(); + break; + + case NONEMPTY_ARRAY: // another in array + out.append(','); + commentAndNewline(); + break; + + case DANGLING_NAME: // value for name + out.append(separator); + replaceTop(NONEMPTY_OBJECT); + break; + + default: + throw new IllegalStateException("Nesting problem."); + } + } +} diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/json5/JsonScope.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/json5/JsonScope.java new file mode 100644 index 0000000..d06e139 --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/json5/JsonScope.java @@ -0,0 +1,65 @@ +package com.github.passerr.idea.plugins.spring.web.json5; + +import java.io.StringWriter; + +/** + * com.google.gson.stream.JsonScope + * @author xiehai + * @date 2021/07/08 14:43 + * @Copyright(c) tellyes tech. inc. co.,ltd + */ +interface JsonScope { + /** + * An array with no elements requires no separators or newlines before + * it is closed. + */ + int EMPTY_ARRAY = 1; + + /** + * A array with at least one value requires a comma and newline before + * the next element. + */ + int NONEMPTY_ARRAY = 2; + + /** + * An object with no name/value pairs requires no separators or newlines + * before it is closed. + */ + int EMPTY_OBJECT = 3; + + /** + * An object whose most recent element is a key. The next element must + * be a value. + */ + int DANGLING_NAME = 4; + + /** + * An object with at least one name/value pair requires a comma and + * newline before the next element. + */ + int NONEMPTY_OBJECT = 5; + + /** + * No object or array has been started. + */ + int EMPTY_DOCUMENT = 6; + + /** + * A document with at an array or object. + */ + int NONEMPTY_DOCUMENT = 7; + + public static void main(String[] args) throws Exception { + StringWriter stringWriter = new StringWriter(); + Json5Writer writer = Json5Writer.json5(stringWriter); + writer.setIndent(" "); + writer.comment("这是注释") + .beginObject() + .name("key") + .value(1L) + .endObject(); + System.out.println(stringWriter); + +// Json5Writer json5Writer = Json51); + } +} \ No newline at end of file diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/json5/package-info.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/json5/package-info.java new file mode 100644 index 0000000..c5bcf3c --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/json5/package-info.java @@ -0,0 +1,7 @@ +package com.github.passerr.idea.plugins.spring.web.json5; +/** + * 自定义json5序列化 + * @author xiehai + * @date 2021/07/08 11:50 + * @Copyright(c) tellyes tech. inc. co.,ltd + */ \ 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 1c5cab7..ae8bb21 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -46,9 +46,6 @@ - com.intellij.modules.lang - com.intellij.modules.java - -- Gitee From 4de109c2f72ed04aa08f744b7c94367389dfad82 Mon Sep 17 00:00:00 2001 From: PasseRR Date: Tue, 20 Jul 2021 08:51:15 +0800 Subject: [PATCH 17/41] =?UTF-8?q?=E8=BF=87=E6=97=B6api=E6=9B=BF=E6=8D=A2?= =?UTF-8?q?=E3=80=81=E5=8D=87=E7=BA=A7idea=20ui?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 7 +++-- .../plugins/spring/web/ApiDocConfigViews.java | 3 +- .../idea/plugins/spring/web/VelocityUtil.java | 29 +++++++++++++++++++ .../web/highlight/TemplateHighlighter.java | 2 +- src/main/resources/META-INF/plugin.xml | 2 +- 5 files changed, 38 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/github/passerr/idea/plugins/spring/web/VelocityUtil.java diff --git a/build.gradle b/build.gradle index d111757..0fe7a4c 100644 --- a/build.gradle +++ b/build.gradle @@ -25,14 +25,17 @@ repositories { } intellij { - //IntelliJ IDEA 2016.3 dependency; for a full list of IntelliJ IDEA releases please see https://www.jetbrains.com/intellij-repository/releases - version = '162.1628.40' + updateSinceUntilBuild = false + version = '2020.1' + plugins = ['java'] } tasks.withType(JavaCompile) { options.encoding = "UTF-8" } +buildSearchableOptions.enabled = false + dependencies { compileOnly group: 'org.projectlombok', name: 'lombok', version: '1.18.2' annotationProcessor group: 'org.projectlombok', name: 'lombok', version: '1.18.2' diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java index 49237d6..b02a704 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java @@ -24,6 +24,7 @@ import com.intellij.openapi.fileTypes.FileTypes; import com.intellij.openapi.fileTypes.PlainSyntaxHighlighter; import com.intellij.openapi.fileTypes.SyntaxHighlighter; import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory; +import com.intellij.openapi.ui.ComboBox; import com.intellij.openapi.util.Pair; import com.intellij.ui.BrowserHyperlinkListener; import com.intellij.ui.PanelWithButtons; @@ -536,7 +537,7 @@ public abstract class ApiDocConfigViews { gb.gridwidth = 1; gb.weightx = 0; p.add(aliasLabel, gb); - JComboBox aliasCombobox = new JComboBox<>( + JComboBox aliasCombobox = new ComboBox<>( Arrays.stream(AliasType.values()) .map(AliasType::getType) .toArray(String[]::new) diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/VelocityUtil.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/VelocityUtil.java new file mode 100644 index 0000000..2aac76e --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/VelocityUtil.java @@ -0,0 +1,29 @@ +package com.github.passerr.idea.plugins.spring.web; + +import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.Velocity; + +import java.io.StringWriter; +import java.util.Map; + +/** + * velocity工具类 + * @author xiehai + * @date 2021/07/09 11:39 + * @Copyright(c) tellyes tech. inc. co.,ltd + */ +public interface VelocityUtil { + /** + * 模版替换 + * @param template 模版 + * @param map 变量 + * @return 替换后文本 + */ + static String format(String template, Map map) { + VelocityContext context = new VelocityContext(map); + StringWriter writer = new StringWriter(); + Velocity.evaluate(context, writer, "eval", template); + + return writer.toString(); + } +} diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/highlight/TemplateHighlighter.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/highlight/TemplateHighlighter.java index 85f3de8..7e32fa6 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/highlight/TemplateHighlighter.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/highlight/TemplateHighlighter.java @@ -37,6 +37,6 @@ public class TemplateHighlighter extends SyntaxHighlighterBase { return pack(TemplateColors.TEMPLATE_VARIABLE_ATTRIBUTES); } - return EMPTY; + return TextAttributesKey.EMPTY_ARRAY; } } \ 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 ae8bb21..8b53e85 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -1,7 +1,7 @@ com.github.passerr.idea.plugins - PasseRR Idea Plugins + PasseRR Idea Tools PasseRR -- Gitee From b80b6a90558881dfda798790be92a6ce9c516aa2 Mon Sep 17 00:00:00 2001 From: PasseRR Date: Tue, 20 Jul 2021 10:34:58 +0800 Subject: [PATCH 18/41] =?UTF-8?q?icon=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 +- src/main/resources/META-INF/plugin.xml | 2 +- src/main/resources/icon/icon.svg | 36 +++++++++++++++++++++++++ src/main/resources/icon/tool.jpg | Bin 590 -> 0 bytes 4 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 src/main/resources/icon/icon.svg delete mode 100644 src/main/resources/icon/tool.jpg diff --git a/build.gradle b/build.gradle index 0fe7a4c..f50f711 100644 --- a/build.gradle +++ b/build.gradle @@ -34,7 +34,7 @@ tasks.withType(JavaCompile) { options.encoding = "UTF-8" } -buildSearchableOptions.enabled = false +//buildSearchableOptions.enabled = false dependencies { compileOnly group: 'org.projectlombok', name: 'lombok', version: '1.18.2' diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 8b53e85..8ef9453 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -80,7 +80,7 @@ - diff --git a/src/main/resources/icon/icon.svg b/src/main/resources/icon/icon.svg new file mode 100644 index 0000000..46e0f15 --- /dev/null +++ b/src/main/resources/icon/icon.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + background + + + + Layer 1 + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/icon/tool.jpg b/src/main/resources/icon/tool.jpg deleted file mode 100644 index 4ece0a84850db4c6fad0dd10ec6968e435367b81..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 590 zcmex=``2_j6xdp@o1cgOJMMZh|#U;coyG6VInuyV4pa*FVB^NNrR z{vTivs?{1S%B=83I!UrWsjSnSc@sf((o-j4aFuIYtI1WBG4KF2G6^yZGT1XLeOf8F z70cD`^cU>Cc~9RvF}MYMQzptEdMtR~#pSRbOS6O_$S{ydJnlq!C zZGL5(HhTg4iL+YSiG@?53b$@qQZ_|bMI-ryZDz=&S6opOjHZgrSGX=S?T<*O;JhO1 wpT}2S>I(fUo9T6UkC(Ed_p5m`zb~ Date: Tue, 20 Jul 2021 10:49:09 +0800 Subject: [PATCH 19/41] =?UTF-8?q?icon=E9=A2=9C=E8=89=B2=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/icon/icon.svg | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/resources/icon/icon.svg b/src/main/resources/icon/icon.svg index 46e0f15..153d383 100644 --- a/src/main/resources/icon/icon.svg +++ b/src/main/resources/icon/icon.svg @@ -14,20 +14,20 @@ Layer 1 - - + + - + - - - + + + - - - + + + -- Gitee From f7723d25e994baaa60e3ff6a793d45f909856544 Mon Sep 17 00:00:00 2001 From: PasseRR Date: Tue, 20 Jul 2021 11:42:39 +0800 Subject: [PATCH 20/41] =?UTF-8?q?velocity=E8=AF=AD=E6=B3=95=E6=94=AF?= =?UTF-8?q?=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 - .../plugins/spring/web/ApiDocConfigViews.java | 15 ++++++++++ .../idea/plugins/spring/web/VelocityUtil.java | 2 +- .../plugins/spring/web/json5/JsonScope.java | 16 ----------- .../passerr/idea/plugins/json5/Json5Spec.java | 28 +++++++++++++++++++ 5 files changed, 44 insertions(+), 18 deletions(-) create mode 100644 src/test/java/com/github/passerr/idea/plugins/json5/Json5Spec.java diff --git a/build.gradle b/build.gradle index f50f711..f434679 100644 --- a/build.gradle +++ b/build.gradle @@ -40,5 +40,4 @@ dependencies { compileOnly group: 'org.projectlombok', name: 'lombok', version: '1.18.2' annotationProcessor group: 'org.projectlombok', name: 'lombok', version: '1.18.2' implementation group: 'com.fifesoft', name: 'rsyntaxtextarea', version: '2.6.1' - implementation group: 'blue.endless', name: 'jankson', version: '1.2.1' } \ No newline at end of file diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java index b02a704..1ddabc4 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java @@ -20,12 +20,17 @@ import com.intellij.openapi.editor.ex.EditorEx; import com.intellij.openapi.editor.ex.util.LayerDescriptor; import com.intellij.openapi.editor.ex.util.LayeredLexerEditorHighlighter; import com.intellij.openapi.editor.highlighter.EditorHighlighter; +import com.intellij.openapi.editor.highlighter.EditorHighlighterFactory; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.fileTypes.FileTypeManager; import com.intellij.openapi.fileTypes.FileTypes; import com.intellij.openapi.fileTypes.PlainSyntaxHighlighter; import com.intellij.openapi.fileTypes.SyntaxHighlighter; import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory; +import com.intellij.openapi.project.ex.ProjectManagerEx; import com.intellij.openapi.ui.ComboBox; import com.intellij.openapi.util.Pair; +import com.intellij.testFramework.LightVirtualFile; import com.intellij.ui.BrowserHyperlinkListener; import com.intellij.ui.PanelWithButtons; import com.intellij.ui.ScrollPaneFactory; @@ -627,6 +632,16 @@ public abstract class ApiDocConfigViews { } private static EditorHighlighter createVelocityHighlight() { + FileType ft = FileTypeManager.getInstance().getFileTypeByExtension("ft"); + if (ft != FileTypes.UNKNOWN) { + return + EditorHighlighterFactory.getInstance() + .createEditorHighlighter( + ProjectManagerEx.getInstance().getDefaultProject(), + new LightVirtualFile("aaa.psr.spring.web.ft") + ); + } + SyntaxHighlighter ohl = Optional.ofNullable(SyntaxHighlighterFactory.getSyntaxHighlighter(FileTypes.PLAIN_TEXT, null, null)) .orElseGet(PlainSyntaxHighlighter::new); diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/VelocityUtil.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/VelocityUtil.java index 2aac76e..754cb9b 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/VelocityUtil.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/VelocityUtil.java @@ -19,7 +19,7 @@ public interface VelocityUtil { * @param map 变量 * @return 替换后文本 */ - static String format(String template, Map map) { + static String format(String template, Map map) { VelocityContext context = new VelocityContext(map); StringWriter writer = new StringWriter(); Velocity.evaluate(context, writer, "eval", template); diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/json5/JsonScope.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/json5/JsonScope.java index d06e139..0b01217 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/json5/JsonScope.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/json5/JsonScope.java @@ -1,7 +1,5 @@ package com.github.passerr.idea.plugins.spring.web.json5; -import java.io.StringWriter; - /** * com.google.gson.stream.JsonScope * @author xiehai @@ -48,18 +46,4 @@ interface JsonScope { * A document with at an array or object. */ int NONEMPTY_DOCUMENT = 7; - - public static void main(String[] args) throws Exception { - StringWriter stringWriter = new StringWriter(); - Json5Writer writer = Json5Writer.json5(stringWriter); - writer.setIndent(" "); - writer.comment("这是注释") - .beginObject() - .name("key") - .value(1L) - .endObject(); - System.out.println(stringWriter); - -// Json5Writer json5Writer = Json51); - } } \ No newline at end of file diff --git a/src/test/java/com/github/passerr/idea/plugins/json5/Json5Spec.java b/src/test/java/com/github/passerr/idea/plugins/json5/Json5Spec.java new file mode 100644 index 0000000..e168a65 --- /dev/null +++ b/src/test/java/com/github/passerr/idea/plugins/json5/Json5Spec.java @@ -0,0 +1,28 @@ +package com.github.passerr.idea.plugins.json5; + +import com.github.passerr.idea.plugins.spring.web.json5.Json5Writer; +import org.junit.Test; + +import java.io.IOException; +import java.io.StringWriter; + +/** + * {@link Json5Writer} + * @author xiehai + * @date 2021/07/20 11:37 + * @Copyright(c) tellyes tech. inc. co.,ltd + */ +public class Json5Spec { + @Test + public void json5() throws IOException { + StringWriter stringWriter = new StringWriter(); + Json5Writer writer = Json5Writer.json5(stringWriter); + writer.setIndent(" "); + writer.comment("这是注释") + .beginObject() + .name("key") + .value(1L) + .endObject(); + System.out.println(stringWriter); + } +} -- Gitee From 3e6845a49920e95d04f6bd6e75f3c0072dd9b465 Mon Sep 17 00:00:00 2001 From: PasseRR Date: Tue, 20 Jul 2021 12:09:40 +0800 Subject: [PATCH 21/41] =?UTF-8?q?=E5=BA=8F=E5=88=97=E5=8C=96ui=E9=97=AE?= =?UTF-8?q?=E9=A2=98=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugins/spring/web/ApiDocConfigViews.java | 24 +++++++++++++------ .../spring/web/CopyMethodApiDocAction.java | 3 +-- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java index 1ddabc4..a64d0a5 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java @@ -53,6 +53,7 @@ import java.awt.event.ItemEvent; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.function.BiFunction; import java.util.function.Function; @@ -130,6 +131,7 @@ public abstract class ApiDocConfigViews { // 描述模块 JEditorPane desc = new JEditorPane(UIUtil.HTML_MIME, ""); desc.setEditable(false); + desc.setEditorKit(UIUtil.getHTMLEditorKit()); desc.addHyperlinkListener(new BrowserHyperlinkListener()); desc.setText(ResourceUtil.readAsString("/api-doc-desc.html")); desc.setCaretPosition(0); @@ -510,7 +512,8 @@ public abstract class ApiDocConfigViews { }; JBTable table = new IdeaJbTable(model); // 弹出层构建器 - BiFunction function = (s, r) -> { + Function, JComponent> function = dialog -> { + ApiDocObjectSerialPo s = dialog.getValue(); JPanel p = new JPanel(new GridBagLayout()); GridBagConstraints gb = new GridBagConstraints(0, 0, 1, 1, 0, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, @@ -526,7 +529,7 @@ public abstract class ApiDocConfigViews { @Override protected void textChanged(javax.swing.event.DocumentEvent e) { s.setType(textField.getText()); - r.run(); + dialog.onChange(); } }); Dimension oldPreferredSize = textField.getPreferredSize(); @@ -551,8 +554,15 @@ public abstract class ApiDocConfigViews { if (e.getStateChange() == ItemEvent.SELECTED) { s.setAlias((String) e.getItem()); } + dialog.onChange(); }); - Optional.ofNullable(s.getAlias()).ifPresent(aliasCombobox::setSelectedItem); + + if (Objects.isNull(s.getAlias())) { + // 新增的时候默认选中第一个 + s.setAlias(aliasCombobox.getItemAt(0)); + } + aliasCombobox.setSelectedItem(s.getAlias()); + gb.gridx = 1; gb.fill = GridBagConstraints.NONE; gb.gridwidth = GridBagConstraints.REMAINDER; @@ -573,7 +583,7 @@ public abstract class ApiDocConfigViews { @Override protected void textChanged(javax.swing.event.DocumentEvent e) { s.setValue(valueField.getText()); - r.run(); + dialog.onChange(); } }); valueField.setPreferredSize(new Dimension(300, oldPreferredSize.height)); @@ -581,7 +591,7 @@ public abstract class ApiDocConfigViews { gb.gridwidth = GridBagConstraints.REMAINDER; gb.weightx = 1; p.add(valueField, gb); - r.run(); + dialog.onChange(); return p; }; @@ -594,7 +604,7 @@ public abstract class ApiDocConfigViews { .value(new ApiDocObjectSerialPo()) .okAction(setting.getObjects()::add) .changePredicate(ApiDocObjectSerialPo::isOk) - .componentFunction(t -> function.apply(t.getValue(), t::onChange)) + .componentFunction(function) .doInit() .showAndGet() ) @@ -605,7 +615,7 @@ public abstract class ApiDocConfigViews { .value(model.getRow(table.getSelectedRow())) .okAction(t -> setting.getObjects().set(table.getSelectedRow(), t)) .changePredicate(ApiDocObjectSerialPo::isOk) - .componentFunction(t -> function.apply(t.getValue(), t::onChange)) + .componentFunction(function) .doInit() .showAndGet() ) diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.java index 886f4db..da38167 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.java @@ -1,6 +1,5 @@ package com.github.passerr.idea.plugins.spring.web; -import blue.endless.jankson.JsonElement; import com.github.passerr.idea.plugins.spring.web.po.ApiDocSettingPo; import com.intellij.codeInsight.AnnotationUtil; import com.intellij.openapi.actionSystem.AnActionEvent; @@ -226,7 +225,7 @@ public class CopyMethodApiDocAction extends BaseWebCopyAction { * @param state 配置状态 * @return */ - private static JsonElement body(PsiMethod method, Map comments, ApiDocSettingPo state) { + private static Object body(PsiMethod method, Map comments, ApiDocSettingPo state) { PsiParameter body = Arrays.stream(method.getParameterList().getParameters()) // 排除body参数类型为排除类型 即无法序列化的类型 如Object、Map等 -- Gitee From 781d493bad783e573af3d57d394a8c8fe2be3f26 Mon Sep 17 00:00:00 2001 From: PasseRR Date: Tue, 20 Jul 2021 14:49:09 +0800 Subject: [PATCH 22/41] =?UTF-8?q?=E5=AE=8C=E5=96=84api=E6=96=87=E6=A1=A3?= =?UTF-8?q?=E5=A4=8D=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../spring/web/CopyMethodApiDocAction.java | 142 ++++++++---------- .../idea/plugins/spring/web/PsiUtil.java | 114 ++++++++++++++ .../idea/plugins/spring/web/ResourceUtil.java | 4 +- .../idea/plugins/spring/web/VelocityUtil.java | 4 +- .../plugins/spring/web/json5/Json5Util.java | 44 ++++++ .../plugins/spring/web/json5/Json5Writer.java | 2 +- .../spring/web/po/ApiDocSettingPo.java | 12 +- .../{ => spring/web}/json5/Json5Spec.java | 3 +- 8 files changed, 234 insertions(+), 91 deletions(-) create mode 100644 src/main/java/com/github/passerr/idea/plugins/spring/web/PsiUtil.java create mode 100644 src/main/java/com/github/passerr/idea/plugins/spring/web/json5/Json5Util.java rename src/test/java/com/github/passerr/idea/plugins/{ => spring/web}/json5/Json5Spec.java (84%) diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.java index da38167..f0d0229 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.java @@ -1,22 +1,23 @@ package com.github.passerr.idea.plugins.spring.web; +import com.github.passerr.idea.plugins.spring.web.json5.Json5Util; import com.github.passerr.idea.plugins.spring.web.po.ApiDocSettingPo; import com.intellij.codeInsight.AnnotationUtil; import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.ide.CopyPasteManager; import com.intellij.psi.PsiAnnotation; import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiField; import com.intellij.psi.PsiMethod; -import com.intellij.psi.PsiModifier; -import com.intellij.psi.PsiModifierList; import com.intellij.psi.PsiParameter; import com.intellij.psi.javadoc.PsiDocComment; import com.intellij.psi.javadoc.PsiDocToken; import com.intellij.psi.util.PsiTypesUtil; +import com.intellij.util.ui.TextTransferable; import lombok.AllArgsConstructor; +import org.jetbrains.annotations.NotNull; -import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -35,23 +36,48 @@ import static com.github.passerr.idea.plugins.spring.web.AliasType.UNKNOWN_ALIAS */ public class CopyMethodApiDocAction extends BaseWebCopyAction { @Override - public void actionPerformed(AnActionEvent e) { + public void actionPerformed(@NotNull AnActionEvent e) { PsiMethod method = BaseWebCopyAction.method(e); PsiAnnotation classAnnotation = BaseWebCopyAction.classAnnotation(method); PsiAnnotation methodAnnotation = BaseWebCopyAction.methodAnnotation(method); + // 方法参数注释缓存 + Map comments = comments(method); + + Map map = new HashMap<>(4); ApiDocSettingPo state = ApiDocStateComponent.getInstance().getState(); + if (Objects.isNull(state)) { + return; + } + String url = BaseWebCopyAction.url(classAnnotation, methodAnnotation); + map.put("url", url); + String httpMethod = getMethod(classAnnotation, methodAnnotation); - // 方法参数注释缓存 - Map comments = comments(method); + map.put("method", httpMethod); + // 路径参数列表 List pathVariables = pathVariables(method, comments, state); + map.put("hasPathVariables", !pathVariables.isEmpty()); + map.put("pathVariables", pathVariables); + // 查询参数列表 - List queryVariables = queryParams(method, comments, state); + List queryParams = queryParams(method, comments, state); + map.put("hasQueryParams", !queryParams.isEmpty()); + map.put("queryParams", queryParams); // body - body(method, comments, state); - // 请求示例 + String body = body(method, comments, state); + // body示例 + map.put("hasBody", Objects.nonNull(body)); + map.put("body", body); + // 应答示例 + String response = response(method, state); + map.put("hasResponse", Objects.nonNull(response)); + map.put("response", response); + + // 模版替换 发送api文档至剪贴板 + CopyPasteManager.getInstance() + .setContents(new TextTransferable(VelocityUtil.format(state.getTemplate(), map))); } /** @@ -100,7 +126,7 @@ public class CopyMethodApiDocAction extends BaseWebCopyAction { Arrays.stream(it.getDataElements()) .filter(e -> e instanceof PsiDocToken) .map(PsiDocToken.class::cast) - .filter(CopyMethodApiDocAction::isDocCommentData) + .filter(PsiUtil::isDocCommentData) .map(e -> e.getText().trim()) .collect(Collectors.joining("")) ) @@ -120,7 +146,7 @@ public class CopyMethodApiDocAction extends BaseWebCopyAction { private static List pathVariables(PsiMethod method, Map comments, ApiDocSettingPo state) { return Arrays.stream(method.getParameterList().getParameters()) - .filter(CopyMethodApiDocAction::isValidParamType) + .filter(PsiUtil::isValidParamType) .map(it -> { PsiAnnotation annotation = AnnotationUtil.findAnnotation( it, WebCopyConstants.PATH_VARIABLE_ANNOTATION); @@ -157,7 +183,7 @@ public class CopyMethodApiDocAction extends BaseWebCopyAction { Arrays.stream(method.getParameterList().getParameters()) .filter(it -> // 排除接口类型参数、忽略类型、带忽略类型注解的参数 - isValidParamType( + PsiUtil.isValidParamType( it, state.getQueryParamIgnoreTypes(), state.getQueryParamIgnoreAnnotations() @@ -179,7 +205,7 @@ public class CopyMethodApiDocAction extends BaseWebCopyAction { return Arrays.stream(clazz.getAllFields()) - .filter(CopyMethodApiDocAction::isValidFiled) + .filter(PsiUtil::isValidFiled) // 查询参数只遍历一层 且忽略掉这层的未知类型 .filter(f -> !UNKNOWN_ALIAS.equals(state.alias(f.getType().getCanonicalText()))) .map(f -> @@ -193,7 +219,7 @@ public class CopyMethodApiDocAction extends BaseWebCopyAction { Arrays.stream(els) .filter(e -> e instanceof PsiDocToken) .map(PsiDocToken.class::cast) - .filter(CopyMethodApiDocAction::isDocCommentData) + .filter(PsiUtil::isDocCommentData) .map(e -> e.getText().trim()) .collect(Collectors.joining("")) ) @@ -223,84 +249,44 @@ public class CopyMethodApiDocAction extends BaseWebCopyAction { * @param method {@link PsiMethod} * @param comments 方法注释 * @param state 配置状态 - * @return + * @return json5 */ - private static Object body(PsiMethod method, Map comments, ApiDocSettingPo state) { - PsiParameter body = + private static String body(PsiMethod method, Map comments, ApiDocSettingPo state) { + PsiParameter parameter = Arrays.stream(method.getParameterList().getParameters()) // 排除body参数类型为排除类型 即无法序列化的类型 如Object、Map等 - .filter(it -> isValidParamType(it, state.getBodyIgnoreTypes())) + .filter(it -> PsiUtil.isValidParamType(it, state.getBodyIgnoreTypes())) // 方法参数中有@RequestBody注解的参数且只会取第一个 .filter(it -> AnnotationUtil.findAnnotation(it, WebCopyConstants.BODY_ANNOTATION) != null) .findFirst() .orElse(null); - if (Objects.isNull(body)) { + if (Objects.isNull(parameter)) { return null; } - return null; - } - - /** - * 是否是合法的参数类型 - * 非接口类型且满足排除类型 - * @param parameter {@link PsiParameter} - * @return true/false - */ - private static boolean isValidParamType(PsiParameter parameter) { - return isValidParamType(parameter, new ArrayList<>()); - } - - /** - * 是否是合法参数类型 - * @param parameter {@link PsiParameter} - * @param excludeTypes 排除类型 - * @return true/false - */ - private static boolean isValidParamType(PsiParameter parameter, List excludeTypes) { - return isValidParamType(parameter, excludeTypes, new ArrayList<>()); - } - - /** - * 是否是合法的参数类型 - * @param parameter {@link PsiParameter} - * @param excludeTypes 排除类型 - * @param excludeAnnotations 排除注解 - * @return true/false - */ - private static boolean isValidParamType(PsiParameter parameter, List excludeTypes, - List excludeAnnotations) { - PsiClass clazz = PsiTypesUtil.getPsiClass(parameter.getType()); - return - Objects.nonNull(clazz) - && !clazz.isInterface() - && !excludeTypes.contains(clazz.getQualifiedName()) - && AnnotationUtil.findAnnotations(parameter, excludeAnnotations).length == 0; - } - - /** - * 是否是合法字段 - * @param field {@link PsiField} - * @return 非static、transient字段 - */ - private static boolean isValidFiled(PsiField field) { - PsiModifierList modifierList = field.getModifierList(); - if (Objects.isNull(modifierList)) { - return true; - } - - return !modifierList.hasExplicitModifier(PsiModifier.STATIC) - && !modifierList.hasExplicitModifier(PsiModifier.TRANSIENT); + Json5Util.toJson5( + parameter.getType(), + comments.get(parameter.getName()), + Collections.unmodifiableList(state.getBodyIgnoreTypes()), + Collections.unmodifiableList(state.getObjects()) + ); } /** - * 是否是注释文本部分 - * @param token {@link PsiDocToken} - * @return true/false + * 获得方法应答参数 + * @param method {@link PsiMethod} + * @param state 配置状态 + * @return json5 */ - private static boolean isDocCommentData(PsiDocToken token) { - return "DOC_COMMENT_DATA".equals(token.getTokenType().toString()); + private static String response(PsiMethod method, ApiDocSettingPo state) { + return + Json5Util.toJson5( + method.getReturnType(), + PsiUtil.returnComment(method), + Collections.unmodifiableList(state.getBodyIgnoreTypes()), + Collections.unmodifiableList(state.getObjects()) + ); } /** diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/PsiUtil.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/PsiUtil.java new file mode 100644 index 0000000..b554732 --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/PsiUtil.java @@ -0,0 +1,114 @@ +package com.github.passerr.idea.plugins.spring.web; + +import com.intellij.codeInsight.AnnotationUtil; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiField; +import com.intellij.psi.PsiMethod; +import com.intellij.psi.PsiModifier; +import com.intellij.psi.PsiModifierList; +import com.intellij.psi.PsiParameter; +import com.intellij.psi.javadoc.PsiDocComment; +import com.intellij.psi.javadoc.PsiDocToken; +import com.intellij.psi.util.PsiTypesUtil; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * psi类型工具 + * @author xiehai + * @date 2021/07/20 14:27 + * @Copyright(c) tellyes tech. inc. co.,ltd + */ +public interface PsiUtil { + /** + * 是否是合法的参数类型 + * 非接口类型且满足排除类型 + * @param parameter {@link PsiParameter} + * @return true/false + */ + static boolean isValidParamType(PsiParameter parameter) { + return isValidParamType(parameter, new ArrayList<>()); + } + + /** + * 是否是合法参数类型 + * @param parameter {@link PsiParameter} + * @param excludeTypes 排除类型 + * @return true/false + */ + static boolean isValidParamType(PsiParameter parameter, List excludeTypes) { + return isValidParamType(parameter, excludeTypes, new ArrayList<>()); + } + + /** + * 是否是合法的参数类型 + * @param parameter {@link PsiParameter} + * @param excludeTypes 排除类型 + * @param excludeAnnotations 排除注解 + * @return true/false + */ + static boolean isValidParamType(PsiParameter parameter, List excludeTypes, + List excludeAnnotations) { + PsiClass clazz = PsiTypesUtil.getPsiClass(parameter.getType()); + + return + Objects.nonNull(clazz) + && !clazz.isInterface() + && !excludeTypes.contains(clazz.getQualifiedName()) + && AnnotationUtil.findAnnotations(parameter, excludeAnnotations).length == 0; + } + + /** + * 是否是合法字段 + * @param field {@link PsiField} + * @return 非static、transient字段 + */ + static boolean isValidFiled(PsiField field) { + PsiModifierList modifierList = field.getModifierList(); + if (Objects.isNull(modifierList)) { + return true; + } + + return !modifierList.hasExplicitModifier(PsiModifier.STATIC) + && !modifierList.hasExplicitModifier(PsiModifier.TRANSIENT); + } + + /** + * 是否是注释文本部分 + * @param token {@link PsiDocToken} + * @return true/false + */ + static boolean isDocCommentData(PsiDocToken token) { + return "DOC_COMMENT_DATA".equals(token.getTokenType().toString()); + } + + /** + * 返回类型注释 + * @param method {@link PsiMethod} + * @return 返回注释 + */ + static String returnComment(PsiMethod method) { + PsiDocComment docComment = method.getDocComment(); + if (Objects.isNull(docComment)) { + return null; + } + + return + Arrays.stream(docComment.getTags()) + .filter(it -> "return".equals(it.getName())) + .findFirst() + .map(it -> + Arrays.stream(it.getDataElements()) + .filter(e -> e instanceof PsiDocToken) + .map(PsiDocToken.class::cast) + .filter(PsiUtil::isDocCommentData) + .map(e -> e.getText().trim()) + .collect(Collectors.joining("")) + ) + .orElse(null); + } +} diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/ResourceUtil.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/ResourceUtil.java index 790ddcf..1df1db2 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/ResourceUtil.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/ResourceUtil.java @@ -2,7 +2,6 @@ package com.github.passerr.idea.plugins.spring.web; import com.google.common.io.CharStreams; -import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; @@ -21,8 +20,9 @@ public interface ResourceUtil { */ static String readAsString(String path) { try (InputStream resourceAsStream = ResourceUtil.class.getResourceAsStream(path)) { + assert resourceAsStream != null; return CharStreams.toString(new InputStreamReader(resourceAsStream, StandardCharsets.UTF_8)); - } catch (IOException ignore) { + } catch (Exception ignore) { return ""; } } diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/VelocityUtil.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/VelocityUtil.java index 754cb9b..ba11fdd 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/VelocityUtil.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/VelocityUtil.java @@ -19,10 +19,10 @@ public interface VelocityUtil { * @param map 变量 * @return 替换后文本 */ - static String format(String template, Map map) { + static String format(StringBuilder template, Map map) { VelocityContext context = new VelocityContext(map); StringWriter writer = new StringWriter(); - Velocity.evaluate(context, writer, "eval", template); + Velocity.evaluate(context, writer, "eval", template.toString()); return writer.toString(); } diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/json5/Json5Util.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/json5/Json5Util.java new file mode 100644 index 0000000..bd78eff --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/json5/Json5Util.java @@ -0,0 +1,44 @@ +package com.github.passerr.idea.plugins.spring.web.json5; + +import com.github.passerr.idea.plugins.spring.web.po.ApiDocObjectSerialPo; +import com.intellij.psi.PsiType; + +import java.io.StringWriter; +import java.util.List; +import java.util.Objects; + +/** + * json5工具类 + * @author xiehai + * @date 2021/07/20 14:13 + * @Copyright(c) tellyes tech. inc. co.,ltd + */ +public interface Json5Util { + /** + * 将任意类型转为json5 + * @param psiType {@link PsiType} + * @param rootComment 根注释 + * @param ignores 忽略类型 + * @param objects 序列化支持类型 + * @return json5 + */ + static String toJson5(PsiType psiType, String rootComment, List ignores, + List objects) { + StringWriter stringWriter = new StringWriter(); + Json5Writer writer = Json5Writer.json5(stringWriter); + writer.setIndent(" "); + try { + if (Objects.nonNull(rootComment) && !rootComment.isEmpty()) { + writer.comment(rootComment); + } + doToJson5(writer, psiType); + } catch (Exception ignore) { + + } + return stringWriter.toString(); + } + + static void doToJson5(Json5Writer writer, PsiType psiType) { + // TODO 完成json5序列化 + } +} diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/json5/Json5Writer.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/json5/Json5Writer.java index 577684e..1847d85 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/json5/Json5Writer.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/json5/Json5Writer.java @@ -127,7 +127,7 @@ import static com.github.passerr.idea.plugins.spring.web.json5.JsonScope.NONEMPT * Instances of this class are not thread safe. Calls that would result in a * malformed JSON string will fail with an {@link IllegalStateException}. */ -public final class Json5Writer implements Closeable, Flushable { +final class Json5Writer implements Closeable, Flushable { /** * From RFC 7159, "All Unicode characters may be placed within the * quotation marks except for the characters that must be escaped: diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java index 95a38f7..3d17f4d 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java @@ -3,9 +3,9 @@ package com.github.passerr.idea.plugins.spring.web.po; import com.github.passerr.idea.plugins.spring.web.AliasType; import com.github.passerr.idea.plugins.spring.web.ResourceUtil; import com.github.passerr.idea.plugins.spring.web.WebCopyConstants; -import com.intellij.util.xmlb.annotations.AbstractCollection; import com.intellij.util.xmlb.annotations.OptionTag; import com.intellij.util.xmlb.annotations.Tag; +import com.intellij.util.xmlb.annotations.XCollection; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Data; @@ -29,19 +29,19 @@ public class ApiDocSettingPo { @OptionTag(tag = "template", nameAttribute = "", converter = StringBuilderConverter.class) StringBuilder template; @Tag("query-param-ignore-types") - @AbstractCollection + @XCollection List queryParamIgnoreTypes; @Tag("query-param-ignore-annotations") - @AbstractCollection + @XCollection List queryParamIgnoreAnnotations; @Tag("body-ignore-types") - @AbstractCollection + @XCollection List bodyIgnoreTypes; @Tag("body-ignore-annotations") - @AbstractCollection + @XCollection List bodyIgnoreAnnotations; @Tag("objects") - @AbstractCollection(elementTypes = ApiDocObjectSerialPo.class) + @XCollection(elementTypes = ApiDocObjectSerialPo.class) List objects; public ApiDocSettingPo() { diff --git a/src/test/java/com/github/passerr/idea/plugins/json5/Json5Spec.java b/src/test/java/com/github/passerr/idea/plugins/spring/web/json5/Json5Spec.java similarity index 84% rename from src/test/java/com/github/passerr/idea/plugins/json5/Json5Spec.java rename to src/test/java/com/github/passerr/idea/plugins/spring/web/json5/Json5Spec.java index e168a65..29b8524 100644 --- a/src/test/java/com/github/passerr/idea/plugins/json5/Json5Spec.java +++ b/src/test/java/com/github/passerr/idea/plugins/spring/web/json5/Json5Spec.java @@ -1,6 +1,5 @@ -package com.github.passerr.idea.plugins.json5; +package com.github.passerr.idea.plugins.spring.web.json5; -import com.github.passerr.idea.plugins.spring.web.json5.Json5Writer; import org.junit.Test; import java.io.IOException; -- Gitee From d04eddc5a37326bd9f56ab1c2730963005a7de9f Mon Sep 17 00:00:00 2001 From: PasseRR Date: Tue, 20 Jul 2021 18:13:24 +0800 Subject: [PATCH 23/41] =?UTF-8?q?velocity=E9=BB=98=E8=AE=A4=E6=A8=A1?= =?UTF-8?q?=E7=89=88=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../spring/web/po/ApiDocSettingPo.java | 2 +- src/main/resources/api-doc-template.vm | 43 ++++++++++++++++++- .../idea/plugins/spring/web/VelocitySpec.java | 20 +++++++++ 3 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 src/test/java/com/github/passerr/idea/plugins/spring/web/VelocitySpec.java diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java index 3d17f4d..94d06c8 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java @@ -45,7 +45,7 @@ public class ApiDocSettingPo { List objects; public ApiDocSettingPo() { - this.template = new StringBuilder(ResourceUtil.readAsString("/api-doc-template.vm")); + this.template = new StringBuilder(ResourceUtil.readAsString("/api-doc-template.vm").replace("\r\n", "\n")); this.queryParamIgnoreTypes = new ArrayList<>(WebCopyConstants.QUERY_PARAM_IGNORE_TYPES); this.queryParamIgnoreAnnotations = new ArrayList<>(WebCopyConstants.QUERY_PARAM_IGNORE_ANNOTATIONS); this.bodyIgnoreTypes = new ArrayList<>(WebCopyConstants.ALL_IGNORE_TYPES); diff --git a/src/main/resources/api-doc-template.vm b/src/main/resources/api-doc-template.vm index 719049c..aae06e0 100644 --- a/src/main/resources/api-doc-template.vm +++ b/src/main/resources/api-doc-template.vm @@ -1 +1,42 @@ -## 默认api模版 \ No newline at end of file +## 默认api模版 markdown模版 +## 这是一行模版注释 +## ##[[这个中间可以是任何内容 不会被转义]]## + +**请求路径** +`$method $url` + +#if ($hasQueryParams) +**查询参数** + +|参数名|类型|说明| +|:---|:---|:---|---| + #foreach($p in $queryParams) + |$p.name|$p.alias|$p.desc| + #end +#end + +#if ($hasPathVariables) +**路径参数** + +|参数名|类型|说明| +|:---|:---|:---|---| + #foreach($p in $pathVariables) + |$p.name|$p.alias|$p.desc| + #end +#end + +#if ($hasBody) +**请求示例** + +```json5 + $body +``` +#end + +#if ($hasResponse) +**应答示例** + +```json5 + $response +``` +#end diff --git a/src/test/java/com/github/passerr/idea/plugins/spring/web/VelocitySpec.java b/src/test/java/com/github/passerr/idea/plugins/spring/web/VelocitySpec.java new file mode 100644 index 0000000..b257b42 --- /dev/null +++ b/src/test/java/com/github/passerr/idea/plugins/spring/web/VelocitySpec.java @@ -0,0 +1,20 @@ +package com.github.passerr.idea.plugins.spring.web; + +import org.junit.Test; + +/** + * {@link VelocityUtil} + * @author xiehai + * @date 2021/07/20 14:59 + * @Copyright(c) tellyes tech. inc. co.,ltd + */ +public class VelocitySpec { + @Test + public void testComment(){ + String template = "## 这是一行注释\n" + + "## 这是另一行注释\n" + + "#[[##]]# 这个是标题"; + + System.out.println(VelocityUtil.format(new StringBuilder(template), null)); + } +} -- Gitee From da6f92075b2e6aa3b39d68448662fd552073f6be Mon Sep 17 00:00:00 2001 From: PasseRR Date: Tue, 20 Jul 2021 18:29:46 +0800 Subject: [PATCH 24/41] =?UTF-8?q?velocity=E9=BB=98=E8=AE=A4=E6=A8=A1?= =?UTF-8?q?=E7=89=88=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/api-doc-template.vm | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/resources/api-doc-template.vm b/src/main/resources/api-doc-template.vm index aae06e0..d6ac10b 100644 --- a/src/main/resources/api-doc-template.vm +++ b/src/main/resources/api-doc-template.vm @@ -2,11 +2,11 @@ ## 这是一行模版注释 ## ##[[这个中间可以是任何内容 不会被转义]]## -**请求路径** +**请求路径** `$method $url` #if ($hasQueryParams) -**查询参数** +**查询参数** |参数名|类型|说明| |:---|:---|:---|---| @@ -16,7 +16,7 @@ #end #if ($hasPathVariables) -**路径参数** +**路径参数** |参数名|类型|说明| |:---|:---|:---|---| @@ -26,7 +26,7 @@ #end #if ($hasBody) -**请求示例** +**请求示例** ```json5 $body @@ -34,7 +34,7 @@ #end #if ($hasResponse) -**应答示例** +**应答示例** ```json5 $response -- Gitee From d8249071fbe85ba75ff55199e2c5559f2238cc22 Mon Sep 17 00:00:00 2001 From: PasseRR Date: Wed, 21 Jul 2021 15:17:50 +0800 Subject: [PATCH 25/41] =?UTF-8?q?=E6=A8=A1=E7=89=88=E7=BC=A9=E8=BF=9B?= =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/api-doc-template.vm | 30 +++++++++++++++----------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/main/resources/api-doc-template.vm b/src/main/resources/api-doc-template.vm index d6ac10b..af19f53 100644 --- a/src/main/resources/api-doc-template.vm +++ b/src/main/resources/api-doc-template.vm @@ -2,41 +2,45 @@ ## 这是一行模版注释 ## ##[[这个中间可以是任何内容 不会被转义]]## -**请求路径** +**请求路径** `$method $url` #if ($hasQueryParams) -**查询参数** +**查询参数** |参数名|类型|说明| |:---|:---|:---|---| - #foreach($p in $queryParams) - |$p.name|$p.alias|$p.desc| - #end + +#foreach($p in $queryParams) +|$p.name|$p.alias|$p.desc| +#end + #end #if ($hasPathVariables) -**路径参数** +**路径参数** |参数名|类型|说明| |:---|:---|:---|---| - #foreach($p in $pathVariables) - |$p.name|$p.alias|$p.desc| - #end + +#foreach($p in $pathVariables) +|$p.name|$p.alias|$p.desc| +#end + #end #if ($hasBody) -**请求示例** +**请求示例** ```json5 - $body +$body ``` #end #if ($hasResponse) -**应答示例** +**应答示例** ```json5 - $response +$response ``` #end -- Gitee From c58ce853e0aac8315b86153ca9182f220948e534 Mon Sep 17 00:00:00 2001 From: PasseRR Date: Wed, 21 Jul 2021 15:21:38 +0800 Subject: [PATCH 26/41] =?UTF-8?q?=E6=A8=A1=E7=89=88=E7=BC=A9=E8=BF=9B?= =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/api-doc-template.vm | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/resources/api-doc-template.vm b/src/main/resources/api-doc-template.vm index af19f53..2536201 100644 --- a/src/main/resources/api-doc-template.vm +++ b/src/main/resources/api-doc-template.vm @@ -10,7 +10,6 @@ |参数名|类型|说明| |:---|:---|:---|---| - #foreach($p in $queryParams) |$p.name|$p.alias|$p.desc| #end @@ -22,7 +21,6 @@ |参数名|类型|说明| |:---|:---|:---|---| - #foreach($p in $pathVariables) |$p.name|$p.alias|$p.desc| #end -- Gitee From 38cd64a9e7856b81322cab02390fe53476c6fbd2 Mon Sep 17 00:00:00 2001 From: PasseRR Date: Wed, 21 Jul 2021 17:26:26 +0800 Subject: [PATCH 27/41] =?UTF-8?q?=E6=A8=A1=E7=89=88=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/api-doc-template.vm | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/resources/api-doc-template.vm b/src/main/resources/api-doc-template.vm index 2536201..8ca3746 100644 --- a/src/main/resources/api-doc-template.vm +++ b/src/main/resources/api-doc-template.vm @@ -13,7 +13,6 @@ #foreach($p in $queryParams) |$p.name|$p.alias|$p.desc| #end - #end #if ($hasPathVariables) @@ -24,7 +23,6 @@ #foreach($p in $pathVariables) |$p.name|$p.alias|$p.desc| #end - #end #if ($hasBody) -- Gitee From 8028756571b71be6ac9bf67e4f32b93988c641a6 Mon Sep 17 00:00:00 2001 From: PasseRR Date: Wed, 21 Jul 2021 17:41:06 +0800 Subject: [PATCH 28/41] =?UTF-8?q?api=E6=96=87=E6=A1=A3=E5=A4=8D=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../idea/plugins/spring/web/AliasType.java | 11 + .../spring/web/CopyMethodApiDocAction.java | 85 +++--- .../web/PsiAnnotationMemberValueUtil.java | 22 +- .../{PsiUtil.java => SpringWebPsiUtil.java} | 8 +- .../plugins/spring/web/WebCopyConstants.java | 5 +- .../spring/web/json5/Json5Generator.java | 260 ++++++++++++++++++ .../plugins/spring/web/json5/Json5Util.java | 44 --- .../plugins/spring/web/json5/Json5Writer.java | 12 + .../spring/web/po/ApiDocSettingPo.java | 12 + 9 files changed, 371 insertions(+), 88 deletions(-) rename src/main/java/com/github/passerr/idea/plugins/spring/web/{PsiUtil.java => SpringWebPsiUtil.java} (93%) create mode 100644 src/main/java/com/github/passerr/idea/plugins/spring/web/json5/Json5Generator.java delete mode 100644 src/main/java/com/github/passerr/idea/plugins/spring/web/json5/Json5Util.java diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/AliasType.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/AliasType.java index ebd1237..4546f1a 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/AliasType.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/AliasType.java @@ -5,6 +5,8 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; +import java.util.Arrays; + /** * 别名类型 * @author xiehai @@ -68,4 +70,13 @@ public enum AliasType { public static final String UNKNOWN_ALIAS = "object"; public abstract Object deserialize(String value); + + public static Object value(String alias, String value) { + return + Arrays.stream(values()) + .filter(it -> it.type.equals(alias)) + .map(it -> it.deserialize(value)) + .findFirst() + .orElse(null); + } } diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.java index f0d0229..ee16901 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.java @@ -1,19 +1,22 @@ package com.github.passerr.idea.plugins.spring.web; -import com.github.passerr.idea.plugins.spring.web.json5.Json5Util; +import com.github.passerr.idea.plugins.spring.web.json5.Json5Generator; import com.github.passerr.idea.plugins.spring.web.po.ApiDocSettingPo; import com.intellij.codeInsight.AnnotationUtil; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.ide.CopyPasteManager; import com.intellij.psi.PsiAnnotation; import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiClassType; import com.intellij.psi.PsiMethod; import com.intellij.psi.PsiParameter; +import com.intellij.psi.impl.source.javadoc.PsiDocParamRef; import com.intellij.psi.javadoc.PsiDocComment; import com.intellij.psi.javadoc.PsiDocToken; import com.intellij.psi.util.PsiTypesUtil; import com.intellij.util.ui.TextTransferable; import lombok.AllArgsConstructor; +import lombok.Getter; import org.jetbrains.annotations.NotNull; import java.util.Arrays; @@ -118,18 +121,24 @@ public class CopyMethodApiDocAction extends BaseWebCopyAction { // 方法注释tag列表 .ifPresent(tags -> tags.stream() - .filter(Objects::nonNull) .filter(it -> "param".equals(it.getName())) .forEach(it -> - comments.put( - it.getName(), - Arrays.stream(it.getDataElements()) - .filter(e -> e instanceof PsiDocToken) - .map(PsiDocToken.class::cast) - .filter(PsiUtil::isDocCommentData) - .map(e -> e.getText().trim()) - .collect(Collectors.joining("")) - ) + Arrays.stream(it.getDataElements()) + .filter(e -> e instanceof PsiDocParamRef) + .map(PsiDocParamRef.class::cast) + .findFirst() + .map(PsiDocParamRef::getText) + .ifPresent(p -> + comments.put( + p, + Arrays.stream(it.getDataElements()) + .filter(e -> e instanceof PsiDocToken) + .map(PsiDocToken.class::cast) + .filter(SpringWebPsiUtil::isDocCommentData) + .map(e -> e.getText().trim()) + .collect(Collectors.joining("")) + ) + ) ) ); @@ -146,11 +155,15 @@ public class CopyMethodApiDocAction extends BaseWebCopyAction { private static List pathVariables(PsiMethod method, Map comments, ApiDocSettingPo state) { return Arrays.stream(method.getParameterList().getParameters()) - .filter(PsiUtil::isValidParamType) + .filter(SpringWebPsiUtil::isValidParamType) .map(it -> { + PsiClass psiClass = ((PsiClassType) it.getType()).resolve(); + if (Objects.isNull(psiClass)) { + return null; + } + String type = psiClass.getQualifiedName(); PsiAnnotation annotation = AnnotationUtil.findAnnotation( it, WebCopyConstants.PATH_VARIABLE_ANNOTATION); - String type = it.getType().getCanonicalText(); String alias = state.alias(type); // 必须要@PathVariable注解存在且是有效别名 if (Objects.nonNull(annotation) && !UNKNOWN_ALIAS.equals(alias)) { @@ -158,10 +171,10 @@ public class CopyMethodApiDocAction extends BaseWebCopyAction { new Var( Optional.ofNullable(PsiAnnotationMemberValueUtil.value(annotation, "value")) .map(String::valueOf) - .orElseGet(it::getText), + .orElseGet(it::getName), type, alias, - comments.getOrDefault(it.getText(), it.getText()) + comments.getOrDefault(it.getName(), it.getName()) ); } @@ -183,43 +196,44 @@ public class CopyMethodApiDocAction extends BaseWebCopyAction { Arrays.stream(method.getParameterList().getParameters()) .filter(it -> // 排除接口类型参数、忽略类型、带忽略类型注解的参数 - PsiUtil.isValidParamType( + SpringWebPsiUtil.isValidParamType( it, state.getQueryParamIgnoreTypes(), state.getQueryParamIgnoreAnnotations() ) ) .flatMap(it -> { + PsiClass clazz = PsiTypesUtil.getPsiClass(it.getType()); + // 未知类型 + if (Objects.isNull(clazz)) { + return Stream.empty(); + } // 查询参数注解 PsiAnnotation annotation = AnnotationUtil.findAnnotation( it, WebCopyConstants.QUERY_PARAM_ANNOTATION); - String type = it.getType().getCanonicalText(); + String type = clazz.getQualifiedName(); String alias = state.alias(type); // 对象类型 if (UNKNOWN_ALIAS.equals(alias)) { - PsiClass clazz = PsiTypesUtil.getPsiClass(it.getType()); - // 未知类型 - if (Objects.isNull(clazz)) { - return Stream.empty(); - } - return Arrays.stream(clazz.getAllFields()) - .filter(PsiUtil::isValidFiled) + .filter(SpringWebPsiUtil::isValidFiled) // 查询参数只遍历一层 且忽略掉这层的未知类型 - .filter(f -> !UNKNOWN_ALIAS.equals(state.alias(f.getType().getCanonicalText()))) + .filter(f -> !UNKNOWN_ALIAS.equals(state.alias(f.getType()))) .map(f -> new Var( f.getName(), - f.getType().getCanonicalText(), - state.alias(f.getType().getCanonicalText()), + Objects.requireNonNull( + PsiTypesUtil.getPsiClass(f.getType()) + ).getQualifiedName(), + state.alias(f.getType()), Optional.ofNullable(f.getDocComment()) .map(PsiDocComment::getDescriptionElements) .map(els -> Arrays.stream(els) .filter(e -> e instanceof PsiDocToken) .map(PsiDocToken.class::cast) - .filter(PsiUtil::isDocCommentData) + .filter(SpringWebPsiUtil::isDocCommentData) .map(e -> e.getText().trim()) .collect(Collectors.joining("")) ) @@ -233,10 +247,10 @@ public class CopyMethodApiDocAction extends BaseWebCopyAction { new Var( Optional.ofNullable(PsiAnnotationMemberValueUtil.value(annotation, "value")) .map(String::valueOf) - .orElseGet(it::getText), + .orElseGet(it::getName), type, alias, - comments.getOrDefault(it.getText(), it.getText()) + comments.getOrDefault(it.getName(), it.getName()) ) ); } @@ -255,7 +269,7 @@ public class CopyMethodApiDocAction extends BaseWebCopyAction { PsiParameter parameter = Arrays.stream(method.getParameterList().getParameters()) // 排除body参数类型为排除类型 即无法序列化的类型 如Object、Map等 - .filter(it -> PsiUtil.isValidParamType(it, state.getBodyIgnoreTypes())) + .filter(it -> SpringWebPsiUtil.isValidParamType(it, state.getBodyIgnoreTypes())) // 方法参数中有@RequestBody注解的参数且只会取第一个 .filter(it -> AnnotationUtil.findAnnotation(it, WebCopyConstants.BODY_ANNOTATION) != null) .findFirst() @@ -265,7 +279,7 @@ public class CopyMethodApiDocAction extends BaseWebCopyAction { } return - Json5Util.toJson5( + Json5Generator.toJson5( parameter.getType(), comments.get(parameter.getName()), Collections.unmodifiableList(state.getBodyIgnoreTypes()), @@ -281,9 +295,9 @@ public class CopyMethodApiDocAction extends BaseWebCopyAction { */ private static String response(PsiMethod method, ApiDocSettingPo state) { return - Json5Util.toJson5( + Json5Generator.toJson5( method.getReturnType(), - PsiUtil.returnComment(method), + SpringWebPsiUtil.returnComment(method), Collections.unmodifiableList(state.getBodyIgnoreTypes()), Collections.unmodifiableList(state.getObjects()) ); @@ -293,7 +307,8 @@ public class CopyMethodApiDocAction extends BaseWebCopyAction { * 参数实体 */ @AllArgsConstructor - private static class Var { + @Getter + public static class Var { /** * 参数名 */ diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/PsiAnnotationMemberValueUtil.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/PsiAnnotationMemberValueUtil.java index df851df..2023a6b 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/PsiAnnotationMemberValueUtil.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/PsiAnnotationMemberValueUtil.java @@ -8,6 +8,7 @@ import com.intellij.psi.PsiExpression; import com.intellij.psi.PsiField; import com.intellij.psi.PsiLiteralExpression; import com.intellij.psi.PsiReferenceExpression; +import com.intellij.psi.impl.compiled.ClsEnumConstantImpl; import java.util.Arrays; import java.util.Objects; @@ -80,12 +81,29 @@ class PsiAnnotationMemberValueUtil { if (Objects.nonNull(value) && value.getClass().isArray()) { Object[] array = (Object[]) value; if (array.length > 0) { - return array[0]; + return format(array[0]); } return null; } - return value; + return format(value); + } + + static Object format(Object value) { + if (value == null) { + return null; + } + + if (value instanceof String || value.getClass().isPrimitive()) { + return value; + } + + if (value instanceof ClsEnumConstantImpl) { + return ((ClsEnumConstantImpl) value).getName(); + } + + // 注解类型忽略 + return null; } } diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/PsiUtil.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/SpringWebPsiUtil.java similarity index 93% rename from src/main/java/com/github/passerr/idea/plugins/spring/web/PsiUtil.java rename to src/main/java/com/github/passerr/idea/plugins/spring/web/SpringWebPsiUtil.java index b554732..3e58507 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/PsiUtil.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/SpringWebPsiUtil.java @@ -18,12 +18,12 @@ import java.util.Objects; import java.util.stream.Collectors; /** - * psi类型工具 + * web psi类型工具 * @author xiehai * @date 2021/07/20 14:27 * @Copyright(c) tellyes tech. inc. co.,ltd */ -public interface PsiUtil { +public interface SpringWebPsiUtil { /** * 是否是合法的参数类型 * 非接口类型且满足排除类型 @@ -103,12 +103,10 @@ public interface PsiUtil { .findFirst() .map(it -> Arrays.stream(it.getDataElements()) - .filter(e -> e instanceof PsiDocToken) - .map(PsiDocToken.class::cast) - .filter(PsiUtil::isDocCommentData) .map(e -> e.getText().trim()) .collect(Collectors.joining("")) ) + .filter(it -> !it.isEmpty()) .orElse(null); } } diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/WebCopyConstants.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/WebCopyConstants.java index 73db5b1..7305bb0 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/WebCopyConstants.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/WebCopyConstants.java @@ -2,6 +2,7 @@ package com.github.passerr.idea.plugins.spring.web; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; /** @@ -14,7 +15,7 @@ public abstract class WebCopyConstants { /** * 不支持的类型 */ - public static final List ALL_IGNORE_TYPES = Arrays.asList("java.lang.Object", "java.util.Map"); + public static final List ALL_IGNORE_TYPES = Collections.singletonList("java.util.Map"); /** * 查询参数忽略类型 */ @@ -47,7 +48,7 @@ public abstract class WebCopyConstants { public static final List QUERY_PARAM_IGNORE_ANNOTATIONS = new ArrayList<>(Arrays.asList( "org.springframework.web.bind.annotation.CookieValue", "org.springframework.web.bind.annotation.RequestHeader", - "org.springframework.web.bind.annotation.ResponseBody", + "org.springframework.web.bind.annotation.RequestBody", "org.springframework.web.bind.annotation.RequestAttribute", "org.springframework.web.bind.annotation.SessionAttribute", "org.springframework.web.bind.annotation.SessionAttributes" diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/json5/Json5Generator.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/json5/Json5Generator.java new file mode 100644 index 0000000..50cc010 --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/json5/Json5Generator.java @@ -0,0 +1,260 @@ +package com.github.passerr.idea.plugins.spring.web.json5; + +import com.github.passerr.idea.plugins.spring.web.AliasType; +import com.github.passerr.idea.plugins.spring.web.SpringWebPsiUtil; +import com.github.passerr.idea.plugins.spring.web.po.ApiDocObjectSerialPo; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.psi.CommonClassNames; +import com.intellij.psi.PsiArrayType; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiClassType; +import com.intellij.psi.PsiField; +import com.intellij.psi.PsiPrimitiveType; +import com.intellij.psi.PsiType; +import com.intellij.psi.PsiTypeParameter; +import com.intellij.psi.impl.source.PsiClassReferenceType; +import com.intellij.psi.javadoc.PsiDocComment; +import com.intellij.psi.javadoc.PsiDocToken; +import com.intellij.psi.util.InheritanceUtil; +import com.intellij.psi.util.PsiTypesUtil; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +/** + * json5工具类 + * @author xiehai + * @date 2021/07/20 14:13 + * @Copyright(c) tellyes tech. inc. co.,ltd + */ +public class Json5Generator { + private final Set ignores; + private final Map serials; + private static final Logger LOG = Logger.getInstance(Json5Generator.class); + private static final Consumer BEGIN_ARRAY = writer -> { + try { + writer.beginArray(); + } catch (IOException e) { + LOG.error(e.getMessage(), e); + } + }; + private static final Consumer END_ARRAY = writer -> { + try { + writer.endArray(); + } catch (IOException e) { + LOG.error(e.getMessage(), e); + } + }; + private static final Consumer BEGIN_OBJECT = writer -> { + try { + writer.beginObject(); + } catch (IOException e) { + LOG.error(e.getMessage(), e); + } + }; + private static final Consumer END_OBJECT = writer -> { + try { + writer.endObject(); + } catch (IOException e) { + LOG.error(e.getMessage(), e); + } + }; + private static final BiConsumer COMMENT = (writer, s) -> { + try { + writer.comment(s); + } catch (IOException e) { + LOG.error(e.getMessage(), e); + } + }; + private static final BiConsumer VALUE = (writer, o) -> { + try { + writer.value(o); + } catch (IOException e) { + LOG.error(e.getMessage(), e); + } + }; + private static final BiConsumer NAME = (writer, s) -> { + try { + writer.name(s); + } catch (IOException e) { + LOG.error(e.getMessage(), e); + } + }; + + Json5Generator(List originIgnores, List originSerials) { + HashSet set = new HashSet<>(originIgnores); + // object不能在校验范围 + set.remove(CommonClassNames.JAVA_LANG_OBJECT); + this.ignores = Collections.unmodifiableSet(set); + Map collect = originSerials.stream() + .collect(Collectors.toMap(ApiDocObjectSerialPo::getType, it -> it, (o, n) -> n)); + // 保证存在原型类型 + collect.putIfAbsent("boolean", new ApiDocObjectSerialPo("boolean", AliasType.BOOLEAN.getType(), "true")); + Arrays.asList("byte", "short", "char", "int") + .forEach(it -> collect.putIfAbsent(it, new ApiDocObjectSerialPo(it, AliasType.INT.getType(), "1024"))); + Arrays.asList("float", "double") + .forEach(it -> collect.putIfAbsent(it, new ApiDocObjectSerialPo(it, AliasType.FLOAT.getType(), "102.4"))); + collect.putIfAbsent("long", new ApiDocObjectSerialPo("long", AliasType.STRING.getType(), "2048")); + this.serials = Collections.unmodifiableMap(collect); + } + + /** + * 是否是忽略类型 + * @param psiType {@link PsiType} + * @return true/false + */ + boolean isIgnore(PsiType psiType) { + if (psiType instanceof PsiPrimitiveType) { + // void类型忽略 + if ("void".equals(((PsiPrimitiveType) psiType).getName())) { + return true; + } + return false; + } + if (!(psiType instanceof PsiClassType)) { + // 非class类型 + return false; + } + PsiClass resolve = ((PsiClassType) psiType).resolve(); + if (Objects.isNull(resolve)) { + return true; + } + String className = resolve.getQualifiedName(); + return + // 自定义忽略类型及void类型忽略 + this.ignores.contains(className) + || CommonClassNames.JAVA_LANG_VOID.equals(className); + } + + String toJson5(PsiType psiType, String rootComment) { + if (this.isIgnore(psiType)) { + return null; + } + + StringWriter stringWriter = new StringWriter(); + Json5Writer writer = Json5Writer.json5(stringWriter); + writer.setIndent(" "); + + // 根注释 + if (Objects.nonNull(rootComment)) { + COMMENT.accept(writer, rootComment); + } + + this.toJson5(writer, psiType); + + return Optional.of(stringWriter.toString()).map(String::trim).filter(it -> it.length() > 0).orElse(null); + } + + void toJson5(Json5Writer writer, PsiType type) { + if (this.isIgnore(type)) { + return; + } + + // 数组类型 + if (type instanceof PsiArrayType) { + PsiArrayType psiArrayType = (PsiArrayType) type; + BEGIN_ARRAY.accept(writer); + this.toJson5(writer, psiArrayType.getComponentType()); + this.toJson5(writer, psiArrayType.getComponentType()); + END_ARRAY.accept(writer); + return; + } + + if (type instanceof PsiPrimitiveType) { + // 基本类型 + PsiPrimitiveType primitiveType = (PsiPrimitiveType) type; + ApiDocObjectSerialPo po = serials.get(primitiveType.getName()); + VALUE.accept(writer, AliasType.value(po.getAlias(), po.getValue())); + return; + } + + PsiClass psiClass = PsiTypesUtil.getPsiClass(type); + // 不支持类型 + if (Objects.isNull(psiClass)) { + return; + } + String className = psiClass.getQualifiedName(); + if (CommonClassNames.JAVA_LANG_OBJECT.equals(className)) { + BEGIN_OBJECT.accept(writer); + END_OBJECT.accept(writer); + return; + } + + // 基本类型 + if (serials.containsKey(className)) { + ApiDocObjectSerialPo po = serials.get(className); + VALUE.accept(writer, AliasType.value(po.getAlias(), po.getValue())); + return; + } + + // 否则则为复杂类型 + if (type instanceof PsiClassReferenceType) { + PsiClassReferenceType referenceType = (PsiClassReferenceType) type; + // 泛型参数 + Map substitutionMap = referenceType.resolveGenerics().getSubstitutor() + .getSubstitutionMap(); + + // 集合类型 + if (InheritanceUtil.isInheritor(type, Collection.class.getName())) { + // 必须存在泛型参数 + if (referenceType.getParameterCount() > 0) { + BEGIN_ARRAY.accept(writer); + this.toJson5(writer, referenceType.getParameters()[0]); + this.toJson5(writer, referenceType.getParameters()[0]); + END_ARRAY.accept(writer); + return; + } + return; + } + + BEGIN_OBJECT.accept(writer); + Arrays.stream(psiClass.getAllFields()) + .filter(SpringWebPsiUtil::isValidFiled) + .sorted(Comparator.comparing(PsiField::getName)) + .forEach(it -> { + // 字段注释 + Optional.ofNullable(it.getDocComment()) + .map(PsiDocComment::getDescriptionElements) + .map(els -> + Arrays.stream(els) + .filter(e -> e instanceof PsiDocToken) + .map(PsiDocToken.class::cast) + .filter(SpringWebPsiUtil::isDocCommentData) + .map(e -> e.getText().trim()) + .collect(Collectors.joining("")) + ) + .filter(comment -> !comment.isEmpty()) + .ifPresent(comment -> COMMENT.accept(writer, comment)); + NAME.accept(writer, it.getName()); + this.toJson5(writer, it.getType()); + }); + END_OBJECT.accept(writer); + } + } + + /** + * 将任意类型转为json5 + * @param psiType {@link PsiType} + * @param rootComment 根注释 + * @param originIgnores 忽略类型 + * @param originSerials 序列化支持类型 + * @return json5 + */ + public static String toJson5(PsiType psiType, String rootComment, List originIgnores, + List originSerials) { + return new Json5Generator(originIgnores, originSerials).toJson5(psiType, rootComment); + } +} diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/json5/Json5Util.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/json5/Json5Util.java deleted file mode 100644 index bd78eff..0000000 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/json5/Json5Util.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.github.passerr.idea.plugins.spring.web.json5; - -import com.github.passerr.idea.plugins.spring.web.po.ApiDocObjectSerialPo; -import com.intellij.psi.PsiType; - -import java.io.StringWriter; -import java.util.List; -import java.util.Objects; - -/** - * json5工具类 - * @author xiehai - * @date 2021/07/20 14:13 - * @Copyright(c) tellyes tech. inc. co.,ltd - */ -public interface Json5Util { - /** - * 将任意类型转为json5 - * @param psiType {@link PsiType} - * @param rootComment 根注释 - * @param ignores 忽略类型 - * @param objects 序列化支持类型 - * @return json5 - */ - static String toJson5(PsiType psiType, String rootComment, List ignores, - List objects) { - StringWriter stringWriter = new StringWriter(); - Json5Writer writer = Json5Writer.json5(stringWriter); - writer.setIndent(" "); - try { - if (Objects.nonNull(rootComment) && !rootComment.isEmpty()) { - writer.comment(rootComment); - } - doToJson5(writer, psiType); - } catch (Exception ignore) { - - } - return stringWriter.toString(); - } - - static void doToJson5(Json5Writer writer, PsiType psiType) { - // TODO 完成json5序列化 - } -} diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/json5/Json5Writer.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/json5/Json5Writer.java index 1847d85..13205d5 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/json5/Json5Writer.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/json5/Json5Writer.java @@ -466,6 +466,18 @@ final class Json5Writer implements Closeable, Flushable { return this; } + public Json5Writer value(Object value) throws IOException { + if (value == null) { + return this.nullValue(); + } else if (value instanceof Number) { + return this.value((Number) value); + } else if (value instanceof Boolean) { + return this.value((Boolean) value); + } else { + return this.value(String.valueOf(value)); + } + } + /** * Encodes {@code null}. * @return this writer. diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java index 94d06c8..e15ce52 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java @@ -3,6 +3,9 @@ package com.github.passerr.idea.plugins.spring.web.po; import com.github.passerr.idea.plugins.spring.web.AliasType; import com.github.passerr.idea.plugins.spring.web.ResourceUtil; import com.github.passerr.idea.plugins.spring.web.WebCopyConstants; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiType; +import com.intellij.psi.util.PsiTypesUtil; import com.intellij.util.xmlb.annotations.OptionTag; import com.intellij.util.xmlb.annotations.Tag; import com.intellij.util.xmlb.annotations.XCollection; @@ -101,6 +104,15 @@ public class ApiDocSettingPo { .orElse(AliasType.UNKNOWN_ALIAS); } + public String alias(PsiType psiType) { + PsiClass psiClass = PsiTypesUtil.getPsiClass(psiType); + if (Objects.isNull(psiClass)) { + return AliasType.UNKNOWN_ALIAS; + } + + return this.alias(psiClass.getQualifiedName()); + } + @Override public boolean equals(Object o) { if (this == o) { return true; } -- Gitee From f4903d4fb7e904e24431595989b4b48d8e91b533 Mon Sep 17 00:00:00 2001 From: PasseRR Date: Wed, 21 Jul 2021 18:23:57 +0800 Subject: [PATCH 29/41] =?UTF-8?q?api=E6=96=87=E6=A1=A3=E5=A4=8D=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugins/spring/web/ApiDocConfigViews.java | 78 +----------------- .../spring/web/CopyMethodApiDocAction.java | 6 +- .../plugins/spring/web/WebCopyConstants.java | 63 +++++++-------- .../spring/web/json5/Json5Generator.java | 79 ++++++++++--------- .../spring/web/po/ApiDocSettingPo.java | 12 +-- 5 files changed, 77 insertions(+), 161 deletions(-) diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java index a64d0a5..514af79 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java @@ -331,72 +331,6 @@ public abstract class ApiDocConfigViews { */ private static JPanel bodyParamPanel(ApiDocSettingPo setting) { JPanel panel = new JPanel(new GridBagLayout()); - PanelWithButtons top = new IdeaPanelWithButtons("忽略类型:") { - @Override - protected JComponent createMainComponent() { - BaseTableModel model = new BaseTableModel<>( - Collections.singletonList("类型"), setting.getBodyIgnoreTypes()); - JBTable table = new IdeaJbTable(model); - // 弹出层构建器 - BiFunction function = (s, r) -> { - JPanel p = new JPanel(new GridBagLayout()); - GridBagConstraints gb = new GridBagConstraints(0, 0, 1, 1, 0, 0, - GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, - JBUI.insets(0, 0, 5, 10), 0, 0 - ); - JLabel typeLabel = new JLabel("类型"); - p.add(typeLabel, gb); - JTextField textField = new JTextField(s.toString()); - textField.getDocument() - .addDocumentListener( - new com.intellij.ui.DocumentAdapter() { - @Override - protected void textChanged(javax.swing.event.DocumentEvent e) { - s.setLength(0); - s.append(textField.getText()); - r.run(); - } - }); - Dimension oldPreferredSize = textField.getPreferredSize(); - textField.setPreferredSize(new Dimension(300, oldPreferredSize.height)); - gb.gridx = 1; - gb.gridwidth = GridBagConstraints.REMAINDER; - gb.weightx = 1; - p.add(textField, gb); - r.run(); - - return p; - }; - return - ToolbarDecorator.createDecorator(table) - .setAddAction(it -> - new IdeaDialog(panel) - .title("新增忽略类型") - .value(new StringBuilder()) - .okAction(t -> setting.getBodyIgnoreTypes().add(t.toString())) - .changePredicate(t -> t.length() > 0) - .componentFunction(t -> function.apply(t.getValue(), t::onChange)) - .doInit() - .showAndGet() - ) - .setAddActionName("新增") - .setEditAction(it -> - new IdeaDialog(panel) - .title("编辑忽略类型") - .value(new StringBuilder(model.getRow(table.getSelectedRow()))) - .okAction(t -> setting.getBodyIgnoreTypes().set(table.getSelectedRow(), t.toString())) - .changePredicate(t -> t.length() > 0) - .componentFunction(t -> function.apply(t.getValue(), t::onChange)) - .doInit() - .showAndGet() - ) - .setEditActionName("编辑") - .setRemoveAction(it -> model.removeRow(table.getSelectedRow())) - .setRemoveActionName("删除") - .disableUpDownActions() - .createPanel(); - } - }; PanelWithButtons bottom = new IdeaPanelWithButtons("忽略注解(字段上的注解):") { @Override protected JComponent createMainComponent() { @@ -465,20 +399,10 @@ public abstract class ApiDocConfigViews { .createPanel(); } }; - panel.add( - top, - new GridBagConstraints( - 0, 0, 1, 1, 1, 1, - GridBagConstraints.NORTH, - GridBagConstraints.BOTH, - JBUI.emptyInsets(), 0, 0 - ) - ); - panel.add( bottom, new GridBagConstraints( - 0, 1, 1, 1, 1, 1, + 0, 0, 1, 1, 1, 1, GridBagConstraints.NORTH, GridBagConstraints.BOTH, JBUI.emptyInsets(), 0, 0 diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.java index ee16901..680f7d0 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.java @@ -268,8 +268,6 @@ public class CopyMethodApiDocAction extends BaseWebCopyAction { private static String body(PsiMethod method, Map comments, ApiDocSettingPo state) { PsiParameter parameter = Arrays.stream(method.getParameterList().getParameters()) - // 排除body参数类型为排除类型 即无法序列化的类型 如Object、Map等 - .filter(it -> SpringWebPsiUtil.isValidParamType(it, state.getBodyIgnoreTypes())) // 方法参数中有@RequestBody注解的参数且只会取第一个 .filter(it -> AnnotationUtil.findAnnotation(it, WebCopyConstants.BODY_ANNOTATION) != null) .findFirst() @@ -282,7 +280,7 @@ public class CopyMethodApiDocAction extends BaseWebCopyAction { Json5Generator.toJson5( parameter.getType(), comments.get(parameter.getName()), - Collections.unmodifiableList(state.getBodyIgnoreTypes()), + Collections.unmodifiableList(state.getBodyIgnoreAnnotations()), Collections.unmodifiableList(state.getObjects()) ); } @@ -298,7 +296,7 @@ public class CopyMethodApiDocAction extends BaseWebCopyAction { Json5Generator.toJson5( method.getReturnType(), SpringWebPsiUtil.returnComment(method), - Collections.unmodifiableList(state.getBodyIgnoreTypes()), + Collections.unmodifiableList(state.getBodyIgnoreAnnotations()), Collections.unmodifiableList(state.getObjects()) ); } diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/WebCopyConstants.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/WebCopyConstants.java index 7305bb0..5a17e0f 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/WebCopyConstants.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/WebCopyConstants.java @@ -1,6 +1,5 @@ package com.github.passerr.idea.plugins.spring.web; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -12,24 +11,6 @@ import java.util.List; * @Copyright(c) tellyes tech. inc. co.,ltd */ public abstract class WebCopyConstants { - /** - * 不支持的类型 - */ - public static final List ALL_IGNORE_TYPES = Collections.singletonList("java.util.Map"); - /** - * 查询参数忽略类型 - */ - public static final List QUERY_PARAM_IGNORE_TYPES = new ArrayList<>(Arrays.asList( - "org.springframework.ui.Model", - "javax.servlet.http.HttpServletRequest", - "javax.servlet.http.HttpServletResponse", - "javax.servlet.http.HttpSession" - )); - - static { - QUERY_PARAM_IGNORE_TYPES.addAll(ALL_IGNORE_TYPES); - } - /** * 路径参数注解 */ @@ -42,19 +23,39 @@ public abstract class WebCopyConstants { * 报文体参数注解 */ public static final String BODY_ANNOTATION = "org.springframework.web.bind.annotation.RequestBody"; + /** + * 查询参数忽略类型 + */ + public static final List QUERY_PARAM_IGNORE_TYPES = Collections.unmodifiableList( + Arrays.asList( + "org.springframework.ui.Model", + "javax.servlet.http.HttpServletRequest", + "javax.servlet.http.HttpServletResponse", + "javax.servlet.http.HttpSession", + "java.util.Map" + ) + ); /** * 查询参数忽略注解 */ - public static final List QUERY_PARAM_IGNORE_ANNOTATIONS = new ArrayList<>(Arrays.asList( - "org.springframework.web.bind.annotation.CookieValue", - "org.springframework.web.bind.annotation.RequestHeader", - "org.springframework.web.bind.annotation.RequestBody", - "org.springframework.web.bind.annotation.RequestAttribute", - "org.springframework.web.bind.annotation.SessionAttribute", - "org.springframework.web.bind.annotation.SessionAttributes" - )); - - static { - QUERY_PARAM_IGNORE_ANNOTATIONS.add(PATH_VARIABLE_ANNOTATION); - } + public static final List QUERY_PARAM_IGNORE_ANNOTATIONS = Collections.unmodifiableList( + Arrays.asList( + "org.springframework.web.bind.annotation.CookieValue", + "org.springframework.web.bind.annotation.RequestHeader", + BODY_ANNOTATION, + "org.springframework.web.bind.annotation.RequestAttribute", + "org.springframework.web.bind.annotation.SessionAttribute", + "org.springframework.web.bind.annotation.SessionAttributes", + PATH_VARIABLE_ANNOTATION + ) + ); + /** + * json字段忽略注解 + */ + public static final List FIELD_IGNORE_ANNOTATIONS = Collections.unmodifiableList( + Arrays.asList( + "com.fasterxml.jackson.annotation.JsonIgnore", + "com.google.gson.annotations.Expose" + ) + ); } diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/json5/Json5Generator.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/json5/Json5Generator.java index 50cc010..f74da91 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/json5/Json5Generator.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/json5/Json5Generator.java @@ -3,6 +3,7 @@ package com.github.passerr.idea.plugins.spring.web.json5; import com.github.passerr.idea.plugins.spring.web.AliasType; import com.github.passerr.idea.plugins.spring.web.SpringWebPsiUtil; import com.github.passerr.idea.plugins.spring.web.po.ApiDocObjectSerialPo; +import com.intellij.codeInsight.AnnotationUtil; import com.intellij.openapi.diagnostic.Logger; import com.intellij.psi.CommonClassNames; import com.intellij.psi.PsiArrayType; @@ -41,6 +42,7 @@ import java.util.stream.Collectors; * @Copyright(c) tellyes tech. inc. co.,ltd */ public class Json5Generator { + // 忽略注解 private final Set ignores; private final Map serials; private static final Logger LOG = Logger.getInstance(Json5Generator.class); @@ -95,10 +97,8 @@ public class Json5Generator { }; Json5Generator(List originIgnores, List originSerials) { - HashSet set = new HashSet<>(originIgnores); // object不能在校验范围 - set.remove(CommonClassNames.JAVA_LANG_OBJECT); - this.ignores = Collections.unmodifiableSet(set); + this.ignores = Collections.unmodifiableSet(new HashSet<>(originIgnores)); Map collect = originSerials.stream() .collect(Collectors.toMap(ApiDocObjectSerialPo::getType, it -> it, (o, n) -> n)); // 保证存在原型类型 @@ -124,19 +124,14 @@ public class Json5Generator { } return false; } - if (!(psiType instanceof PsiClassType)) { - // 非class类型 - return false; - } + PsiClass resolve = ((PsiClassType) psiType).resolve(); if (Objects.isNull(resolve)) { - return true; + return false; } - String className = resolve.getQualifiedName(); - return - // 自定义忽略类型及void类型忽略 - this.ignores.contains(className) - || CommonClassNames.JAVA_LANG_VOID.equals(className); + + // java.lang.Void类型忽略 + return CommonClassNames.JAVA_LANG_VOID.equals(resolve.getQualifiedName()); } String toJson5(PsiType psiType, String rootComment) { @@ -149,8 +144,8 @@ public class Json5Generator { writer.setIndent(" "); // 根注释 - if (Objects.nonNull(rootComment)) { - COMMENT.accept(writer, rootComment); + if (Objects.nonNull(rootComment) && !rootComment.trim().isEmpty()) { + COMMENT.accept(writer, rootComment.trim()); } this.toJson5(writer, psiType); @@ -209,38 +204,44 @@ public class Json5Generator { // 集合类型 if (InheritanceUtil.isInheritor(type, Collection.class.getName())) { - // 必须存在泛型参数 + BEGIN_ARRAY.accept(writer); + // 若存在泛型参数 if (referenceType.getParameterCount() > 0) { - BEGIN_ARRAY.accept(writer); this.toJson5(writer, referenceType.getParameters()[0]); this.toJson5(writer, referenceType.getParameters()[0]); - END_ARRAY.accept(writer); - return; } + END_ARRAY.accept(writer); return; } BEGIN_OBJECT.accept(writer); - Arrays.stream(psiClass.getAllFields()) - .filter(SpringWebPsiUtil::isValidFiled) - .sorted(Comparator.comparing(PsiField::getName)) - .forEach(it -> { - // 字段注释 - Optional.ofNullable(it.getDocComment()) - .map(PsiDocComment::getDescriptionElements) - .map(els -> - Arrays.stream(els) - .filter(e -> e instanceof PsiDocToken) - .map(PsiDocToken.class::cast) - .filter(SpringWebPsiUtil::isDocCommentData) - .map(e -> e.getText().trim()) - .collect(Collectors.joining("")) - ) - .filter(comment -> !comment.isEmpty()) - .ifPresent(comment -> COMMENT.accept(writer, comment)); - NAME.accept(writer, it.getName()); - this.toJson5(writer, it.getType()); - }); + // 接口类型、枚举类型不序列化 + if (!psiClass.isInterface() && !psiClass.isEnum()) { + Arrays.stream(psiClass.getAllFields()) + // 非static、transient字段 + .filter(SpringWebPsiUtil::isValidFiled) + // 非注解标记字段 + .filter(it -> AnnotationUtil.findAnnotations(it, this.ignores).length == 0) + .sorted(Comparator.comparing(PsiField::getName)) + .forEach(it -> { + // 字段注释 + Optional.ofNullable(it.getDocComment()) + .map(PsiDocComment::getDescriptionElements) + .map(els -> + Arrays.stream(els) + .filter(e -> e instanceof PsiDocToken) + .map(PsiDocToken.class::cast) + .filter(SpringWebPsiUtil::isDocCommentData) + .map(e -> e.getText().trim()) + .collect(Collectors.joining("")) + ) + .filter(comment -> !comment.isEmpty()) + .ifPresent(comment -> COMMENT.accept(writer, comment)); + NAME.accept(writer, it.getName()); + // TODO 处理泛型和递归调用问题 + this.toJson5(writer, it.getType()); + }); + } END_OBJECT.accept(writer); } } diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java index e15ce52..d5b28fc 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java @@ -37,9 +37,6 @@ public class ApiDocSettingPo { @Tag("query-param-ignore-annotations") @XCollection List queryParamIgnoreAnnotations; - @Tag("body-ignore-types") - @XCollection - List bodyIgnoreTypes; @Tag("body-ignore-annotations") @XCollection List bodyIgnoreAnnotations; @@ -51,8 +48,7 @@ public class ApiDocSettingPo { this.template = new StringBuilder(ResourceUtil.readAsString("/api-doc-template.vm").replace("\r\n", "\n")); this.queryParamIgnoreTypes = new ArrayList<>(WebCopyConstants.QUERY_PARAM_IGNORE_TYPES); this.queryParamIgnoreAnnotations = new ArrayList<>(WebCopyConstants.QUERY_PARAM_IGNORE_ANNOTATIONS); - this.bodyIgnoreTypes = new ArrayList<>(WebCopyConstants.ALL_IGNORE_TYPES); - this.bodyIgnoreAnnotations = new ArrayList<>(); + this.bodyIgnoreAnnotations = new ArrayList<>(WebCopyConstants.FIELD_IGNORE_ANNOTATIONS); this.objects = ApiDocObjectSerialPo.defaultObjects(); } @@ -62,7 +58,6 @@ public class ApiDocSettingPo { new StringBuilder(this.template), new ArrayList<>(this.queryParamIgnoreTypes), new ArrayList<>(this.queryParamIgnoreAnnotations), - new ArrayList<>(this.bodyIgnoreTypes), new ArrayList<>(this.bodyIgnoreAnnotations), this.objects.stream().map(ApiDocObjectSerialPo::deepCopy).collect(Collectors.toList()) ); @@ -75,8 +70,6 @@ public class ApiDocSettingPo { this.queryParamIgnoreTypes.addAll(source.getQueryParamIgnoreTypes()); this.queryParamIgnoreAnnotations.clear(); this.queryParamIgnoreAnnotations.addAll(source.getQueryParamIgnoreAnnotations()); - this.bodyIgnoreTypes.clear(); - this.bodyIgnoreTypes.addAll(source.getBodyIgnoreTypes()); this.bodyIgnoreAnnotations.clear(); this.bodyIgnoreAnnotations.addAll(source.getBodyIgnoreAnnotations()); this.objects.clear(); @@ -120,7 +113,6 @@ public class ApiDocSettingPo { ApiDocSettingPo that = (ApiDocSettingPo) o; return Objects.equals(template.toString(), that.template.toString()) && - Objects.equals(bodyIgnoreTypes, that.bodyIgnoreTypes) && Objects.equals(queryParamIgnoreTypes, that.queryParamIgnoreTypes) && Objects.equals(queryParamIgnoreAnnotations, that.queryParamIgnoreAnnotations) && Objects.equals(this.bodyIgnoreAnnotations, that.bodyIgnoreAnnotations) && @@ -132,7 +124,7 @@ public class ApiDocSettingPo { return Objects.hash( template, queryParamIgnoreTypes, queryParamIgnoreAnnotations, - bodyIgnoreTypes, bodyIgnoreAnnotations, objects + bodyIgnoreAnnotations, objects ); } } -- Gitee From a311898262263c333f3e6ce2ae12e85350dbc61b Mon Sep 17 00:00:00 2001 From: PasseRR Date: Wed, 21 Jul 2021 18:26:02 +0800 Subject: [PATCH 30/41] =?UTF-8?q?api=E6=96=87=E6=A1=A3=E5=A4=8D=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../passerr/idea/plugins/spring/web/ApiDocConfigViews.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java index 514af79..5d99114 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java @@ -75,8 +75,8 @@ public abstract class ApiDocConfigViews { Arrays.asList( Pair.pair("Api模版", apiTemplatePanel(setting)), Pair.pair("查询参数", queryParamPanel(setting)), - Pair.pair("报文参数", bodyParamPanel(setting)), - Pair.pair("序列化配置", serialPanel(setting)) + Pair.pair("报文体", bodyParamPanel(setting)), + Pair.pair("序列化", serialPanel(setting)) ); } -- Gitee From a84530ec0981e3b5e57288365faeed6b5a457e70 Mon Sep 17 00:00:00 2001 From: PasseRR Date: Thu, 22 Jul 2021 09:59:46 +0800 Subject: [PATCH 31/41] =?UTF-8?q?=E5=BA=8F=E5=88=97=E5=8C=96=E5=B8=B8?= =?UTF-8?q?=E9=87=8F=E6=8F=90=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../spring/web/CopyMethodApiDocAction.java | 1 - .../web/{json5 => }/Json5Generator.java | 30 +++----- .../plugins/spring/web/SpringWebPsiUtil.java | 2 +- .../plugins/spring/web/WebCopyConstants.java | 76 +++++++++++++++++-- .../plugins/spring/web/json5/Json5Writer.java | 2 +- .../spring/web/po/ApiDocObjectSerialPo.java | 59 +------------- 6 files changed, 85 insertions(+), 85 deletions(-) rename src/main/java/com/github/passerr/idea/plugins/spring/web/{json5 => }/Json5Generator.java (89%) diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.java index 680f7d0..4ee71be 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.java @@ -1,6 +1,5 @@ package com.github.passerr.idea.plugins.spring.web; -import com.github.passerr.idea.plugins.spring.web.json5.Json5Generator; import com.github.passerr.idea.plugins.spring.web.po.ApiDocSettingPo; import com.intellij.codeInsight.AnnotationUtil; import com.intellij.openapi.actionSystem.AnActionEvent; diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/json5/Json5Generator.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/Json5Generator.java similarity index 89% rename from src/main/java/com/github/passerr/idea/plugins/spring/web/json5/Json5Generator.java rename to src/main/java/com/github/passerr/idea/plugins/spring/web/Json5Generator.java index f74da91..da5dd91 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/json5/Json5Generator.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/Json5Generator.java @@ -1,7 +1,6 @@ -package com.github.passerr.idea.plugins.spring.web.json5; +package com.github.passerr.idea.plugins.spring.web; -import com.github.passerr.idea.plugins.spring.web.AliasType; -import com.github.passerr.idea.plugins.spring.web.SpringWebPsiUtil; +import com.github.passerr.idea.plugins.spring.web.json5.Json5Writer; import com.github.passerr.idea.plugins.spring.web.po.ApiDocObjectSerialPo; import com.intellij.codeInsight.AnnotationUtil; import com.intellij.openapi.diagnostic.Logger; @@ -42,8 +41,13 @@ import java.util.stream.Collectors; * @Copyright(c) tellyes tech. inc. co.,ltd */ public class Json5Generator { - // 忽略注解 + /** + * 字段忽略注解 + */ private final Set ignores; + /** + * 字段序列化 + */ private final Map serials; private static final Logger LOG = Logger.getInstance(Json5Generator.class); private static final Consumer BEGIN_ARRAY = writer -> { @@ -102,12 +106,7 @@ public class Json5Generator { Map collect = originSerials.stream() .collect(Collectors.toMap(ApiDocObjectSerialPo::getType, it -> it, (o, n) -> n)); // 保证存在原型类型 - collect.putIfAbsent("boolean", new ApiDocObjectSerialPo("boolean", AliasType.BOOLEAN.getType(), "true")); - Arrays.asList("byte", "short", "char", "int") - .forEach(it -> collect.putIfAbsent(it, new ApiDocObjectSerialPo(it, AliasType.INT.getType(), "1024"))); - Arrays.asList("float", "double") - .forEach(it -> collect.putIfAbsent(it, new ApiDocObjectSerialPo(it, AliasType.FLOAT.getType(), "102.4"))); - collect.putIfAbsent("long", new ApiDocObjectSerialPo("long", AliasType.STRING.getType(), "2048")); + WebCopyConstants.PRIMITIVE_SERIALS.forEach(it -> collect.putIfAbsent(it.getType(), it.deepCopy())); this.serials = Collections.unmodifiableMap(collect); } @@ -119,19 +118,12 @@ public class Json5Generator { boolean isIgnore(PsiType psiType) { if (psiType instanceof PsiPrimitiveType) { // void类型忽略 - if ("void".equals(((PsiPrimitiveType) psiType).getName())) { - return true; - } - return false; + return void.class.getName().equals(((PsiPrimitiveType) psiType).getName()); } PsiClass resolve = ((PsiClassType) psiType).resolve(); - if (Objects.isNull(resolve)) { - return false; - } - // java.lang.Void类型忽略 - return CommonClassNames.JAVA_LANG_VOID.equals(resolve.getQualifiedName()); + return resolve != null && Void.class.getName().equals(resolve.getQualifiedName()); } String toJson5(PsiType psiType, String rootComment) { diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/SpringWebPsiUtil.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/SpringWebPsiUtil.java index 3e58507..f8f4fac 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/SpringWebPsiUtil.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/SpringWebPsiUtil.java @@ -106,7 +106,7 @@ public interface SpringWebPsiUtil { .map(e -> e.getText().trim()) .collect(Collectors.joining("")) ) - .filter(it -> !it.isEmpty()) + .filter(it -> !it.trim().isEmpty()) .orElse(null); } } diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/WebCopyConstants.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/WebCopyConstants.java index 5a17e0f..58e8e37 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/WebCopyConstants.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/WebCopyConstants.java @@ -1,7 +1,16 @@ package com.github.passerr.idea.plugins.spring.web; +import com.github.passerr.idea.plugins.spring.web.po.ApiDocObjectSerialPo; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; import java.util.Arrays; import java.util.Collections; +import java.util.Date; import java.util.List; /** @@ -10,23 +19,23 @@ import java.util.List; * @date 2021/06/30 19:07 * @Copyright(c) tellyes tech. inc. co.,ltd */ -public abstract class WebCopyConstants { +public interface WebCopyConstants { /** * 路径参数注解 */ - public static final String PATH_VARIABLE_ANNOTATION = "org.springframework.web.bind.annotation.PathVariable"; + String PATH_VARIABLE_ANNOTATION = "org.springframework.web.bind.annotation.PathVariable"; /** * 查询参数注解 */ - public static final String QUERY_PARAM_ANNOTATION = "org.springframework.web.bind.annotation.RequestParam"; + String QUERY_PARAM_ANNOTATION = "org.springframework.web.bind.annotation.RequestParam"; /** * 报文体参数注解 */ - public static final String BODY_ANNOTATION = "org.springframework.web.bind.annotation.RequestBody"; + String BODY_ANNOTATION = "org.springframework.web.bind.annotation.RequestBody"; /** * 查询参数忽略类型 */ - public static final List QUERY_PARAM_IGNORE_TYPES = Collections.unmodifiableList( + List QUERY_PARAM_IGNORE_TYPES = Collections.unmodifiableList( Arrays.asList( "org.springframework.ui.Model", "javax.servlet.http.HttpServletRequest", @@ -38,7 +47,7 @@ public abstract class WebCopyConstants { /** * 查询参数忽略注解 */ - public static final List QUERY_PARAM_IGNORE_ANNOTATIONS = Collections.unmodifiableList( + List QUERY_PARAM_IGNORE_ANNOTATIONS = Collections.unmodifiableList( Arrays.asList( "org.springframework.web.bind.annotation.CookieValue", "org.springframework.web.bind.annotation.RequestHeader", @@ -52,10 +61,63 @@ public abstract class WebCopyConstants { /** * json字段忽略注解 */ - public static final List FIELD_IGNORE_ANNOTATIONS = Collections.unmodifiableList( + List FIELD_IGNORE_ANNOTATIONS = Collections.unmodifiableList( Arrays.asList( "com.fasterxml.jackson.annotation.JsonIgnore", "com.google.gson.annotations.Expose" ) ); + /** + * 原生类型序列化 + */ + List PRIMITIVE_SERIALS = Collections.unmodifiableList( + Arrays.asList( + new ApiDocObjectSerialPo(boolean.class.getName(), AliasType.BOOLEAN.getType(), "true"), + new ApiDocObjectSerialPo(byte.class.getName(), AliasType.INT.getType(), "128"), + new ApiDocObjectSerialPo(short.class.getName(), AliasType.INT.getType(), "256"), + new ApiDocObjectSerialPo(char.class.getName(), AliasType.INT.getType(), "512"), + new ApiDocObjectSerialPo(int.class.getName(), AliasType.INT.getType(), "1024"), + new ApiDocObjectSerialPo(float.class.getName(), AliasType.FLOAT.getType(), "102.4"), + new ApiDocObjectSerialPo(double.class.getName(), AliasType.FLOAT.getType(), "204.8"), + new ApiDocObjectSerialPo(long.class.getName(), AliasType.STRING.getType(), "2048") + ) + ); + /** + * 包装类型序列化 + */ + List WRAPPED_SERIALS = Collections.unmodifiableList( + Arrays.asList( + new ApiDocObjectSerialPo(Boolean.class.getSimpleName(), AliasType.BOOLEAN.getType(), "true"), + new ApiDocObjectSerialPo(Byte.class.getSimpleName(), AliasType.INT.getType(), "128"), + new ApiDocObjectSerialPo(Short.class.getSimpleName(), AliasType.INT.getType(), "256"), + new ApiDocObjectSerialPo(Character.class.getSimpleName(), AliasType.INT.getType(), "512"), + new ApiDocObjectSerialPo(Integer.class.getSimpleName(), AliasType.INT.getType(), "1024"), + new ApiDocObjectSerialPo(Float.class.getSimpleName(), AliasType.FLOAT.getType(), "102.4"), + new ApiDocObjectSerialPo(Double.class.getSimpleName(), AliasType.FLOAT.getType(), "204.8"), + new ApiDocObjectSerialPo(Long.class.getSimpleName(), AliasType.STRING.getType(), "2048"), + new ApiDocObjectSerialPo(BigDecimal.class.getSimpleName(), AliasType.FLOAT.getType(), "409.2"), + new ApiDocObjectSerialPo(BigInteger.class.getSimpleName(), AliasType.STRING.getType(), "4092"), + new ApiDocObjectSerialPo(String.class.getSimpleName(), AliasType.STRING.getType(), "string"), + new ApiDocObjectSerialPo( + LocalTime.class.getSimpleName(), + AliasType.STRING.getType(), + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")) + ), + new ApiDocObjectSerialPo( + LocalDate.class.getSimpleName(), + AliasType.STRING.getType(), + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")) + ), + new ApiDocObjectSerialPo( + Date.class.getSimpleName(), + AliasType.STRING.getType(), + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + ), + new ApiDocObjectSerialPo( + LocalDateTime.class.getSimpleName(), + AliasType.STRING.getType(), + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + ) + ) + ); } diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/json5/Json5Writer.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/json5/Json5Writer.java index 13205d5..c42bb03 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/json5/Json5Writer.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/json5/Json5Writer.java @@ -127,7 +127,7 @@ import static com.github.passerr.idea.plugins.spring.web.json5.JsonScope.NONEMPT * Instances of this class are not thread safe. Calls that would result in a * malformed JSON string will fail with an {@link IllegalStateException}. */ -final class Json5Writer implements Closeable, Flushable { +public final class Json5Writer implements Closeable, Flushable { /** * From RFC 7159, "All Unicode characters may be placed within the * quotation marks except for the characters that must be escaped: diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocObjectSerialPo.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocObjectSerialPo.java index 48e0171..9406cd8 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocObjectSerialPo.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocObjectSerialPo.java @@ -1,6 +1,6 @@ package com.github.passerr.idea.plugins.spring.web.po; -import com.github.passerr.idea.plugins.spring.web.AliasType; +import com.github.passerr.idea.plugins.spring.web.WebCopyConstants; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Data; @@ -8,12 +8,7 @@ import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import lombok.experimental.FieldDefaults; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.format.DateTimeFormatter; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -52,56 +47,8 @@ public class ApiDocObjectSerialPo { */ static List defaultObjects() { List objects = new ArrayList<>(); - Arrays.asList("boolean", "java.lang.Boolean") - .forEach(it -> objects.add(new ApiDocObjectSerialPo(it, AliasType.BOOLEAN.getType(), "true"))); - - Arrays.asList( - "byte", "java.lang.Byte", - "short", "java.lang.Short", - "char", "java.lang.Character", - "int", "java.lang.Integer" - ).forEach(it -> objects.add(new ApiDocObjectSerialPo(it, AliasType.INT.getType(), "1024"))); - - Arrays.asList( - "float", "java.lang.Float", - "double", "java.lang.Double" - ).forEach(it -> objects.add(new ApiDocObjectSerialPo(it, AliasType.FLOAT.getType(), "102.4"))); - - Arrays.asList( - "long", - "java.lang.Long", - "java.math.BigInteger" - ).forEach(it -> objects.add(new ApiDocObjectSerialPo(it, AliasType.STRING.getType(), "2048"))); - - Arrays.asList( - "java.util.Date", - "java.time.LocalDateTime" - ).forEach(it -> - objects.add( - new ApiDocObjectSerialPo( - it, - AliasType.STRING.getType(), - LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) - ) - ) - ); - objects.add( - new ApiDocObjectSerialPo( - "java.time.LocalDate", - AliasType.STRING.getType(), - LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")) - ) - ); - objects.add( - new ApiDocObjectSerialPo( - "java.time.LocalTime", - AliasType.STRING.getType(), - LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")) - ) - ); - objects.add(new ApiDocObjectSerialPo("java.math.BigDecimal", AliasType.STRING.getType(), "204.8")); - objects.add(new ApiDocObjectSerialPo("java.lang.String", AliasType.STRING.getType(), "String")); - + WebCopyConstants.PRIMITIVE_SERIALS.stream().map(ApiDocObjectSerialPo::deepCopy).forEach(objects::add); + WebCopyConstants.WRAPPED_SERIALS.stream().map(ApiDocObjectSerialPo::deepCopy).forEach(objects::add); return objects; } -- Gitee From bd63d32af8df305a6fe87bd16d840bbe7c2eb93f Mon Sep 17 00:00:00 2001 From: PasseRR Date: Thu, 22 Jul 2021 11:07:27 +0800 Subject: [PATCH 32/41] =?UTF-8?q?=E6=B3=9B=E5=9E=8B=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugins/spring/web/Json5Generator.java | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/Json5Generator.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/Json5Generator.java index da5dd91..11b5bbd 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/Json5Generator.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/Json5Generator.java @@ -24,6 +24,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -31,6 +32,7 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.function.BiConsumer; +import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -49,6 +51,10 @@ public class Json5Generator { * 字段序列化 */ private final Map serials; + /** + * 调用次数记录 + */ + private final Map count; private static final Logger LOG = Logger.getInstance(Json5Generator.class); private static final Consumer BEGIN_ARRAY = writer -> { try { @@ -99,6 +105,8 @@ public class Json5Generator { LOG.error(e.getMessage(), e); } }; + private static final BiFunction COUNT_KEY = (t, f) -> + String.format("%s#%s", t.getCanonicalText(), f.getName()); Json5Generator(List originIgnores, List originSerials) { // object不能在校验范围 @@ -108,6 +116,7 @@ public class Json5Generator { // 保证存在原型类型 WebCopyConstants.PRIMITIVE_SERIALS.forEach(it -> collect.putIfAbsent(it.getType(), it.deepCopy())); this.serials = Collections.unmodifiableMap(collect); + this.count = new HashMap<>(); } /** @@ -214,8 +223,11 @@ public class Json5Generator { .filter(SpringWebPsiUtil::isValidFiled) // 非注解标记字段 .filter(it -> AnnotationUtil.findAnnotations(it, this.ignores).length == 0) + // 允许递归调用一次 + .filter(it -> this.count.getOrDefault(COUNT_KEY.apply(type, it), 0) < 1) .sorted(Comparator.comparing(PsiField::getName)) .forEach(it -> { + this.count.merge(COUNT_KEY.apply(type, it), 1, Integer::sum); // 字段注释 Optional.ofNullable(it.getDocComment()) .map(PsiDocComment::getDescriptionElements) @@ -230,8 +242,18 @@ public class Json5Generator { .filter(comment -> !comment.isEmpty()) .ifPresent(comment -> COMMENT.accept(writer, comment)); NAME.accept(writer, it.getName()); - // TODO 处理泛型和递归调用问题 - this.toJson5(writer, it.getType()); + PsiType fieldType = it.getType(); + // 存在泛型参数 + if (fieldType instanceof PsiClassReferenceType) { + PsiClass parameterType = ((PsiClassReferenceType) fieldType).resolve(); + if (parameterType instanceof PsiTypeParameter + && substitutionMap.containsKey(parameterType)) { + this.toJson5(writer, substitutionMap.get(parameterType)); + // 提前结束泛型参数处理 + return; + } + } + this.toJson5(writer, fieldType); }); } END_OBJECT.accept(writer); -- Gitee From a80629914ee13b4712f0643a8a50c2f19b3e444e Mon Sep 17 00:00:00 2001 From: PasseRR Date: Thu, 22 Jul 2021 11:20:35 +0800 Subject: [PATCH 33/41] =?UTF-8?q?=E6=97=A5=E5=BF=97=E5=A4=8D=E5=88=B6=20?= =?UTF-8?q?=E5=9C=A8=E9=80=89=E4=B8=AD=E5=86=85=E5=AE=B9=E4=B8=8D=E5=90=88?= =?UTF-8?q?=E6=B3=95=E6=98=AF=E8=8F=9C=E5=8D=95=E4=B8=8D=E5=8F=AF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugins/mybatis/MybatisLog2SqlAction.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/main/java/com/github/passerr/idea/plugins/mybatis/MybatisLog2SqlAction.java b/src/main/java/com/github/passerr/idea/plugins/mybatis/MybatisLog2SqlAction.java index 80afe31..1b90fe3 100644 --- a/src/main/java/com/github/passerr/idea/plugins/mybatis/MybatisLog2SqlAction.java +++ b/src/main/java/com/github/passerr/idea/plugins/mybatis/MybatisLog2SqlAction.java @@ -8,6 +8,7 @@ import com.intellij.openapi.editor.SelectionModel; import com.intellij.openapi.ide.CopyPasteManager; import com.intellij.util.ui.TextTransferable; import org.apache.commons.lang.StringUtils; +import org.jetbrains.annotations.NotNull; import java.util.Optional; @@ -35,4 +36,26 @@ public class MybatisLog2SqlAction extends AnAction { } } } + + @Override + public void update(@NotNull AnActionEvent e) { + String[] logs = + Optional.ofNullable(e.getData(PlatformDataKeys.EDITOR)) + .map(Editor::getSelectionModel) + .map(SelectionModel::getSelectedText) + .orElse(LogConstants.EMPTY) + .split(LogConstants.BREAK_LINE); + + int match = 0; + for (String log : logs) { + if (log.contains(LogConstants.PREFIX_SQL)) { + match++; + } else if (log.contains(LogConstants.PREFIX_PARAMS_WITHOUT_SPACE)) { + match++; + } + } + + // 选中内容合法 才允许日志解析 + e.getPresentation().setEnabled(match > 1); + } } -- Gitee From 3870394f9e121a7fd5d903798b2637f932ec1dde Mon Sep 17 00:00:00 2001 From: PasseRR Date: Thu, 22 Jul 2021 12:13:56 +0800 Subject: [PATCH 34/41] =?UTF-8?q?api=E6=96=87=E6=A1=A3=E6=8F=8F=E8=BF=B0?= =?UTF-8?q?=E6=A8=A1=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/api-doc-desc.html | 170 ++++++++++++++++----------- 1 file changed, 101 insertions(+), 69 deletions(-) diff --git a/src/main/resources/api-doc-desc.html b/src/main/resources/api-doc-desc.html index 3b0a7c4..7707f83 100644 --- a/src/main/resources/api-doc-desc.html +++ b/src/main/resources/api-doc-desc.html @@ -2,118 +2,150 @@ - +
中文Along with static text, code and comments, you can also use - predefined variables (listed below) that will then be expanded like macros into the corresponding - values.
- It is also possible to specify an arbitrary number of custom variables in the format - ${<VARIABLE_NAME>}. In this case, before the new - file is created, you will be prompted with a dialog where you can define particular values for all - custom variables.
- Using the #parse directive, you can include templates from the Includes - tab, by specifying the full name of the desired template as a parameter in quotation marks. - For example:
- #parse("File Header.java") -
+ + 关于rest接口内置的velocity参数,主要包括url、http方法、查询参数、路径参数、报文体、应答报文。 + 其中,查询参数及路径参数均为List类型,具体介绍如下。 + +
+


+查询参数、路径参数属性 + - + + + + - + + - - - + + - - - + + - - - + + - - +
Predefined variables will take the following values:属性名类型属性描述
- ${PACKAGE_NAME} + + name + string + 参数名  name of the package in which the new file is - created
- ${NAME} + + type + string + 参数java类型全名  name of the new file specified by you in the New - <TEMPLATE_NAME> dialog
- ${USER} + + alias + string + 参数类型别名  current user system login name
- ${DATE} + + desc + string + 参数描述  current system date
+
+预定义velocity参数描述 + + - + + - - + - + + + + + + + + + + + + - - - - - + + - - - + + - - - + + - - - + + - - - + + - - - + + - - - + +
- ${TIME} + + 参数名 + 类型 + 参数描述  current system time
- ${YEAR} + + ${method} + string + http方法类型若为指定则为UNKNOWN +
+ ${url} + string + rest方法路径 +
+ ${hasQueryParams} + boolean + 是否有查询参数  current year
- ${MONTH} + + ${queryParams}  current monthlist查询参数列表
- ${MONTH_NAME_SHORT} + + ${hasPathVariables}  first 3 letters of the current month name. Example: - Jan, Feb, etc.boolean是否有路径参数
- ${MONTH_NAME_FULL} + + ${pathVariables}  full name of the current month. Example: January, - February, etc.list路径参数列表
- ${DAY} + + ${hasBody}  current day of the monthboolean是否存在请求报文体
- ${HOUR} + + ${body}  current hourstring请求报文体json5序列化字符串包含字段注释
- ${MINUTE} + + ${hasResponse}  current minuteboolean是否存在应答报文体
- ${PROJECT_NAME} + + ${response}  the name of the current projectstring应答报文体json5序列化字符串包含字段注释

-- Gitee From 2c695c29c525e4249a6c2807a6de695c86b05836 Mon Sep 17 00:00:00 2001 From: PasseRR Date: Thu, 22 Jul 2021 12:42:13 +0800 Subject: [PATCH 35/41] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=A4=8D=E5=88=B6?= =?UTF-8?q?=E8=BF=94=E5=9B=9E=E7=B1=BB=E5=9E=8B=E4=B8=BAjson5=E7=9A=84?= =?UTF-8?q?=E5=8A=A8=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../spring/web/CopyReturnTypeAction.java | 49 +++++++++++++++++++ src/main/resources/META-INF/plugin.xml | 16 +++++- 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/github/passerr/idea/plugins/spring/web/CopyReturnTypeAction.java diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyReturnTypeAction.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyReturnTypeAction.java new file mode 100644 index 0000000..c6462d6 --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyReturnTypeAction.java @@ -0,0 +1,49 @@ +package com.github.passerr.idea.plugins.spring.web; + +import com.github.passerr.idea.plugins.spring.web.po.ApiDocSettingPo; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.ide.CopyPasteManager; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiMethod; +import com.intellij.util.ui.TextTransferable; +import org.jetbrains.annotations.NotNull; + +import java.util.Collections; +import java.util.Objects; +import java.util.Optional; + +/** + * 复制方法返回类型为json5 + * @author xiehai + * @date 2021/07/22 12:29 + * @Copyright(c) tellyes tech. inc. co.,ltd + */ +public class CopyReturnTypeAction extends BaseWebCopyAction { + @Override + public void actionPerformed(@NotNull AnActionEvent e) { + PsiMethod method = BaseWebCopyAction.method(e); + ApiDocSettingPo state = ApiDocStateComponent.getInstance().getState(); + if (Objects.isNull(state)) { + return; + } + + Optional.ofNullable( + Json5Generator.toJson5( + method.getReturnType(), + SpringWebPsiUtil.returnComment(method), + Collections.unmodifiableList(state.getBodyIgnoreAnnotations()), + Collections.unmodifiableList(state.getObjects()) + ) + ).ifPresent(it -> CopyPasteManager.getInstance().setContents(new TextTransferable(it))); + } + + @Override + public void update(AnActionEvent e) { + DataContext dataContext = e.getDataContext(); + PsiElement data = CommonDataKeys.PSI_ELEMENT.getData(dataContext); + // 方法上才有效 + e.getPresentation().setEnabled(data instanceof PsiMethod); + } +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 8ef9453..dbe096e 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -12,6 +12,14 @@ 2.0.0: +
    +
  • 从groovy改为java
  • +
  • 添加复制spring controller方法全路径的action
  • +
  • 添加复制spring controller方法为api文档的action
  • +
  • 添加复制方法返回类型为json5的action
  • +
  • 修改复制为mybatis日志为sql的动作 仅当选择内容包含
  • +

1.1.0:

  • differential input/output textarea
  • @@ -66,10 +74,16 @@ + + + - + Date: Thu, 22 Jul 2021 12:42:52 +0800 Subject: [PATCH 36/41] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=A4=8D=E5=88=B6?= =?UTF-8?q?=E8=BF=94=E5=9B=9E=E7=B1=BB=E5=9E=8B=E4=B8=BAjson5=E7=9A=84?= =?UTF-8?q?=E5=8A=A8=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/META-INF/plugin.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index dbe096e..d32a2ca 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -18,7 +18,7 @@
  • 添加复制spring controller方法全路径的action
  • 添加复制spring controller方法为api文档的action
  • 添加复制方法返回类型为json5的action
  • -
  • 修改复制为mybatis日志为sql的动作 仅当选择内容包含
  • +
  • 修改复制为mybatis日志为sql的动作 仅当选择内容包含Preparing:及Parameters:

1.1.0:

    -- Gitee From 714461b8d5c6b8674e21734727c953fdd72e1a47 Mon Sep 17 00:00:00 2001 From: PasseRR Date: Thu, 22 Jul 2021 12:43:25 +0800 Subject: [PATCH 37/41] =?UTF-8?q?=E7=89=88=E6=9C=AC=E5=8D=87=E7=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index f434679..2ba9c4b 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ group 'com.github.passerr' -version '1.3.0' +version '2.0.0' buildscript { repositories { -- Gitee From 6d95288d4217ff5d7421286dab63c8d9bbc3d945 Mon Sep 17 00:00:00 2001 From: PasseRR Date: Thu, 22 Jul 2021 12:46:53 +0800 Subject: [PATCH 38/41] =?UTF-8?q?=E7=89=88=E6=9C=AC=E5=8D=87=E7=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 2ba9c4b..d0a2bc8 100644 --- a/build.gradle +++ b/build.gradle @@ -34,7 +34,9 @@ tasks.withType(JavaCompile) { options.encoding = "UTF-8" } -//buildSearchableOptions.enabled = false +compileJava { + options.compilerArgs << '-Xlint:deprecation' +} dependencies { compileOnly group: 'org.projectlombok', name: 'lombok', version: '1.18.2' -- Gitee From efe3f6f4dc5542261bb4c0615d31b47f9e3b0b99 Mon Sep 17 00:00:00 2001 From: PasseRR Date: Thu, 22 Jul 2021 12:54:35 +0800 Subject: [PATCH 39/41] =?UTF-8?q?=E8=BF=87=E6=97=B6api=E6=B8=85=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../idea/plugins/camel/ToggleCamelCase.java | 14 +++++--------- .../idea/plugins/spring/web/ApiDocConfigViews.java | 4 ++-- .../passerr/idea/plugins/tool/ConvertType.java | 3 +-- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/github/passerr/idea/plugins/camel/ToggleCamelCase.java b/src/main/java/com/github/passerr/idea/plugins/camel/ToggleCamelCase.java index 61e2fd3..206e134 100644 --- a/src/main/java/com/github/passerr/idea/plugins/camel/ToggleCamelCase.java +++ b/src/main/java/com/github/passerr/idea/plugins/camel/ToggleCamelCase.java @@ -4,7 +4,6 @@ import com.intellij.codeInsight.actions.MultiCaretCodeInsightAction; import com.intellij.codeInsight.actions.MultiCaretCodeInsightActionHandler; import com.intellij.openapi.actionSystem.ActionGroup; import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.Result; import com.intellij.openapi.application.WriteAction; import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.editor.Caret; @@ -64,14 +63,11 @@ public class ToggleCamelCase extends MultiCaretCodeInsightAction { CommandProcessor.getInstance().executeCommand( project, () -> - new WriteAction() { - @Override - protected void run(@NotNull Result result) { - int start = editor.getSelectionModel().getSelectionStart(); - EditorModificationUtil.insertStringAtCaret(editor, newText); - editor.getSelectionModel().setSelection(start, start + newText.length()); - } - }.execute().throwException() + WriteAction.run(() -> { + int start = editor.getSelectionModel().getSelectionStart(); + EditorModificationUtil.insertStringAtCaret(editor, newText); + editor.getSelectionModel().setSelection(start, start + newText.length()); + }) , "CamelCase", ActionGroup.EMPTY_GROUP diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java index 5d99114..6cc0d66 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java @@ -14,8 +14,8 @@ import com.intellij.openapi.editor.EditorFactory; import com.intellij.openapi.editor.EditorSettings; import com.intellij.openapi.editor.colors.EditorColorsManager; import com.intellij.openapi.editor.colors.EditorColorsScheme; -import com.intellij.openapi.editor.event.DocumentAdapter; import com.intellij.openapi.editor.event.DocumentEvent; +import com.intellij.openapi.editor.event.DocumentListener; import com.intellij.openapi.editor.ex.EditorEx; import com.intellij.openapi.editor.ex.util.LayerDescriptor; import com.intellij.openapi.editor.ex.util.LayeredLexerEditorHighlighter; @@ -90,7 +90,7 @@ public abstract class ApiDocConfigViews { // 编辑模块 EditorFactory editorFactory = EditorFactory.getInstance(); Document document = editorFactory.createDocument(setting.getTemplate()); - document.addDocumentListener(new DocumentAdapter() { + document.addDocumentListener(new DocumentListener() { @Override public void documentChanged(DocumentEvent e) { setting.setStringTemplate(e.getDocument().getText()); diff --git a/src/main/java/com/github/passerr/idea/plugins/tool/ConvertType.java b/src/main/java/com/github/passerr/idea/plugins/tool/ConvertType.java index 55b8a3c..f3575b9 100644 --- a/src/main/java/com/github/passerr/idea/plugins/tool/ConvertType.java +++ b/src/main/java/com/github/passerr/idea/plugins/tool/ConvertType.java @@ -40,9 +40,8 @@ enum ConvertType { @Override void handle(RSyntaxTextArea input, RSyntaxTextArea output) { try { - JsonParser jsonParser = new JsonParser(); // 可以同时解析数组或者Object - JsonElement element = jsonParser.parse(input.getText()); + JsonElement element = JsonParser.parseString(input.getText()); Gson gson = new GsonBuilder().setPrettyPrinting().create(); output.setText(gson.toJson(element)); // 格式化成功后定位到第一行 -- Gitee From 6eb6864a948629314f990ed0ee76007872d27120 Mon Sep 17 00:00:00 2001 From: PasseRR Date: Thu, 22 Jul 2021 16:08:14 +0800 Subject: [PATCH 40/41] =?UTF-8?q?=E9=97=AE=E9=A2=98=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../spring/web/ApiDocStateComponent.java | 3 +- .../plugins/spring/web/Json5Generator.java | 4 +++ .../idea/plugins/spring/web/VelocityUtil.java | 5 +++ .../plugins/spring/web/WebCopyConstants.java | 33 ++++++++++--------- 4 files changed, 28 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocStateComponent.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocStateComponent.java index 0db4907..5c931db 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocStateComponent.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocStateComponent.java @@ -7,6 +7,7 @@ import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.components.State; import com.intellij.openapi.components.Storage; import com.intellij.util.xmlb.XmlSerializerUtil; +import org.jetbrains.annotations.NotNull; import java.util.Objects; @@ -29,7 +30,7 @@ public class ApiDocStateComponent implements PersistentStateComponent map) { VelocityContext context = new VelocityContext(map); StringWriter writer = new StringWriter(); + // 不打印velocity日志 + Velocity.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS, NullLogChute.class.getName()); + Velocity.init(); Velocity.evaluate(context, writer, "eval", template.toString()); return writer.toString(); diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/WebCopyConstants.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/WebCopyConstants.java index 58e8e37..acee072 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/WebCopyConstants.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/WebCopyConstants.java @@ -79,7 +79,9 @@ public interface WebCopyConstants { new ApiDocObjectSerialPo(int.class.getName(), AliasType.INT.getType(), "1024"), new ApiDocObjectSerialPo(float.class.getName(), AliasType.FLOAT.getType(), "102.4"), new ApiDocObjectSerialPo(double.class.getName(), AliasType.FLOAT.getType(), "204.8"), - new ApiDocObjectSerialPo(long.class.getName(), AliasType.STRING.getType(), "2048") + new ApiDocObjectSerialPo(long.class.getName(), AliasType.STRING.getType(), "2048"), + // 将string作为了基本类型 + new ApiDocObjectSerialPo(String.class.getName(), AliasType.STRING.getType(), "string") ) ); /** @@ -87,34 +89,33 @@ public interface WebCopyConstants { */ List WRAPPED_SERIALS = Collections.unmodifiableList( Arrays.asList( - new ApiDocObjectSerialPo(Boolean.class.getSimpleName(), AliasType.BOOLEAN.getType(), "true"), - new ApiDocObjectSerialPo(Byte.class.getSimpleName(), AliasType.INT.getType(), "128"), - new ApiDocObjectSerialPo(Short.class.getSimpleName(), AliasType.INT.getType(), "256"), - new ApiDocObjectSerialPo(Character.class.getSimpleName(), AliasType.INT.getType(), "512"), - new ApiDocObjectSerialPo(Integer.class.getSimpleName(), AliasType.INT.getType(), "1024"), - new ApiDocObjectSerialPo(Float.class.getSimpleName(), AliasType.FLOAT.getType(), "102.4"), - new ApiDocObjectSerialPo(Double.class.getSimpleName(), AliasType.FLOAT.getType(), "204.8"), - new ApiDocObjectSerialPo(Long.class.getSimpleName(), AliasType.STRING.getType(), "2048"), - new ApiDocObjectSerialPo(BigDecimal.class.getSimpleName(), AliasType.FLOAT.getType(), "409.2"), - new ApiDocObjectSerialPo(BigInteger.class.getSimpleName(), AliasType.STRING.getType(), "4092"), - new ApiDocObjectSerialPo(String.class.getSimpleName(), AliasType.STRING.getType(), "string"), + new ApiDocObjectSerialPo(Boolean.class.getName(), AliasType.BOOLEAN.getType(), "true"), + new ApiDocObjectSerialPo(Byte.class.getName(), AliasType.INT.getType(), "128"), + new ApiDocObjectSerialPo(Short.class.getName(), AliasType.INT.getType(), "256"), + new ApiDocObjectSerialPo(Character.class.getName(), AliasType.INT.getType(), "512"), + new ApiDocObjectSerialPo(Integer.class.getName(), AliasType.INT.getType(), "1024"), + new ApiDocObjectSerialPo(Float.class.getName(), AliasType.FLOAT.getType(), "102.4"), + new ApiDocObjectSerialPo(Double.class.getName(), AliasType.FLOAT.getType(), "204.8"), + new ApiDocObjectSerialPo(Long.class.getName(), AliasType.STRING.getType(), "2048"), + new ApiDocObjectSerialPo(BigDecimal.class.getName(), AliasType.FLOAT.getType(), "409.2"), + new ApiDocObjectSerialPo(BigInteger.class.getName(), AliasType.STRING.getType(), "4092"), new ApiDocObjectSerialPo( - LocalTime.class.getSimpleName(), + LocalTime.class.getName(), AliasType.STRING.getType(), LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")) ), new ApiDocObjectSerialPo( - LocalDate.class.getSimpleName(), + LocalDate.class.getName(), AliasType.STRING.getType(), LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")) ), new ApiDocObjectSerialPo( - Date.class.getSimpleName(), + Date.class.getName(), AliasType.STRING.getType(), LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) ), new ApiDocObjectSerialPo( - LocalDateTime.class.getSimpleName(), + LocalDateTime.class.getName(), AliasType.STRING.getType(), LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) ) -- Gitee From 6230f06722b9d8b72b97ba54350a7d9e0a2f8044 Mon Sep 17 00:00:00 2001 From: PasseRR Date: Fri, 23 Jul 2021 13:44:54 +0800 Subject: [PATCH 41/41] =?UTF-8?q?velocity=20=E9=97=AE=E9=A2=98=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 ++ .../spring/web/CopyMethodApiDocAction.java | 25 ++++++++++--------- .../spring/web/CopyReturnTypeAction.java | 2 +- .../plugins/spring/web/Json5Generator.java | 16 +++--------- .../idea/plugins/spring/web/VelocityUtil.java | 17 +++++++------ 5 files changed, 28 insertions(+), 34 deletions(-) diff --git a/build.gradle b/build.gradle index d0a2bc8..9d3ac66 100644 --- a/build.gradle +++ b/build.gradle @@ -38,6 +38,8 @@ compileJava { options.compilerArgs << '-Xlint:deprecation' } +buildSearchableOptions.enabled = false + dependencies { compileOnly group: 'org.projectlombok', name: 'lombok', version: '1.18.2' annotationProcessor group: 'org.projectlombok', name: 'lombok', version: '1.18.2' diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.java index 4ee71be..4c565dd 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.java @@ -127,17 +127,18 @@ public class CopyMethodApiDocAction extends BaseWebCopyAction { .map(PsiDocParamRef.class::cast) .findFirst() .map(PsiDocParamRef::getText) - .ifPresent(p -> - comments.put( - p, - Arrays.stream(it.getDataElements()) - .filter(e -> e instanceof PsiDocToken) - .map(PsiDocToken.class::cast) - .filter(SpringWebPsiUtil::isDocCommentData) - .map(e -> e.getText().trim()) - .collect(Collectors.joining("")) - ) - ) + .ifPresent(p -> { + String comment = Arrays.stream(it.getDataElements()) + .filter(e -> e instanceof PsiDocToken) + .map(PsiDocToken.class::cast) + .filter(SpringWebPsiUtil::isDocCommentData) + .map(e -> e.getText().trim()) + .collect(Collectors.joining("")); + + if (!comment.isEmpty()) { + comments.put(p, comment); + } + }) ) ); @@ -294,7 +295,7 @@ public class CopyMethodApiDocAction extends BaseWebCopyAction { return Json5Generator.toJson5( method.getReturnType(), - SpringWebPsiUtil.returnComment(method), + null, Collections.unmodifiableList(state.getBodyIgnoreAnnotations()), Collections.unmodifiableList(state.getObjects()) ); diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyReturnTypeAction.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyReturnTypeAction.java index c6462d6..ebb8475 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyReturnTypeAction.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyReturnTypeAction.java @@ -32,7 +32,7 @@ public class CopyReturnTypeAction extends BaseWebCopyAction { Optional.ofNullable( Json5Generator.toJson5( method.getReturnType(), - SpringWebPsiUtil.returnComment(method), + null, Collections.unmodifiableList(state.getBodyIgnoreAnnotations()), Collections.unmodifiableList(state.getObjects()) ) diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/Json5Generator.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/Json5Generator.java index 37d084d..0944b75 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/Json5Generator.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/Json5Generator.java @@ -4,7 +4,6 @@ import com.github.passerr.idea.plugins.spring.web.json5.Json5Writer; import com.github.passerr.idea.plugins.spring.web.po.ApiDocObjectSerialPo; import com.intellij.codeInsight.AnnotationUtil; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.psi.CommonClassNames; import com.intellij.psi.PsiArrayType; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiClassType; @@ -32,7 +31,6 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.function.BiConsumer; -import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -54,7 +52,7 @@ public class Json5Generator { /** * 调用次数记录 */ - private final Map count; + private final Map count; private static final Logger LOG = Logger.getInstance(Json5Generator.class); private static final Consumer BEGIN_ARRAY = writer -> { try { @@ -105,8 +103,6 @@ public class Json5Generator { LOG.error(e.getMessage(), e); } }; - private static final BiFunction COUNT_KEY = (t, f) -> - String.format("%s#%s", t.getCanonicalText(), f.getName()); Json5Generator(List originIgnores, List originSerials) { // object不能在校验范围 @@ -187,12 +183,6 @@ public class Json5Generator { return; } String className = psiClass.getQualifiedName(); - if (CommonClassNames.JAVA_LANG_OBJECT.equals(className)) { - BEGIN_OBJECT.accept(writer); - END_OBJECT.accept(writer); - return; - } - // 基本类型 if (serials.containsKey(className)) { ApiDocObjectSerialPo po = serials.get(className); @@ -228,10 +218,10 @@ public class Json5Generator { // 非注解标记字段 .filter(it -> AnnotationUtil.findAnnotations(it, this.ignores).length == 0) // 允许递归调用一次 - .filter(it -> this.count.getOrDefault(COUNT_KEY.apply(type, it), 0) < 1) + .filter(it -> this.count.getOrDefault(it, 0) < 2) .sorted(Comparator.comparing(PsiField::getName)) .forEach(it -> { - this.count.merge(COUNT_KEY.apply(type, it), 1, Integer::sum); + this.count.merge(it, 1, Integer::sum); // 字段注释 Optional.ofNullable(it.getDocComment()) .map(PsiDocComment::getDescriptionElements) diff --git a/src/main/java/com/github/passerr/idea/plugins/spring/web/VelocityUtil.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/VelocityUtil.java index c42def0..7b31163 100644 --- a/src/main/java/com/github/passerr/idea/plugins/spring/web/VelocityUtil.java +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/VelocityUtil.java @@ -1,9 +1,8 @@ package com.github.passerr.idea.plugins.spring.web; import org.apache.velocity.VelocityContext; -import org.apache.velocity.app.Velocity; -import org.apache.velocity.runtime.RuntimeConstants; -import org.apache.velocity.runtime.log.NullLogChute; +import org.apache.velocity.app.VelocityEngine; +import org.jetbrains.java.generate.velocity.VelocityFactory; import java.io.StringWriter; import java.util.Map; @@ -22,12 +21,14 @@ public interface VelocityUtil { * @return 替换后文本 */ static String format(StringBuilder template, Map map) { - VelocityContext context = new VelocityContext(map); StringWriter writer = new StringWriter(); - // 不打印velocity日志 - Velocity.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS, NullLogChute.class.getName()); - Velocity.init(); - Velocity.evaluate(context, writer, "eval", template.toString()); + VelocityEngine velocity = VelocityFactory.getVelocityEngine(); + velocity.evaluate( + new VelocityContext(map), + writer, + VelocityUtil.class.getName(), + template.toString() + ); return writer.toString(); } -- Gitee