diff --git a/build.gradle b/build.gradle index bd756bdffa2f9c2e058677256f90e557512862da..9d3ac66358be5770fcaf6811928ac36f3e7ad47a 100644 --- a/build.gradle +++ b/build.gradle @@ -1,42 +1,47 @@ group 'com.github.passerr' -version '1.2.0' +version '2.0.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: 'java' apply plugin: 'idea' -apply plugin: 'groovy' -apply plugin: 'org.jetbrains.intellij' +apply plugin: "org.jetbrains.intellij" repositories { + maven { + url 'https://maven.aliyun.com/repository/public' + } mavenCentral() } intellij { - // 不修改plugin.xml的idea-version - updateSinceUntilBuild false - //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" + updateSinceUntilBuild = false + version = '2020.1' + plugins = ['java'] } tasks.withType(JavaCompile) { options.encoding = "UTF-8" } +compileJava { + options.compilerArgs << '-Xlint:deprecation' +} + +buildSearchableOptions.enabled = false + dependencies { - compile group: 'org.codehaus.groovy', name: 'groovy-all', version: '2.4.6' - compile group: 'com.fifesoft', name: 'rsyntaxtextarea', version: '2.6.1' - compile group: 'com.google.code.gson', name: 'gson', version: '2.8.5' + 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' } \ 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 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000000000000000000000000000000000..622ab64a3cb60378cd29384961554c0b032c9368 --- /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 0000000000000000000000000000000000000000..fbd7c515832dab7b01092e80db76e5e03fe32d29 --- /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 0000000000000000000000000000000000000000..5093609d512a96947851d39fa2dc05df2af2248e --- /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/groovy/com/github/passerr/idea/plugins/BaseTableModel.groovy b/src/main/groovy/com/github/passerr/idea/plugins/BaseTableModel.groovy deleted file mode 100644 index e4dbb7329c2dd6326e41efc2d998c353f4e02a59..0000000000000000000000000000000000000000 --- 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 7db64d728306b377e85769589e36a4d6845006a6..0000000000000000000000000000000000000000 --- 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 f6c49b01d5a27a1d4fafa677419a598363368478..0000000000000000000000000000000000000000 --- 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/MybatisLog2SqlAction.groovy b/src/main/groovy/com/github/passerr/idea/plugins/mybatis/MybatisLog2SqlAction.groovy deleted file mode 100644 index 5d1f764573f66fffc9a24a22b3c2242391f03811..0000000000000000000000000000000000000000 --- 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/spring/web/ApiDocConfigView.groovy b/src/main/groovy/com/github/passerr/idea/plugins/spring/web/ApiDocConfigView.groovy deleted file mode 100644 index d3f52cc1b8523fc38d5388ac07a4a5f3fb8d8855..0000000000000000000000000000000000000000 --- 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 fc3af9ce08a8718eeaedb4d6653bd7e7b9eb6046..0000000000000000000000000000000000000000 --- 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 98b6905895514c5d2ef2f5f1097d7f4a0123d982..0000000000000000000000000000000000000000 --- 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 a05319494bfe36dcd832f4d20e158e9775f52706..0000000000000000000000000000000000000000 --- 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 a4d11137503b8bc99a49293604f260c37fca53a1..0000000000000000000000000000000000000000 --- 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/WebCopyConstants.groovy b/src/main/groovy/com/github/passerr/idea/plugins/spring/web/WebCopyConstants.groovy deleted file mode 100644 index 6314eb4c59ec2f594d7821c6991299d2c27e86ac..0000000000000000000000000000000000000000 --- a/src/main/groovy/com/github/passerr/idea/plugins/spring/web/WebCopyConstants.groovy +++ /dev/null @@ -1,127 +0,0 @@ -package com.github.passerr.idea.plugins.spring.web - -/** - * 复制相关常量 - * @date 2021/06/28 16:41 - * @Copyright (c) wisewe co.,ltd - * @author xiehai - */ -class WebCopyConstants { - /** - * 均不支持的类型 - */ - public static final List ALL_IGNORE_TYPES = [ - "java.lang.Object", - "java.util.Map" - ] - /** - * 查询参数忽略类型 - */ - public static final List QUERY_PARAM_IGNORE_TYPES = [ - "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) - } - - /** - * 路径参数注解 - */ - public static final String PATH_VARIABLE_ANNOTATION = "org.springframework.web.bind.annotation.PathVariable" - /** - * 查询参数注解 - */ - public static final List QUERY_PARAM_ANNOTATIONS = [ - "org.springframework.web.bind.annotation.RequestParam", - "org.springframework.web.bind.annotation.RequestAttribute" - ] - /** - * 查询参数忽略注解 - */ - public static final List QUERY_PARAM_IGNORE_ANNOTATIONS = [ - "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) - } - - /** - * 默认数据类型别名 - */ - 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 = [ - BOOLEAN_ALIAS, - INT_ALIAS, - FLOAT_ALIAS, - STRING_ALIAS, - DEFAULT_ALIAS - ] - /** - * 默认类型映射 - */ - public static final Map DEFAULT_ALIAS_MAPPINGS = [:] - static { - ["boolean", "java.lang.Boolean"].each { it -> DEFAULT_ALIAS_MAPPINGS.put(it, BOOLEAN_ALIAS) } - - [ - "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) } - - [ - "float", "java.lang.Float", - "double", "java.lang.Double" - ].each { it -> DEFAULT_ALIAS_MAPPINGS.put(it, FLOAT_ALIAS) } - - [ - "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" - ].each { it -> DEFAULT_ALIAS_MAPPINGS.put(it, STRING_ALIAS) } - } - - /** - * 默认文档模版 - */ - public static final String DEFAULT_TEMPLATE = """Hello - World""" - - private WebCopyConstants() { - - } -} 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 b5f9148fe97d8e03ccb3edf714d61092ca8a3372..0000000000000000000000000000000000000000 --- 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 acfe22ad6e0c15a127e80244d56a82b875e124d7..0000000000000000000000000000000000000000 --- 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/TextHandlerToolWindow.groovy b/src/main/groovy/com/github/passerr/idea/plugins/tool/TextHandlerToolWindow.groovy deleted file mode 100644 index 6df9d121a64aba8777fbc56db693eb478776c67c..0000000000000000000000000000000000000000 --- 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/java/com/github/passerr/idea/plugins/BaseTableModel.java b/src/main/java/com/github/passerr/idea/plugins/BaseTableModel.java new file mode 100644 index 0000000000000000000000000000000000000000..815f4eab400c09e2d143f8c7a1bdea2e7557bb8e --- /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.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 + */ +@FieldDefaults(level = AccessLevel.PRIVATE) +public class BaseTableModel extends AbstractTableModel implements ItemRemovable { + final List data; + final List headers; + + public 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().size(); + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + return this.columns().get(columnIndex).apply(this.data.get(rowIndex)); + } + + public T getRow(int rowIndex) { + return this.data.get(rowIndex); + } + + /** + * 列属性 + * @return 列 + */ + protected List> columns() { + return Collections.singletonList(it -> it); + } +} 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 0000000000000000000000000000000000000000..665bb7d92e8e1b1c618a79b289e3b157ffa7fafd --- /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/IdeaJbTable.java b/src/main/java/com/github/passerr/idea/plugins/IdeaJbTable.java new file mode 100644 index 0000000000000000000000000000000000000000..544c76fe3f51897b8ec0b11b7c2c68e4128af45e --- /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 0000000000000000000000000000000000000000..f67a2d0cb261e02595df9f0c112aa9994ecf49d5 --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/IdeaPanelWithButtons.java @@ -0,0 +1,34 @@ +package com.github.passerr.idea.plugins; + +import com.intellij.ui.PanelWithButtons; +import org.jetbrains.annotations.Nullable; + +import javax.swing.JButton; + +/** + * {@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 { + 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/NotificationThread.java b/src/main/java/com/github/passerr/idea/plugins/NotificationThread.java new file mode 100644 index 0000000000000000000000000000000000000000..5348f1ce8aa6a92bd13dfc1da0a9ce969396a6ff --- /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; + + public NotificationThread(Notification notification) { + // 默认4秒关闭弹窗 + this(notification, 4); + } + + public 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 0000000000000000000000000000000000000000..206e134742d95fe3a742a9a9204636b308d40714 --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/camel/ToggleCamelCase.java @@ -0,0 +1,120 @@ +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.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, + () -> + WriteAction.run(() -> { + int start = editor.getSelectionModel().getSelectionStart(); + EditorModificationUtil.insertStringAtCaret(editor, newText); + editor.getSelectionModel().setSelection(start, start + newText.length()); + }) + , + "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/groovy/com/github/passerr/idea/plugins/mybatis/LogConstants.groovy b/src/main/java/com/github/passerr/idea/plugins/mybatis/LogConstants.java similarity index 51% rename from src/main/groovy/com/github/passerr/idea/plugins/mybatis/LogConstants.groovy rename to src/main/java/com/github/passerr/idea/plugins/mybatis/LogConstants.java index 0c245dfa16020e1fde8c21e253241c23620ceb14..5bff7ab0f7a074a394d6c45c7ad87bc5441dda80 100644 --- a/src/main/groovy/com/github/passerr/idea/plugins/mybatis/LogConstants.groovy +++ b/src/main/java/com/github/passerr/idea/plugins/mybatis/LogConstants.java @@ -1,58 +1,61 @@ -package com.github.passerr.idea.plugins.mybatis +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 + * @Copyright (c) gome inc Gome Co.,LTD */ interface LogConstants { + /** + * 空格 + */ + String SPACE = " "; /** * sql语句前缀 */ - String PREFIX_SQL = "Preparing: " + String PREFIX_SQL = "Preparing: "; /** * 参数占位符 */ - String PARAM_PLACEHOLDER = "\\?" + String PARAM_PLACEHOLDER = "\\?"; /** * 换行符 */ - String BREAK_LINE = "\n" + String BREAK_LINE = "\n"; /** * 无sql参数前缀 */ - String PREFIX_PARAMS_WITHOUT_SPACE = "Parameters:" + String PREFIX_PARAMS_WITHOUT_SPACE = "Parameters:"; /** * sql参数前缀 */ - String PREFIX_PARAMS = PREFIX_PARAMS_WITHOUT_SPACE + SPACE + String PREFIX_PARAMS = PREFIX_PARAMS_WITHOUT_SPACE + SPACE; /** * 参数值分隔符 */ - String PARAM_SEPARATOR = ", " + String PARAM_SEPARATOR = ", "; /** * 左括号 */ - String LEFT_BRACKET = "(" + String LEFT_BRACKET = "("; /** * 右括号 */ - String RIGHT_BRACKET = ")" + String RIGHT_BRACKET = ")"; /** * 空字符串 */ - String EMPTY = "" + String EMPTY = ""; /** * null值 */ - String NULL = "null" - /** - * 空格 - */ - String SPACE = " " + String NULL = "null"; /** * 不需要加单引号的类型 */ - List NON_QUOTED_TYPES = Arrays.asList("Integer", "Long", "Double", "Float", "Boolean") -} \ No newline at end of file + List NON_QUOTED_TYPES = Arrays.asList("Integer", "Long", "Double", "Float", "Boolean"); +} diff --git a/src/main/groovy/com/github/passerr/idea/plugins/mybatis/LogParser.groovy b/src/main/java/com/github/passerr/idea/plugins/mybatis/LogParser.java similarity index 46% rename from src/main/groovy/com/github/passerr/idea/plugins/mybatis/LogParser.groovy rename to src/main/java/com/github/passerr/idea/plugins/mybatis/LogParser.java index 0f98cb72837cd47d76aea70cc3d422440709c3e4..1ca264d8f2d1f4d1c88209aa0520bd3e62e325fb 100644 --- a/src/main/groovy/com/github/passerr/idea/plugins/mybatis/LogParser.groovy +++ b/src/main/java/com/github/passerr/idea/plugins/mybatis/LogParser.java @@ -1,36 +1,58 @@ -package com.github.passerr.idea.plugins.mybatis +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 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.* +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 xiehai1 - * @date 2018/11/08 11:49 - * @Copyright ( c ) gome inc Gome Co.,LTD + * @author xiehai + * @date 2021/07/01 14:44 + * @Copyright(c) tellyes tech. inc. co.,ltd */ -class LogParser { - private LogParser() { - +public class LogParser { + /** + * 日志解析为美化后的sql语句 + * @param log 日志内容 + * @return sql + */ + public static String toBeautifulSql(String log) { + return SqlFormatter.format(toSql(log)); } + /** * 日志解析为可执行的sql语句 * @param log 日志内容 * @return sql */ - static String toSql(String log) { - String sqlLine = null, valueLine = null + 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 + sqlLine = line; } else if (line.contains(PREFIX_PARAMS)) { - valueLine = line + valueLine = line; } else if (line.contains(PREFIX_PARAMS_WITHOUT_SPACE)) { // 没有参数的sql 自动补齐一个空格 - valueLine = line + SPACE + valueLine = line + SPACE; } } @@ -44,9 +66,9 @@ class LogParser { "selected log without \"Preparing:\" line, nothing will send to clipboard!", NotificationType.WARNING ) - ).start() + ).start(); - return EMPTY + return EMPTY; } else if (Objects.isNull(valueLine)) { // 提示信息 new NotificationThread( @@ -56,34 +78,34 @@ class LogParser { "selected log without \"Parameters:\" line, nothing will send to clipboard!", NotificationType.WARNING ) - ).start() + ).start(); - return EMPTY + return EMPTY; } // 带占位符的sql - int sqlPrefixIndex = sqlLine.indexOf(PREFIX_SQL) - String originSql = sqlLine.substring(sqlPrefixIndex + PREFIX_SQL.length(), sqlLine.length()) + 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(), valueLine.length()) + int paramPrefixIndex = valueLine.indexOf(PREFIX_PARAMS); + String paramValues = valueLine.substring(paramPrefixIndex + PREFIX_PARAMS.length()); - List originSqlSections = originSql.split(PARAM_PLACEHOLDER) - List paramValuesSections = paramValues.split(PARAM_SEPARATOR) - int i = 0 - StringBuilder sb = new StringBuilder() + 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++ + sb.append(originSqlSections.get(i)); + sb.append(parseParam(paramValuesSections.get(i))); + i++; } while (originSqlSections.size() > i) { - sb.append(originSqlSections.get(i)) - i++ + sb.append(originSqlSections.get(i)); + i++; } - sb.toString() + return sb.toString(); } /** @@ -93,23 +115,23 @@ class LogParser { */ private static String parseParam(String paramValue) { // 如果是空字符串直接返回 - if (paramValue.size() == 0) { - return EMPTY + if (paramValue.length() == 0) { + return EMPTY; } // 如果是null 直接返回null - if (paramValue.trim() == NULL) { - return NULL + if (paramValue.trim().equals(NULL)) { + return NULL; } // 括号的索引 - int lastLeftBracketIndex = paramValue.lastIndexOf(LEFT_BRACKET) - int lastRightBracketIndex = paramValue.lastIndexOf(RIGHT_BRACKET) + int lastLeftBracketIndex = paramValue.lastIndexOf(LEFT_BRACKET); + int lastRightBracketIndex = paramValue.lastIndexOf(RIGHT_BRACKET); // 参数值 - String param = paramValue.substring(0, lastLeftBracketIndex) + String param = paramValue.substring(0, lastLeftBracketIndex); // 参数类型 - String type = paramValue.substring(lastLeftBracketIndex + 1, lastRightBracketIndex) + String type = paramValue.substring(lastLeftBracketIndex + 1, lastRightBracketIndex); - return NON_QUOTED_TYPES.contains(type) ? param : String.format("'%s'", param) + 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 0000000000000000000000000000000000000000..1b90fe3424a46c4b0f95a14cb88c194106148975 --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/mybatis/MybatisLog2SqlAction.java @@ -0,0 +1,61 @@ +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 org.jetbrains.annotations.NotNull; + +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))); + } + } + } + + @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); + } +} diff --git a/src/main/groovy/com/github/passerr/idea/plugins/mybatis/SqlFormatter.groovy b/src/main/java/com/github/passerr/idea/plugins/mybatis/SqlFormatter.java similarity index 33% rename from src/main/groovy/com/github/passerr/idea/plugins/mybatis/SqlFormatter.groovy rename to src/main/java/com/github/passerr/idea/plugins/mybatis/SqlFormatter.java index 85b358858ea93f1bf22f60e5f91f70af1b429e8b..62f5b27da119667c65aef7bdddec5e8a61f18eba 100644 --- a/src/main/groovy/com/github/passerr/idea/plugins/mybatis/SqlFormatter.groovy +++ b/src/main/java/com/github/passerr/idea/plugins/mybatis/SqlFormatter.java @@ -1,361 +1,364 @@ -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 + * Hibernate, Relational Persistence for Idiomatic Java * - * it imported org.hibernate.internal.util.StringHelper, but it doesn't really need that. + * 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 org.hibernate.internal.util.StringHelper; +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 */ -class SqlFormatter { - +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() + 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") + 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 = " " - // MOD: Make initial indent zero - private static final String INITIAL = /*System.lineSeparator() + INDENT_STRING*/ "" + private static final String INDENT_STRING = " "; + private static final String INITIAL = System.lineSeparator() + INDENT_STRING; - static String format(String source) { - return new FormatProcess(source).perform() + 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() - - // MOD: Make BOL indent zero - int indent = /*1*/ 0 - - StringBuilder result = new StringBuilder() - StringTokenizer tokens - String lastToken - String token - String lcToken - - FormatProcess(String sql) { + 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, - "()+*/-=<>'`\"[]," + /*StringHelper.*/WHITESPACE, + "()+*/-=<>'`\"[]," + WHITESPACE, true - ) + ); } - String perform() { + public String perform() { - result.append(INITIAL) + 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 + token = tokens.nextToken(); + lcToken = token.toLowerCase(Locale.ROOT); + + if ("'".equals(token)) { + String t; + do { + t = tokens.nextToken(); + token += t; } - } else if ("\"" == token) { - String t - while ("\"" != t && tokens.hasMoreTokens()){ - 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 ("[" == token) { - String t - while ("]" != t && tokens.hasMoreTokens()){ - t = tokens.nextToken() - token += t + else if ("[".equals(token)) { + String t; + do { + t = tokens.nextToken(); + token += t; } + while (!"]".equals(t) && tokens.hasMoreTokens()); } - if (afterByOrSetOrFromOrSelect && "," == token) { - commaAfterByOrFromOrSelect() - } else if (afterOn && "," == token) { - commaAfterOn() - } else if ("(" == token) { - openParen() - } else if (")" == token) { - closeParen() + 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() + beginNewClause(); } else if (END_CLAUSES.contains(lcToken)) { - endNewClause() - } else if ("select" == lcToken) { - select() + endNewClause(); + } else if ("select".equals(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 + 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() + logical(); } else if (isWhitespace(token)) { - white() + white(); } else { - misc() + misc(); } if (!isWhitespace(token)) { - lastToken = lcToken + lastToken = lcToken; } } - return result.toString() + return result.toString(); } private void commaAfterOn() { - out() - indent-- - newline() - afterOn = false - afterByOrSetOrFromOrSelect = true + out(); + indent--; + newline(); + afterOn = false; + afterByOrSetOrFromOrSelect = true; } private void commaAfterByOrFromOrSelect() { - out() - newline() + out(); + newline(); } private void logical() { - if ("end" == lcToken) { - indent-- + if ("end".equals(lcToken)) { + indent--; } - newline() - out() - beginLine = false + newline(); + out(); + beginLine = false; } private void on() { - indent++ - afterOn = true - newline() - out() - beginLine = false + indent++; + afterOn = true; + newline(); + out(); + beginLine = false; } private void misc() { - out() - if ("between" == lcToken) { - afterBetween = true + out(); + if ("between".equals(lcToken)) { + afterBetween = true; } if (afterInsert) { - newline() - afterInsert = false + newline(); + afterInsert = false; } else { - beginLine = false - if ("case" == lcToken) { - indent++ + beginLine = false; + if ("case".equals(lcToken)) { + indent++; } } } private void white() { if (!beginLine) { - result.append(" ") + result.append(" "); } } private void updateOrInsertOrDelete() { - out() - indent++ - beginLine = false - if ("update" == lcToken) { - newline() + out(); + indent++; + beginLine = false; + if ("update".equals(lcToken)) { + newline(); } - if ("insert" == lcToken) { - afterInsert = true + if ("insert".equals(lcToken)) { + afterInsert = true; } } private void select() { - out() - indent++ - newline() - parenCounts.addLast(parensSinceSelect) - afterByOrFromOrSelects.addLast(afterByOrSetOrFromOrSelect) - parensSinceSelect = 0 - afterByOrSetOrFromOrSelect = true + out(); + indent++; + newline(); + parenCounts.addLast(parensSinceSelect); + afterByOrFromOrSelects.addLast(afterByOrSetOrFromOrSelect); + parensSinceSelect = 0; + afterByOrSetOrFromOrSelect = true; } private void out() { - result.append(token) + result.append(token); } private void endNewClause() { if (!afterBeginBeforeEnd) { - indent-- + indent--; if (afterOn) { - indent-- - afterOn = false + indent--; + afterOn = false; } - newline() + newline(); } - out() - if ("union" != lcToken) { - indent++ + out(); + if (!"union".equals(lcToken)) { + indent++; } - newline() - afterBeginBeforeEnd = false - afterByOrSetOrFromOrSelect = "by" == lcToken || "set" == lcToken || "from" == lcToken + newline(); + afterBeginBeforeEnd = false; + afterByOrSetOrFromOrSelect = "by".equals(lcToken) + || "set".equals(lcToken) + || "from".equals(lcToken); } private void beginNewClause() { if (!afterBeginBeforeEnd) { if (afterOn) { - indent-- - afterOn = false + indent--; + afterOn = false; } - indent-- - newline() + indent--; + newline(); } - out() - beginLine = false - afterBeginBeforeEnd = true + out(); + beginLine = false; + afterBeginBeforeEnd = true; } private void values() { - indent-- - newline() - out() - indent++ - newline() - afterValues = true + indent--; + newline(); + out(); + indent++; + newline(); + afterValues = true; } private void closeParen() { - parensSinceSelect-- + parensSinceSelect--; if (parensSinceSelect < 0) { - indent-- - parensSinceSelect = parenCounts.removeLast() - afterByOrSetOrFromOrSelect = afterByOrFromOrSelects.removeLast() + indent--; + parensSinceSelect = parenCounts.removeLast(); + afterByOrSetOrFromOrSelect = afterByOrFromOrSelects.removeLast(); } if (inFunction > 0) { - inFunction-- - out() + inFunction--; + out(); } else { if (!afterByOrSetOrFromOrSelect) { - indent-- - newline() + indent--; + newline(); } - out() + out(); } - beginLine = false + beginLine = false; } private void openParen() { if (isFunctionName(lastToken) || inFunction > 0) { - inFunction++ + inFunction++; } - beginLine = false + beginLine = false; if (inFunction > 0) { - out() + out(); } else { - out() + out(); if (!afterByOrSetOrFromOrSelect) { - indent++ - newline() - beginLine = true + indent++; + newline(); + beginLine = true; } } - parensSinceSelect++ + parensSinceSelect++; } private static boolean isFunctionName(String tok) { if (tok == null || tok.length() == 0) { - return false + return false; } - final char begin = tok.charAt(0) - final boolean isIdentifier = Character.isJavaIdentifierStart(begin) || '"' as char == begin + 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) + !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) + return WHITESPACE.contains(token); } private void newline() { - result.append(System.lineSeparator()) + result.append(System.lineSeparator()); for (int i = 0; i < indent; i++) { - result.append(INDENT_STRING) + result.append(INDENT_STRING); } - beginLine = true + beginLine = true; } } + } \ No newline at end of file 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 0000000000000000000000000000000000000000..4546f1a1b1db4d33e56c0443480c5ca1ed0ed679 --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/AliasType.java @@ -0,0 +1,82 @@ +package com.github.passerr.idea.plugins.spring.web; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; + +import java.util.Arrays; + +/** + * 别名类型 + * @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 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/ApiDocConfigViews.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java new file mode 100644 index 0000000000000000000000000000000000000000..6cc0d66fb1bd9f157796900d1f8456a4f423a03a --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigViews.java @@ -0,0 +1,589 @@ +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; +import com.github.passerr.idea.plugins.spring.web.highlight.TemplateHighlighter; +import com.github.passerr.idea.plugins.spring.web.po.ApiDocObjectSerialPo; +import com.github.passerr.idea.plugins.spring.web.po.ApiDocSettingPo; +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.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; +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; +import com.intellij.ui.SeparatorFactory; +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.JComboBox; +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.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; + +/** + * 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 List> panels(ApiDocSettingPo setting) { + return + Arrays.asList( + Pair.pair("Api模版", apiTemplatePanel(setting)), + Pair.pair("查询参数", queryParamPanel(setting)), + Pair.pair("报文体", bodyParamPanel(setting)), + Pair.pair("序列化", serialPanel(setting)) + ); + } + + /** + * api模版视图 + * @param setting {@link ApiDocSettingPo} + * @return {@link JPanel} + */ + 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 DocumentListener() { + @Override + public void documentChanged(DocumentEvent e) { + setting.setStringTemplate(e.getDocument().getText()); + } + }); + Editor editor = editorFactory.createEditor(document); + 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); + ((EditorEx) editor).setHighlighter(createVelocityHighlight()); + + 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.setEditorKit(UIUtil.getHTMLEditorKit()); + desc.addHyperlinkListener(new BrowserHyperlinkListener()); + desc.setText(ResourceUtil.readAsString("/api-doc-desc.html")); + desc.setCaretPosition(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( + descriptionPanel, + 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<>( + 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 -> + 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 -> + 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("删除") + .disableUpDownActions() + .createPanel(); + } + }; + PanelWithButtons bottom = new IdeaPanelWithButtons("忽略注解:") { + @Override + protected JComponent createMainComponent() { + 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 -> + 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 -> + 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("删除") + .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; + } + + /** + * 报文参数忽略类型设置 + * @param setting {@link ApiDocSettingPo} + * @return {@link JPanel} + */ + private static JPanel bodyParamPanel(ApiDocSettingPo setting) { + JPanel panel = new JPanel(new GridBagLayout()); + 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( + bottom, + 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 serialPanel(ApiDocSettingPo setting) { + JPanel panel = new JPanel(new GridBagLayout()); + PanelWithButtons top = new IdeaPanelWithButtons("") { + @Override + protected JComponent createMainComponent() { + BaseTableModel model = new BaseTableModel( + Arrays.asList("类型", "别名", "序列化默认值"), setting.getObjects()) { + @Override + protected List> columns() { + return + Arrays.asList( + ApiDocObjectSerialPo::getType, + ApiDocObjectSerialPo::getAlias, + ApiDocObjectSerialPo::getValue + ); + } + }; + JBTable table = new IdeaJbTable(model); + // 弹出层构建器 + 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, + 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()); + dialog.onChange(); + } + }); + 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 ComboBox<>( + Arrays.stream(AliasType.values()) + .map(AliasType::getType) + .toArray(String[]::new) + ); + aliasCombobox.addItemListener(e -> { + if (e.getStateChange() == ItemEvent.SELECTED) { + s.setAlias((String) e.getItem()); + } + dialog.onChange(); + }); + + 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; + 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()); + dialog.onChange(); + } + }); + valueField.setPreferredSize(new Dimension(300, oldPreferredSize.height)); + gb.gridx = 1; + gb.gridwidth = GridBagConstraints.REMAINDER; + gb.weightx = 1; + p.add(valueField, gb); + dialog.onChange(); + + return p; + }; + + return + ToolbarDecorator.createDecorator(table) + .setAddAction(it -> + new IdeaDialog(panel) + .title("新增序列化") + .value(new ApiDocObjectSerialPo()) + .okAction(setting.getObjects()::add) + .changePredicate(ApiDocObjectSerialPo::isOk) + .componentFunction(function) + .doInit() + .showAndGet() + ) + .setAddActionName("新增") + .setEditAction(it -> + new IdeaDialog(panel) + .title("编辑序列化") + .value(model.getRow(table.getSelectedRow())) + .okAction(t -> setting.getObjects().set(table.getSelectedRow(), t)) + .changePredicate(ApiDocObjectSerialPo::isOk) + .componentFunction(function) + .doInit() + .showAndGet() + ) + .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; + } + + 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); + 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/ApiDocConfigurable.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigurable.java new file mode 100644 index 0000000000000000000000000000000000000000..d4d1969609e5e8c790e5b4fd7929b9defe607667 --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocConfigurable.java @@ -0,0 +1,114 @@ +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.annotation.Generated; +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 = this.source.deepCopy(); + } + + @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; + } + // 切换tab自动保存 + if (this.isModified()) { + this.apply(); + } + String tab = selectedInfo.getText(); + panels.stream() + .filter(it -> Objects.equals(it.getFirst(), tab)) + .findFirst() + .ifPresent(it -> { + it.getSecond().validate(); + it.getSecond().repaint(); + }); + }); + + tabbedPanel.setSelectedIndex(0); + + return tabbedPanel.getComponent(); + } + + @Override + public boolean isModified() { + return !Objects.equals(this.source, this.copy); + } + + @Override + public void apply() { + this.source.shallowCopy(this.copy); + } + + @Override + public void reset() { + this.copy.shallowCopy(this.source); + } + + @Generated({}) + @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 0000000000000000000000000000000000000000..5c931dbafd4608cd20b22bf75bacdda6268033b0 --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/ApiDocStateComponent.java @@ -0,0 +1,56 @@ +package com.github.passerr.idea.plugins.spring.web; + +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; +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; + +/** + * 配置持久化组件 + * @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(@NotNull ApiDocSettingPo state) { + XmlSerializerUtil.copyBean(state, this.apiDocSettingPo); + } + + /** + * 别名获取 + * @param type 类型全称 + * @return 别名 + */ + String alias(String type) { + return + this.apiDocSettingPo.getObjects() + .stream() + .filter(it -> Objects.equals(it.getType(), type)) + .map(ApiDocObjectSerialPo::getAlias) + .findFirst() + // 默认设置为字符串 + .orElseGet(AliasType.STRING::getType); + } + + 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 new file mode 100644 index 0000000000000000000000000000000000000000..6efcc7364cf3b51b685f5f893f2ae875bda38327 --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/BaseWebCopyAction.java @@ -0,0 +1,76 @@ +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; + +/** + * spring web基础action + * @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/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 0000000000000000000000000000000000000000..4c565dd27fadddcfc92ae82fc843c34fbab1ee75 --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/CopyMethodApiDocAction.java @@ -0,0 +1,327 @@ +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.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; +import java.util.Collections; +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; +import java.util.stream.Stream; + +import static com.github.passerr.idea.plugins.spring.web.AliasType.UNKNOWN_ALIAS; + +/** + * 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(@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.put("method", httpMethod); + + // 路径参数列表 + List pathVariables = pathVariables(method, comments, state); + map.put("hasPathVariables", !pathVariables.isEmpty()); + map.put("pathVariables", pathVariables); + + // 查询参数列表 + List queryParams = queryParams(method, comments, state); + map.put("hasQueryParams", !queryParams.isEmpty()); + map.put("queryParams", queryParams); + // body + 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))); + } + + /** + * 获取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 {@link Map} + */ + private static Map comments(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(it -> "param".equals(it.getName())) + .forEach(it -> + Arrays.stream(it.getDataElements()) + .filter(e -> e instanceof PsiDocParamRef) + .map(PsiDocParamRef.class::cast) + .findFirst() + .map(PsiDocParamRef::getText) + .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); + } + }) + ) + ); + + return comments; + } + + /** + * 获取接口路径参数 + * @param method {@link PsiMethod} + * @param comments 方法注释 + * @param state 配置状态 + * @return 路径参数列表 + */ + private static List pathVariables(PsiMethod method, Map comments, ApiDocSettingPo state) { + return + Arrays.stream(method.getParameterList().getParameters()) + .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 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::getName), + type, + alias, + comments.getOrDefault(it.getName(), it.getName()) + ); + } + + return null; + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + /** + * 查询参数注解 + * @param method {@link PsiMethod} + * @param comments 方法注释 + * @param state 配置状态 + * @return {@link List} + */ + private static List queryParams(PsiMethod method, Map comments, ApiDocSettingPo state) { + return + Arrays.stream(method.getParameterList().getParameters()) + .filter(it -> + // 排除接口类型参数、忽略类型、带忽略类型注解的参数 + 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 = clazz.getQualifiedName(); + String alias = state.alias(type); + // 对象类型 + if (UNKNOWN_ALIAS.equals(alias)) { + return + Arrays.stream(clazz.getAllFields()) + .filter(SpringWebPsiUtil::isValidFiled) + // 查询参数只遍历一层 且忽略掉这层的未知类型 + .filter(f -> !UNKNOWN_ALIAS.equals(state.alias(f.getType()))) + .map(f -> + new Var( + f.getName(), + 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(SpringWebPsiUtil::isDocCommentData) + .map(e -> e.getText().trim()) + .collect(Collectors.joining("")) + ) + .orElseGet(f::getName) + ) + ); + } else { + // 基础类型 + return + Stream.of( + new Var( + Optional.ofNullable(PsiAnnotationMemberValueUtil.value(annotation, "value")) + .map(String::valueOf) + .orElseGet(it::getName), + type, + alias, + comments.getOrDefault(it.getName(), it.getName()) + ) + ); + } + }) + .collect(Collectors.toList()); + } + + /** + * 获得方法的报文参数 + * @param method {@link PsiMethod} + * @param comments 方法注释 + * @param state 配置状态 + * @return json5 + */ + private static String body(PsiMethod method, Map comments, ApiDocSettingPo state) { + PsiParameter parameter = + Arrays.stream(method.getParameterList().getParameters()) + // 方法参数中有@RequestBody注解的参数且只会取第一个 + .filter(it -> AnnotationUtil.findAnnotation(it, WebCopyConstants.BODY_ANNOTATION) != null) + .findFirst() + .orElse(null); + if (Objects.isNull(parameter)) { + return null; + } + + return + Json5Generator.toJson5( + parameter.getType(), + comments.get(parameter.getName()), + Collections.unmodifiableList(state.getBodyIgnoreAnnotations()), + Collections.unmodifiableList(state.getObjects()) + ); + } + + /** + * 获得方法应答参数 + * @param method {@link PsiMethod} + * @param state 配置状态 + * @return json5 + */ + private static String response(PsiMethod method, ApiDocSettingPo state) { + return + Json5Generator.toJson5( + method.getReturnType(), + null, + Collections.unmodifiableList(state.getBodyIgnoreAnnotations()), + Collections.unmodifiableList(state.getObjects()) + ); + } + + /** + * 参数实体 + */ + @AllArgsConstructor + @Getter + public static class Var { + /** + * 参数名 + */ + String name; + /** + * 类型 + */ + String type; + /** + * 类型别名 + */ + String alias; + /** + * 参数描述 + */ + String desc; + } +} 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 b81d477fe5fb02b9054c82997f8450833d765943..a0ce0728e705e54c0a582b2b919f1a289360d571 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/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 0000000000000000000000000000000000000000..ebb8475841f215feff6495491f96eda24d6e1c68 --- /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(), + null, + 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/java/com/github/passerr/idea/plugins/spring/web/Json5Generator.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/Json5Generator.java new file mode 100644 index 0000000000000000000000000000000000000000..0944b75c46ef1b9e4d8d7a2a313ec1956921b311 --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/Json5Generator.java @@ -0,0 +1,269 @@ +package com.github.passerr.idea.plugins.spring.web; + +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.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.HashMap; +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 final Map count; + 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) { + // object不能在校验范围 + this.ignores = Collections.unmodifiableSet(new HashSet<>(originIgnores)); + Map collect = originSerials.stream() + .collect(Collectors.toMap(ApiDocObjectSerialPo::getType, it -> it, (o, n) -> n)); + // 保证存在原型类型 + WebCopyConstants.PRIMITIVE_SERIALS.forEach(it -> collect.putIfAbsent(it.getType(), it.deepCopy())); + this.serials = Collections.unmodifiableMap(collect); + this.count = new HashMap<>(); + } + + /** + * 是否是忽略类型 + * @param psiType {@link PsiType} + * @return true/false + */ + boolean isIgnore(PsiType psiType) { + if (psiType instanceof PsiPrimitiveType) { + // void类型忽略 + return void.class.getName().equals(((PsiPrimitiveType) psiType).getName()); + } + + if (!(psiType instanceof PsiClassType)) { + return false; + } + + PsiClass resolve = ((PsiClassType) psiType).resolve(); + // java.lang.Void类型忽略 + return resolve != null && Void.class.getName().equals(resolve.getQualifiedName()); + } + + 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) && !rootComment.trim().isEmpty()) { + COMMENT.accept(writer, rootComment.trim()); + } + + 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 (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())) { + BEGIN_ARRAY.accept(writer); + // 若存在泛型参数 + if (referenceType.getParameterCount() > 0) { + this.toJson5(writer, referenceType.getParameters()[0]); + this.toJson5(writer, referenceType.getParameters()[0]); + } + END_ARRAY.accept(writer); + return; + } + + BEGIN_OBJECT.accept(writer); + // 接口类型、枚举类型不序列化 + if (!psiClass.isInterface() && !psiClass.isEnum()) { + Arrays.stream(psiClass.getAllFields()) + // 非static、transient字段 + .filter(SpringWebPsiUtil::isValidFiled) + // 非注解标记字段 + .filter(it -> AnnotationUtil.findAnnotations(it, this.ignores).length == 0) + // 允许递归调用一次 + .filter(it -> this.count.getOrDefault(it, 0) < 2) + .sorted(Comparator.comparing(PsiField::getName)) + .forEach(it -> { + this.count.merge(it, 1, Integer::sum); + // 字段注释 + 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()); + 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); + } + } + + /** + * 将任意类型转为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/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 35% 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 15f87ef415ec80c729e329b3313ae98c0ce655ce..2023a6b22c32745ee8e1c5394e5198d693845f82 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,24 @@ -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 +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.PsiElement; +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; +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 +28,82 @@ 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 format(array[0]); } - return null + return null; + } + + 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 value + // 注解类型忽略 + return 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 new file mode 100644 index 0000000000000000000000000000000000000000..1df1db280de09e3eb19b39ba47177566d98156bb --- /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.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)) { + assert resourceAsStream != null; + return CharStreams.toString(new InputStreamReader(resourceAsStream, StandardCharsets.UTF_8)); + } catch (Exception ignore) { + return ""; + } + } +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..f8f4facfb0598ccd35c74689143f049375ab87e5 --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/SpringWebPsiUtil.java @@ -0,0 +1,112 @@ +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; + +/** + * web psi类型工具 + * @author xiehai + * @date 2021/07/20 14:27 + * @Copyright(c) tellyes tech. inc. co.,ltd + */ +public interface SpringWebPsiUtil { + /** + * 是否是合法的参数类型 + * 非接口类型且满足排除类型 + * @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()) + .map(e -> e.getText().trim()) + .collect(Collectors.joining("")) + ) + .filter(it -> !it.trim().isEmpty()) + .orElse(null); + } +} 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 0000000000000000000000000000000000000000..7b31163f2588598f2dcfe3925d5f643db8f02865 --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/VelocityUtil.java @@ -0,0 +1,35 @@ +package com.github.passerr.idea.plugins.spring.web; + +import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.VelocityEngine; +import org.jetbrains.java.generate.velocity.VelocityFactory; + +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(StringBuilder template, Map map) { + StringWriter writer = new StringWriter(); + VelocityEngine velocity = VelocityFactory.getVelocityEngine(); + velocity.evaluate( + new VelocityContext(map), + writer, + VelocityUtil.class.getName(), + 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 new file mode 100644 index 0000000000000000000000000000000000000000..acee072348455db558043996db185efd3e0377c1 --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/WebCopyConstants.java @@ -0,0 +1,124 @@ +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; + +/** + * web常量 + * @author xiehai + * @date 2021/06/30 19:07 + * @Copyright(c) tellyes tech. inc. co.,ltd + */ +public interface WebCopyConstants { + /** + * 路径参数注解 + */ + String PATH_VARIABLE_ANNOTATION = "org.springframework.web.bind.annotation.PathVariable"; + /** + * 查询参数注解 + */ + String QUERY_PARAM_ANNOTATION = "org.springframework.web.bind.annotation.RequestParam"; + /** + * 报文体参数注解 + */ + String BODY_ANNOTATION = "org.springframework.web.bind.annotation.RequestBody"; + /** + * 查询参数忽略类型 + */ + 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" + ) + ); + /** + * 查询参数忽略注解 + */ + 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字段忽略注解 + */ + 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"), + // 将string作为了基本类型 + new ApiDocObjectSerialPo(String.class.getName(), AliasType.STRING.getType(), "string") + ) + ); + /** + * 包装类型序列化 + */ + List WRAPPED_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(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.getName(), + AliasType.STRING.getType(), + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")) + ), + new ApiDocObjectSerialPo( + LocalDate.class.getName(), + AliasType.STRING.getType(), + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")) + ), + new ApiDocObjectSerialPo( + Date.class.getName(), + AliasType.STRING.getType(), + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + ), + new ApiDocObjectSerialPo( + LocalDateTime.class.getName(), + 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/highlight/FileTemplateTextLexer.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/highlight/FileTemplateTextLexer.java new file mode 100644 index 0000000000000000000000000000000000000000..47777beab71217466d370c2153d211e8c7f8b084 --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/highlight/FileTemplateTextLexer.java @@ -0,0 +1,525 @@ +/* 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; + + +/** + * This class is a scanner generated by + * JFlex 1.7.0 + * from the specification file FileTemplateTextLexer.flex + */ +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); + } + 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++); + } + 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); + } + 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; + } + + /** 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(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 {@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]; + } + 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 + * @exception 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; + } + // 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 new file mode 100644 index 0000000000000000000000000000000000000000..591fc8a8343a41f63d87b6728e9b9d18906c9230 --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/highlight/FileTemplateTokenType.java @@ -0,0 +1,17 @@ +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 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/highlight/TemplateHighlighter.java b/src/main/java/com/github/passerr/idea/plugins/spring/web/highlight/TemplateHighlighter.java new file mode 100644 index 0000000000000000000000000000000000000000..7e32fa60a61d32aa62e643309e8e984b8a4b589d --- /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 TextAttributesKey.EMPTY_ARRAY; + } +} \ No newline at end of file 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 0000000000000000000000000000000000000000..c42bb03c656600e3c6fe99cdc3323a68ce16de18 --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/json5/Json5Writer.java @@ -0,0 +1,768 @@ +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; + } + + 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. + */ + 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 0000000000000000000000000000000000000000..0b01217fa07bd9f871efd9771e98318386e8c3d5 --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/json5/JsonScope.java @@ -0,0 +1,49 @@ +package com.github.passerr.idea.plugins.spring.web.json5; + +/** + * 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; +} \ 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 0000000000000000000000000000000000000000..c5bcf3cd79be7a12c6f7d1feacfe40107b50dfd5 --- /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/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 0000000000000000000000000000000000000000..9406cd8994638b727172bca9cce800af43d60682 --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocObjectSerialPo.java @@ -0,0 +1,62 @@ +package com.github.passerr.idea.plugins.spring.web.po; + +import com.github.passerr.idea.plugins.spring.web.WebCopyConstants; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.FieldDefaults; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * 对象序列化 + * @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 deepCopy() { + return new ApiDocObjectSerialPo(this.type, this.alias, this.value); + } + + /** + * 默认值设置 + * @return {@link List} + */ + static List defaultObjects() { + List objects = new ArrayList<>(); + WebCopyConstants.PRIMITIVE_SERIALS.stream().map(ApiDocObjectSerialPo::deepCopy).forEach(objects::add); + WebCopyConstants.WRAPPED_SERIALS.stream().map(ApiDocObjectSerialPo::deepCopy).forEach(objects::add); + + 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(); + } +} 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 0000000000000000000000000000000000000000..d5b28fc20349a84cd2958cf08b4020d76350534b --- /dev/null +++ b/src/main/java/com/github/passerr/idea/plugins/spring/web/po/ApiDocSettingPo.java @@ -0,0 +1,130 @@ +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; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.experimental.FieldDefaults; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * 配置持久化po + * @author xiehai + * @date 2021/06/30 19:30 + * @Copyright(c) tellyes tech. inc. co.,ltd + */ +@Data +@FieldDefaults(level = AccessLevel.PRIVATE) +@AllArgsConstructor +public class ApiDocSettingPo { + @OptionTag(tag = "template", nameAttribute = "", converter = StringBuilderConverter.class) + StringBuilder template; + @Tag("query-param-ignore-types") + @XCollection + List queryParamIgnoreTypes; + @Tag("query-param-ignore-annotations") + @XCollection + List queryParamIgnoreAnnotations; + @Tag("body-ignore-annotations") + @XCollection + List bodyIgnoreAnnotations; + @Tag("objects") + @XCollection(elementTypes = ApiDocObjectSerialPo.class) + List objects; + + public 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.bodyIgnoreAnnotations = new ArrayList<>(WebCopyConstants.FIELD_IGNORE_ANNOTATIONS); + this.objects = ApiDocObjectSerialPo.defaultObjects(); + } + + public ApiDocSettingPo deepCopy() { + return + new ApiDocSettingPo( + new StringBuilder(this.template), + new ArrayList<>(this.queryParamIgnoreTypes), + new ArrayList<>(this.queryParamIgnoreAnnotations), + new ArrayList<>(this.bodyIgnoreAnnotations), + this.objects.stream().map(ApiDocObjectSerialPo::deepCopy).collect(Collectors.toList()) + ); + } + + public void shallowCopy(ApiDocSettingPo source) { + this.template.setLength(0); + this.template.append(source.template); + this.queryParamIgnoreTypes.clear(); + this.queryParamIgnoreTypes.addAll(source.getQueryParamIgnoreTypes()); + this.queryParamIgnoreAnnotations.clear(); + this.queryParamIgnoreAnnotations.addAll(source.getQueryParamIgnoreAnnotations()); + this.bodyIgnoreAnnotations.clear(); + this.bodyIgnoreAnnotations.addAll(source.getBodyIgnoreAnnotations()); + this.objects.clear(); + this.objects.addAll(source.getObjects()); + } + + public void setStringTemplate(String template) { + this.template.setLength(0); + 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); + } + + 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; } + if (o == null || getClass() != o.getClass()) { return false; } + ApiDocSettingPo that = (ApiDocSettingPo) o; + return + Objects.equals(template.toString(), that.template.toString()) && + 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, queryParamIgnoreTypes, queryParamIgnoreAnnotations, + bodyIgnoreAnnotations, objects + ); + } +} 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 0000000000000000000000000000000000000000..51c4e02760210c771c4cd3a2a5cc4831794f521e --- /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 diff --git a/src/main/groovy/com/github/passerr/idea/plugins/tool/ConvertType.groovy b/src/main/java/com/github/passerr/idea/plugins/tool/ConvertType.java similarity index 32% rename from src/main/groovy/com/github/passerr/idea/plugins/tool/ConvertType.groovy rename to src/main/java/com/github/passerr/idea/plugins/tool/ConvertType.java index d4c5160bb498a5515f5897e938fa2bf254aa890c..f3575b9ec95651d671adb74bf28cc1a91d5a5767 100644 --- a/src/main/groovy/com/github/passerr/idea/plugins/tool/ConvertType.groovy +++ b/src/main/java/com/github/passerr/idea/plugins/tool/ConvertType.java @@ -1,108 +1,123 @@ -package com.github.passerr.idea.plugins.tool +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 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 java.awt.* -import java.awt.event.MouseEvent -import java.util.regex.Pattern +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 +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){ + 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)) + JsonElement element = JsonParser.parseString(input.getText()); + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + output.setText(gson.toJson(element)); // 格式化成功后定位到第一行 - output.setCaretPosition(0) + output.setCaretPosition(0); } catch (JsonSyntaxException ex) { - String msg = ex.getMessage() - def matcher = ERROR_PATTERN.matcher(msg) + String msg = ex.getMessage(); + Matcher matcher = ERROR_PATTERN.matcher(msg); if (matcher.find()) { // 行索引从0开始 - int lineIndex = (matcher.group(1) as int) - 1 + int lineIndex = (Integer.parseInt(matcher.group(1))) - 1; // 设置错误行背景色 - input.addLineHighlight(lineIndex, Color.RED) + try { + input.addLineHighlight(lineIndex, JBColor.RED); + } catch (BadLocationException ignore) { + } // 设置提示信息 - input.setToolTipSupplier({ RTextArea rt, MouseEvent me -> - def offset = input.getLineOfOffset(input.viewToModel(me.getPoint())) - offset == lineIndex ? msg : null - } as ToolTipSupplier) + 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) + input.setCaretPosition(lineIndex); } - new NotificationThread(new Notification("Json Format", "Json Format", msg, ERROR)).start() + new NotificationThread(new Notification("Json Format", "Json Format", msg, ERROR)).start(); } } }, /** * mybatis日志转可执行sql */ - SQL(SyntaxConstants.SYNTAX_STYLE_SQL){ + SQL(SyntaxConstants.SYNTAX_STYLE_SQL) { @Override void handle(RSyntaxTextArea input, RSyntaxTextArea output) { - def sql = LogParser.toSql(input.getText()) + String sql = LogParser.toSql(input.getText()); if (StringUtils.isEmpty(sql)) { // 设置错误行背景色 // 默认设置第一行 - input.addLineHighlight(0, Color.RED) + try { + input.addLineHighlight(0, JBColor.RED); + } catch (BadLocationException ignore) { + } // 设置提示信息 - 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) + 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.setText(SqlFormatter.format(sql)); // 格式化成功后定位到第一行 - output.setCaretPosition(0) + output.setCaretPosition(0); } } }, /** * 文本 */ - TEXT(SyntaxConstants.SYNTAX_STYLE_NONE) + TEXT(SyntaxConstants.SYNTAX_STYLE_NONE); - String style + String style; /** * json错误信息正则匹配 */ - private static final ERROR_PATTERN = Pattern.compile("line (\\w+) column") - - ConvertType(String style) { - this.style = style - } + private static final Pattern ERROR_PATTERN = Pattern.compile("line (\\w+) column"); /** * 对文本进行格式化 - * @param input 输入文本域 + * @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/java/com/github/passerr/idea/plugins/tool/TextFormatView.java similarity index 32% rename from src/main/groovy/com/github/passerr/idea/plugins/tool/TextFormatView.groovy rename to src/main/java/com/github/passerr/idea/plugins/tool/TextFormatView.java index 917c254fe15b429f9c6b94c6948f8f069cc9e8e9..b47ceb619f763bf3ae78dd6fbf9174b97f5ad33f 100644 --- a/src/main/groovy/com/github/passerr/idea/plugins/tool/TextFormatView.groovy +++ b/src/main/java/com/github/passerr/idea/plugins/tool/TextFormatView.java @@ -1,63 +1,71 @@ -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 +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 */ -class TextFormatView extends JRootPane { - Project project +public class TextFormatView extends JRootPane { + Project project; /** * 菜单 */ - ToolMenu menu + ToolMenu menu; /** * 输入文本域 */ - RSyntaxTextArea inputTextArea = new RSyntaxTextArea(19, 0) + RSyntaxTextArea inputTextArea = new RSyntaxTextArea(19, 0); /** * 输出文本域 */ - RSyntaxTextArea outputTextArea = new RSyntaxTextArea(34, 0) + RSyntaxTextArea outputTextArea = new RSyntaxTextArea(34, 0); /** * cache instance by project */ - private static Map INSTANCES = new HashMap<>() + 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.project = project; + super.getContentPane().setLayout(new BoxLayout(super.getContentPane(), BoxLayout.Y_AXIS)); // 初始化输入文本域 - this.doInitInputTextArea() + this.doInitInputTextArea(); // 初始化菜单 - this.doInitMenu() + this.doInitMenu(); // 初始输出化文本域 - this.doInitOutputTextArea() + 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) + 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) { - } finally { - inputStream && inputStream.close() } } @@ -65,126 +73,117 @@ class TextFormatView extends JRootPane { * 输入文本域初始化 */ 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() + 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)) + ); + super.getContentPane().add(new RTextScrollPane(this.inputTextArea)); } /** * 输出文本域初始化 */ private void doInitOutputTextArea() { - this.outputTextArea.with { - setCodeFoldingEnabled(true) - setAutoIndentEnabled(true) - } - super.getContentPane().add(new RTextScrollPane(this.outputTextArea)) + this.outputTextArea.setCodeFoldingEnabled(true); + this.outputTextArea.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) - } + ItemListener subMenuItemListener = e -> { + if (e.getStateChange() == ItemEvent.SELECTED) { + switchMenu((ToolMenu) e.getItem()); } - } + }; // 编/解码 - JComboBox encodeMenu = new JComboBox<>([ - ToolMenu.URL_ENCODE, - ToolMenu.URL_DECODE - ] as ToolMenu[]) - encodeMenu.addItemListener(subMenuItemListener) - encodeMenu.setVisible(false) + JComboBox encodeMenu = new ComboBox<>(new ToolMenu[]{ToolMenu.URL_ENCODE, ToolMenu.URL_DECODE}); + encodeMenu.addItemListener(subMenuItemListener); + encodeMenu.setVisible(false); // 加/解密 - JComboBox encryptMenu = new JComboBox<>([ + JComboBox encryptMenu = new ComboBox<>(new ToolMenu[]{ ToolMenu.MD5_ENCRYPTION, ToolMenu.BASE64_ENCRYPTION, ToolMenu.BASE64_DECRYPTION - ] as ToolMenu[]) - encryptMenu.addItemListener(subMenuItemListener) - encryptMenu.setVisible(false) + }); + encryptMenu.addItemListener(subMenuItemListener); + encryptMenu.setVisible(false); // 主菜单 - JComboBox mainMenu = new JComboBox<>([ + JComboBox mainMenu = new ComboBox<>(new ToolMenu[]{ 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.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(mainMenu.getSelectedItem() as ToolMenu) + mainMenu.setSelectedItem(0); + this.switchMenu((ToolMenu) mainMenu.getSelectedItem()); // 转换按钮 - 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) + 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 convertType 转换类型 + * @param menu 菜单 */ 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) + 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); + } } } @@ -194,14 +193,14 @@ class TextFormatView extends JRootPane { private void doFormat() { // 文本框为空 不进行格式化 if (StringUtils.isEmpty(this.inputTextArea.getText())) { - return + return; } try { - this.inputTextArea.setToolTipSupplier(null) - this.inputTextArea.removeAllLineHighlights() - this.menu.handle(this.inputTextArea, this.outputTextArea) + this.inputTextArea.setToolTipSupplier(null); + this.inputTextArea.removeAllLineHighlights(); + this.menu.handle(this.inputTextArea, this.outputTextArea); } finally { - this.getContentPane().repaint() + this.getContentPane().repaint(); } } @@ -214,11 +213,11 @@ class TextFormatView extends JRootPane { if (!INSTANCES.containsKey(project)) { synchronized (TextFormatView.class) { if (!INSTANCES.containsKey(project)) { - INSTANCES.putIfAbsent(project, new TextFormatView(project)) + INSTANCES.putIfAbsent(project, new TextFormatView(project)); } } } - return INSTANCES.get(project) + return INSTANCES.get(project); } -} \ No newline at end of file +} 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 0000000000000000000000000000000000000000..41276ebcee55c1f28c36f9249bd938d40d8475b4 --- /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/groovy/com/github/passerr/idea/plugins/tool/ToolMenu.groovy b/src/main/java/com/github/passerr/idea/plugins/tool/ToolMenu.java similarity index 41% rename from src/main/groovy/com/github/passerr/idea/plugins/tool/ToolMenu.groovy rename to src/main/java/com/github/passerr/idea/plugins/tool/ToolMenu.java index 7f921cecfa01eaaa19836361a89cc14f0f6ce017..a7fb211eebf679e8cffc796eeca90122eb425fdd 100644 --- a/src/main/groovy/com/github/passerr/idea/plugins/tool/ToolMenu.groovy +++ b/src/main/java/com/github/passerr/idea/plugins/tool/ToolMenu.java @@ -1,16 +1,25 @@ -package com.github.passerr.idea.plugins.tool +package com.github.passerr.idea.plugins.tool; -import org.apache.commons.codec.digest.DigestUtils -import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea +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.nio.charset.StandardCharsets +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 - * @author xiehai */ +@FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true) +@RequiredArgsConstructor enum ToolMenu { /** * main menu @@ -23,62 +32,59 @@ enum ToolMenu { /** * sub menu */ - URL_DECODE("url解码", ConvertType.TEXT){ + URL_DECODE("url解码", ConvertType.TEXT) { @Override void handle(RSyntaxTextArea input, RSyntaxTextArea output) { - output.setText(URLDecoder.decode(input.getText(), StandardCharsets.UTF_8.name())) + try { + output.setText(URLDecoder.decode(input.getText(), StandardCharsets.UTF_8.name())); + } catch (UnsupportedEncodingException ignore) { + } } }, - URL_ENCODE("url编码", ConvertType.TEXT){ + URL_ENCODE("url编码", ConvertType.TEXT) { @Override void handle(RSyntaxTextArea input, RSyntaxTextArea output) { - output.setText(URLEncoder.encode(input.getText(), StandardCharsets.UTF_8.name())) + try { + output.setText(URLEncoder.encode(input.getText(), StandardCharsets.UTF_8.name())); + } catch (UnsupportedEncodingException ignore) { + } } }, - MD5_ENCRYPTION("md5加密", ConvertType.TEXT){ + MD5_ENCRYPTION("md5加密", ConvertType.TEXT) { @Override void handle(RSyntaxTextArea input, RSyntaxTextArea output) { - output.setText(DigestUtils.md5Hex(input.getText())) + output.setText(DigestUtils.md5Hex(input.getText())); } }, - BASE64_DECRYPTION("base64解密", ConvertType.TEXT){ + BASE64_DECRYPTION("base64解密", ConvertType.TEXT) { @Override void handle(RSyntaxTextArea input, RSyntaxTextArea output) { - try { - output.setText(new String(input.getText().decodeBase64())) - } catch (Exception ignore) { - - } + output.setText(new String(Base64.decodeBase64(input.getText()))); } }, - BASE64_ENCRYPTION("base64加密", ConvertType.TEXT){ + BASE64_ENCRYPTION("base64加密", ConvertType.TEXT) { @Override void handle(RSyntaxTextArea input, RSyntaxTextArea output) { - output.setText(input.getText().bytes.encodeBase64().toString()) + output.setText(new String(Base64.encodeBase64(input.getText().getBytes()))); } - } - - String name - ConvertType type + }; - ToolMenu(String name, ConvertType type) { - this.name = name - this.type = type - } + String name; + ConvertType type; @Override - String toString() { - return this.name + public String toString() { + return this.name; } /** * 默认文本域操作 - * @param input 输入文本域 + * @param input 输入文本域 * @param output 输出文本域 */ void handle(RSyntaxTextArea input, RSyntaxTextArea output) { // 默认对应类型格式化 - this.type.handle(input, output) + this.type.handle(input, output); } -} \ 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 2762a9b5ef072bdc8d23a2871954e91daf3d28b2..d32a2caa78b5ba43a04f60f3db51c2810b0ed025 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -1,18 +1,25 @@ + com.github.passerr.idea.plugins - PasseRR Idea Plugins + PasseRR Idea Tools PasseRR - 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文档 ]]> 2.0.0: +
    +
  • 从groovy改为java
  • +
  • 添加复制spring controller方法全路径的action
  • +
  • 添加复制spring controller方法为api文档的action
  • +
  • 添加复制方法返回类型为json5的action
  • +
  • 修改复制为mybatis日志为sql的动作 仅当选择内容包含Preparing:及Parameters:
  • +

1.1.0:

  • differential input/output textarea
  • @@ -47,9 +54,6 @@ - com.intellij.modules.lang - com.intellij.modules.java - @@ -70,10 +74,16 @@ + + + - + - - diff --git a/src/main/resources/api-doc-desc.html b/src/main/resources/api-doc-desc.html new file mode 100644 index 0000000000000000000000000000000000000000..7707f83d0ee7eb6038ab92bf8a710559eb8fef2d --- /dev/null +++ b/src/main/resources/api-doc-desc.html @@ -0,0 +1,157 @@ + + + + + + +
    + + 关于rest接口内置的velocity参数,主要包括url、http方法、查询参数、路径参数、报文体、应答报文。 + 其中,查询参数及路径参数均为List类型,具体介绍如下。 + +
    +
    +查询参数、路径参数属性 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    属性名类型属性描述
    + name + string + 参数名 +
    + type + string + 参数java类型全名 +
    + alias + string + 参数类型别名 +
    + desc + string + 参数描述 +
    +
    +预定义velocity参数描述 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + 参数名 + 类型 + 参数描述 +
    + ${method} + string + http方法类型若为指定则为UNKNOWN +
    + ${url} + string + rest方法路径 +
    + ${hasQueryParams} + boolean + 是否有查询参数 +
    + ${queryParams} + list查询参数列表
    + ${hasPathVariables} + boolean是否有路径参数
    + ${pathVariables} + list路径参数列表
    + ${hasBody} + boolean是否存在请求报文体
    + ${body} + string请求报文体json5序列化字符串包含字段注释
    + ${hasResponse} + boolean是否存在应答报文体
    + ${response} + string应答报文体json5序列化字符串包含字段注释
    +
    + + Apache Velocity template language is used + + + \ No newline at end of file diff --git a/src/main/resources/api-doc-template.vm b/src/main/resources/api-doc-template.vm new file mode 100644 index 0000000000000000000000000000000000000000..8ca3746aa61d9a5be2a50f875dc1d0c7e9b4991f --- /dev/null +++ b/src/main/resources/api-doc-template.vm @@ -0,0 +1,42 @@ +## 默认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/main/resources/icon/icon.svg b/src/main/resources/icon/icon.svg new file mode 100644 index 0000000000000000000000000000000000000000..153d3838e1965bd397aca408fcf9b4a1f14cd4e1 --- /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 Binary files a/src/main/resources/icon/tool.jpg and /dev/null differ 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 0000000000000000000000000000000000000000..b257b421aac95057cab3fdb025d23efc30b72428 --- /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)); + } +} diff --git a/src/test/java/com/github/passerr/idea/plugins/spring/web/json5/Json5Spec.java b/src/test/java/com/github/passerr/idea/plugins/spring/web/json5/Json5Spec.java new file mode 100644 index 0000000000000000000000000000000000000000..29b85244a0176707e9127d16ae561936ad0f08f4 --- /dev/null +++ b/src/test/java/com/github/passerr/idea/plugins/spring/web/json5/Json5Spec.java @@ -0,0 +1,27 @@ +package com.github.passerr.idea.plugins.spring.web.json5; + +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); + } +}