diff --git a/.gitignore b/.gitignore
index 3199f0f..5be79fc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,44 @@
+.gradle
+build/
+!gradle/wrapper/gradle-wrapper.jar
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### IntelliJ IDEA ###
.idea
-out
-target
+.idea/modules.xml
+.idea/jarRepositories.xml
+.idea/compiler.xml
+.idea/libraries/
+*.iws
+*.iml
+*.ipr
+out/
+!**/src/main/**/out/
+!**/src/test/**/out/
+.intellijPlatform
+
+### Eclipse ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+bin/
+!**/src/main/**/bin/
+!**/src/test/**/bin/
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+
+### VS Code ###
+.vscode/
+
+### Mac OS ###
+.DS_Store
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 0000000..6364a25
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,74 @@
+plugins {
+ id("java")
+ id("org.jetbrains.intellij.platform") version "2.0.1"
+}
+
+group = "me.lotabout"
+version = "2.0.5"
+
+repositories {
+ mavenCentral()
+ intellijPlatform {
+ defaultRepositories()
+ }
+}
+
+dependencies {
+ intellijPlatform {
+ create("IC", "2025.1")
+ bundledPlugin("com.intellij.java")
+ pluginVerifier()
+ zipSigner()
+ instrumentationTools()
+ }
+ implementation("org.apache.commons:commons-lang3:3.12.0")
+ implementation("org.apache.velocity.tools:velocity-tools-generic:3.1") {
+ exclude(group = "org.apache.velocity", module = "velocity-engine-core")
+ exclude(group = "org.slf4j", module = "slf4j-api")
+ }
+ testImplementation("org.junit.jupiter:junit-jupiter-api:5.7.0")
+ testImplementation("org.mockito:mockito-core:5.10.0")
+ testImplementation("org.mockito:mockito-junit-jupiter:5.10.0")
+ testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.7.0")
+}
+
+tasks {
+ // Set the JVM compatibility versions
+ withType {
+ sourceCompatibility = "21"
+ targetCompatibility = "21"
+ options.compilerArgs.addAll(listOf("-Xlint:deprecation", "-Xlint:unchecked"))
+ }
+
+ // 禁用buildSearchableOptions任务
+ named("buildSearchableOptions") {
+ enabled = false
+ }
+
+ // 禁用测试任务
+ named("test") {
+ enabled = false
+ }
+
+ patchPluginXml {
+ sinceBuild.set("251")
+ untilBuild.set("253.*")
+ changeNotes.set("""
+
+ - Support for IntelliJ IDEA 2025.1 - 2025.3
+ - Fix settings dialog compatibility issue
+ - Fix NullPointerException when opening plugin settings
+
+ """)
+ }
+
+ signPlugin {
+ certificateChain.set(System.getenv("CERTIFICATE_CHAIN"))
+ privateKey.set(System.getenv("PRIVATE_KEY"))
+ password.set(System.getenv("PRIVATE_KEY_PASSWORD"))
+ }
+
+ publishPlugin {
+ token.set(System.getenv("PUBLISH_TOKEN"))
+ }
+}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..6c35a1d
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,8 @@
+# Opt-out flag for bundling Kotlin standard library -> https://jb.gg/intellij-platform-kotlin-stdlib
+kotlin.stdlib.default.dependency = false
+
+# Enable Gradle Configuration Cache -> https://docs.gradle.org/current/userguide/configuration_cache.html
+org.gradle.configuration-cache = true
+
+# Enable Gradle Build Cache -> https://docs.gradle.org/current/userguide/build_cache.html
+org.gradle.caching = true
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..249e583
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 0000000..fc76012
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Thu Feb 20 02:23:18 CST 2025
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..1b6c787
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,234 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original 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 POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+APP_NAME="Gradle"
+APP_BASE_NAME=${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 "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# 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 ;; #(
+ MSYS* | 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" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+# Collect all arguments for the java command;
+# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+# shell script including quotes and variable substitutions, so put them in
+# double quotes to make sure that they get re-expanded; and
+# * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..ac1b06f
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,89 @@
+@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 execute
+
+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 execute
+
+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
+
+: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 %*
+
+: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/resources/template/default.vm b/resources/template/default.vm
deleted file mode 100644
index 8dfb529..0000000
--- a/resources/template/default.vm
+++ /dev/null
@@ -1,130 +0,0 @@
-## Tutorial for writing your templates
-##
-## 1. First you need to know basic syntax of velocity[1].
-## 2. Then it is necessary to understand the variable that CodeGenerator provides
-## and its inner structure for retrieving the information you need for generating code.
-## 3. Learn to use the utils provided so that you can ask for further information
-## or reduce your workload.
-##
-## Variables Provided (Class Mode)
-## -------------------------------
-## Class mode means you want to create new classes(file).
-##
-## - ClassName: String The name spcified by `Target Class Name`
-## - PackageName: String The package name specified by `Target Class Name`
-## - class0: ClassEntry The class that the action is triggered upon
-## - raw: PsiClass
-## - String packageName
-## - importList: List
-## - fields: List
-## - allFields: List
-## - methods: List
-## - allMethods: List
-## - innerClasses: List
-## - allInnerClasses: List
-## - typeParamList: List
-## - name: String
-## - superName: String
-## - superQualifiedName: String
-## - qualifiedName: String
-## - typeParams: int
-## - hasSuper: boolean
-## - deprecated: boolean
-## - enum: boolean
-## - exception: boolean
-## - abstract: boolean
-## - implementNames: String[]
-## - isImplements(String): bool
-## - isExtends(String): bool
-## - matchName(String): bool
-##
-## - class1: ClassEntry The first selected class, where `1` is the postfix
-## you specify in pipeline
-## ...
-##
-## - MemberEntry (FieldEntry/MethodEntry common properties)
-## - raw: PsiField(for field), PsiMethod(for method)
-## - name: String
-## - accessor: String
-## - array: boolean
-## - nestedArray: boolean
-## - collection: boolean
-## - map: boolean
-## - primitive: boolean
-## - string: boolean
-## - primitiveArray: boolean
-## - objectArray: boolean
-## - numeric: boolean
-## - object: boolean
-## - date: boolean
-## - set: boolean
-## - list: boolean
-## - stringArray: boolean
-## - calendar: boolean
-## - typeName: String
-## - typeQualifiedName: String
-## - type: String
-## - boolean: boolean
-## - long: boolean
-## - float: boolean
-## - double: boolean
-## - void: boolean
-## - notNull: boolean
-## - char: boolean
-## - byte: boolean
-## - short: boolean
-## - modifierStatic: boolean
-## - modifierPublic: boolean
-## - modifierProtected: boolean
-## - modifierPackageLocal: boolean
-## - modifierPrivate: boolean
-## - modifierFinal: boolean
-##
-## - FieldEntry
-## - constant: boolean
-## - modifierTransient: boolean
-## - modifierVolatile: boolean
-## - enum: boolean
-## - matchName(String): bool
-##
-## - MethodEntry
-## - methodName: String
-## - fieldName: String
-## - modifierAbstract: boolean
-## - modifierSynchronzied: boolean
-## - modifierSynchronized: boolean
-## - returnTypeVoid: boolean
-## - getter: boolean
-## - deprecated: boolean
-## - matchName(String): bool
-##
-## Variables for Body Mode
-## -----------------------
-## - class0: ClassEntry The current class
-## - fields: List All selected fields
-## - methods: List All selected methods
-## - members: List selected fields+methods
-## - parentMethod: MethodEntry The nearest method that surround the current cursor
-##
-## Utilities
-## ---------
-## - settings: CodeStyleSettings settings of code style
-## - project: Project The project instance, normally used by Psi related utilities
-## - helper: GenerationHelper
-## - StringUtil: Class
-## - NameUtil: Class
-## - PsiShortNamesCache: Class utility to search classes
-## - PsiJavaPsiFacade: Class Java specific utility to search classes
-## - GlobalSearchScope: Class class to create search scopes, used by above utilities
-## - EntryFactory: Class EntryFactory.of(...) to turn PsiXXX to XXXEntry.
-##
-## Other feature
-## -------------
-## - Auto import. If the generated code contains full qualified name, Code Generator will try to
-## import the packages automatically and shorten the name.
-## For example `java.util.List<>` -> `List<>`
-##
-## References
-## ----------
-## - Velocity syntax: http://velocity.apache.org/engine/1.7/user-guide.html
-
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100644
index 0000000..5a5e6a4
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1,8 @@
+pluginManagement {
+ repositories {
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+
+rootProject.name = "CodeGenerator"
diff --git a/src/main/java/me/lotabout/codegenerator/CodeGenerator.java b/src/main/java/me/lotabout/codegenerator/CodeGenerator.java
new file mode 100644
index 0000000..aa0c5c9
--- /dev/null
+++ b/src/main/java/me/lotabout/codegenerator/CodeGenerator.java
@@ -0,0 +1,9 @@
+package me.lotabout.codegenerator;
+
+import com.intellij.openapi.components.Service;
+
+@Service
+public final class CodeGenerator {
+ public CodeGenerator() {
+ }
+}
diff --git a/src/main/java/me/lotabout/codegenerator/CodeGeneratorSettings.java b/src/main/java/me/lotabout/codegenerator/CodeGeneratorSettings.java
new file mode 100644
index 0000000..50c6f37
--- /dev/null
+++ b/src/main/java/me/lotabout/codegenerator/CodeGeneratorSettings.java
@@ -0,0 +1,146 @@
+package me.lotabout.codegenerator;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+import org.jetbrains.annotations.Nullable;
+
+import com.intellij.openapi.components.PersistentStateComponent;
+import com.intellij.openapi.components.State;
+import com.intellij.openapi.components.Storage;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.util.xmlb.XmlSerializerUtil;
+
+import me.lotabout.codegenerator.config.CodeTemplate;
+import me.lotabout.codegenerator.config.CodeTemplateList;
+import me.lotabout.codegenerator.config.include.Include;
+
+@State(name = "CodeGeneratorSettings", storages = {@Storage("$APP_CONFIG$/CodeGenerator-settings.xml")})
+public class CodeGeneratorSettings implements PersistentStateComponent {
+
+ private static final Logger LOGGER = Logger.getInstance(CodeGeneratorSettings.class);
+ private List codeTemplates;
+ private List includes;
+
+ public CodeGeneratorSettings() {
+
+ }
+
+ public List getIncludes() {
+ if (includes == null) {
+ includes = new ArrayList<>();
+ }
+ return includes;
+ }
+
+ public void setIncludes(final List includes) {
+ this.includes = includes;
+ }
+
+ public CodeGeneratorSettings setCodeTemplates(final List codeTemplates) {
+ this.codeTemplates = codeTemplates;
+ return this;
+ }
+
+
+ @Nullable
+ @Override
+ public CodeGeneratorSettings getState() {
+ if (codeTemplates == null) {
+ codeTemplates = loadDefaultTemplates();
+ }
+ return this;
+ }
+
+ @Override
+ public void loadState(final CodeGeneratorSettings codeGeneratorSettings) {
+ XmlSerializerUtil.copyBean(codeGeneratorSettings, this);
+
+ // 修复可能的null字段问题
+ if (this.codeTemplates != null) {
+ for (CodeTemplate template : this.codeTemplates) {
+ if (template != null) {
+ // 确保这些字段不为null,避免NPE
+ if (template.whenDuplicatesOption == null) {
+ template.whenDuplicatesOption = org.jetbrains.java.generate.config.DuplicationPolicy.ASK;
+ }
+ if (template.insertNewMethodOption == null) {
+ template.insertNewMethodOption = org.jetbrains.java.generate.config.InsertWhere.AT_CARET;
+ }
+ }
+ }
+ }
+ }
+
+ public List getCodeTemplates() {
+ if (codeTemplates == null) {
+ codeTemplates = loadDefaultTemplates();
+ }
+
+ // 确保所有模板的字段不为null
+ for (CodeTemplate template : codeTemplates) {
+ if (template != null) {
+ if (template.whenDuplicatesOption == null) {
+ template.whenDuplicatesOption = org.jetbrains.java.generate.config.DuplicationPolicy.ASK;
+ }
+ if (template.insertNewMethodOption == null) {
+ template.insertNewMethodOption = org.jetbrains.java.generate.config.InsertWhere.AT_CARET;
+ }
+ }
+ }
+
+ return codeTemplates;
+ }
+
+ public Optional getCodeTemplate(final String templateId) {
+ Optional template = codeTemplates.stream()
+ .filter(t -> t != null && t.getId().equals(templateId))
+ .findFirst();
+
+ // 确保返回的模板字段不为null
+ template.ifPresent(t -> {
+ if (t.whenDuplicatesOption == null) {
+ t.whenDuplicatesOption = org.jetbrains.java.generate.config.DuplicationPolicy.ASK;
+ }
+ if (t.insertNewMethodOption == null) {
+ t.insertNewMethodOption = org.jetbrains.java.generate.config.InsertWhere.AT_CARET;
+ }
+ });
+
+ return template;
+ }
+
+ public Optional getInclude(final String includeId) {
+ return includes.stream()
+ .filter(t -> t != null && t.getId().equals(includeId))
+ .findFirst();
+ }
+
+ public void removeCodeTemplate(final String templateId) {
+ codeTemplates.removeIf(template -> template.name.equals(templateId));
+ }
+
+ private List loadDefaultTemplates() {
+ final List templates = new ArrayList<>();
+ try {
+ templates.addAll(loadTemplates("getters-and-setters.xml"));
+ templates.addAll(loadTemplates("to-string.xml"));
+ templates.addAll(loadTemplates("HUE-Serialization.xml"));
+ } catch (final Exception e) {
+ LOGGER.error("loadDefaultTemplates failed", e);
+ }
+ return templates;
+ }
+
+ private List loadTemplates(final String templateFileName) throws IOException {
+ final InputStream in = CodeGeneratorSettings.class.getResourceAsStream("/template/" + templateFileName);
+ if (in == null) {
+ throw new IOException("Resource not found: " + templateFileName);
+ }
+ return CodeTemplateList.fromXML(FileUtil.loadTextAndClose(in));
+ }
+}
diff --git a/src/me/lotabout/codegenerator/ConflictResolutionPolicy.java b/src/main/java/me/lotabout/codegenerator/ConflictResolutionPolicy.java
similarity index 100%
rename from src/me/lotabout/codegenerator/ConflictResolutionPolicy.java
rename to src/main/java/me/lotabout/codegenerator/ConflictResolutionPolicy.java
diff --git a/src/main/java/me/lotabout/codegenerator/action/CodeGeneratorAction.java b/src/main/java/me/lotabout/codegenerator/action/CodeGeneratorAction.java
new file mode 100644
index 0000000..dc508c6
--- /dev/null
+++ b/src/main/java/me/lotabout/codegenerator/action/CodeGeneratorAction.java
@@ -0,0 +1,409 @@
+package me.lotabout.codegenerator.action;
+
+import java.awt.BorderLayout;
+import java.io.File;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.swing.JPanel;
+
+import org.apache.commons.lang3.StringUtils;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.java.generate.config.Config;
+import org.jetbrains.java.generate.config.FilterPattern;
+import org.jetbrains.java.generate.exception.GenerateCodeException;
+
+import com.intellij.codeInsight.generation.PsiElementClassMember;
+import com.intellij.codeInsight.generation.PsiFieldMember;
+import com.intellij.codeInsight.generation.PsiMethodMember;
+import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.ide.highlighter.JavaFileType;
+import com.intellij.ide.util.MemberChooser;
+import com.intellij.ide.util.TreeClassChooser;
+import com.intellij.ide.util.TreeClassChooserFactory;
+import com.intellij.openapi.actionSystem.ActionUpdateThread;
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.LangDataKeys;
+import com.intellij.openapi.actionSystem.Presentation;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.ProjectRootManager;
+import com.intellij.openapi.ui.DialogWrapper;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.JavaPsiFacade;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiField;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiFileFactory;
+import com.intellij.psi.PsiJavaFile;
+import com.intellij.psi.PsiMember;
+import com.intellij.psi.PsiMethod;
+import com.intellij.psi.search.GlobalSearchScope;
+import com.intellij.psi.util.PsiTreeUtil;
+import com.intellij.psi.util.PsiUtil;
+import com.intellij.util.IncorrectOperationException;
+
+import me.lotabout.codegenerator.CodeGeneratorSettings;
+import me.lotabout.codegenerator.config.ClassSelectionConfig;
+import me.lotabout.codegenerator.config.CodeTemplate;
+import me.lotabout.codegenerator.config.MemberSelectionConfig;
+import me.lotabout.codegenerator.config.PipelineStep;
+import me.lotabout.codegenerator.util.TypeEntry;
+import me.lotabout.codegenerator.util.EntryFactory;
+import me.lotabout.codegenerator.util.GenerationUtil;
+import me.lotabout.codegenerator.util.MemberEntry;
+import me.lotabout.codegenerator.worker.JavaBodyWorker;
+import me.lotabout.codegenerator.worker.JavaCaretWorker;
+import me.lotabout.codegenerator.worker.JavaClassWorker;
+
+import static me.lotabout.codegenerator.util.GenerationUtil.convertClassMembersToPsiMembers;
+import static me.lotabout.codegenerator.util.GenerationUtil.velocityEvaluate;
+
+public class CodeGeneratorAction extends AnAction {
+ private static final Logger logger = Logger.getInstance(CodeGeneratorAction.class);
+ private final String templateKey;
+ private final CodeGeneratorSettings settings;
+
+ public CodeGeneratorAction(final String templateKey, final String templateName) {
+ getTemplatePresentation().setDescription("description");
+ getTemplatePresentation().setText(templateName, false);
+
+ this.settings = ApplicationManager.getApplication().getService(CodeGeneratorSettings.class);;
+ this.templateKey = templateKey;
+ }
+
+ @Override
+ @NotNull
+ public ActionUpdateThread getActionUpdateThread() {
+ return super.getActionUpdateThread();
+ }
+
+ @Override
+ public void update(final AnActionEvent e) {
+ // Code Generation action could run without editor
+ final Presentation presentation = e.getPresentation();
+ final Project project = e.getProject();
+ if (project == null) {
+ presentation.setEnabled(false);
+ }
+
+ // final PsiFile file = e.getDataContext().getData(LangDataKeys.PSI_FILE);
+ // if (!(file instanceof PsiJavaFile)) {
+ // presentation.setEnabled(false);
+ // }
+
+ presentation.setEnabled(true);
+ }
+
+ @Override
+ public void actionPerformed(final AnActionEvent e) {
+ final CodeTemplate codeTemplate = settings
+ .getCodeTemplate(templateKey)
+ .orElseThrow(IllegalStateException::new);
+ final Project project = e.getProject();
+ if (project == null) {
+ logger.info("No project found in context");
+ return;
+ }
+ final DataContext context = e.getDataContext();
+ final PsiFile file = context.getData(LangDataKeys.PSI_FILE);
+ if (file == null) {
+ logger.info("No file found in context");
+ return;
+ }
+ if ((! (file instanceof PsiJavaFile)) && !codeTemplate.type.isSupportNonJavaFile()) {
+ logger.info("Not a java file, cannot execute template: " + codeTemplate.name);
+ return;
+ }
+ final Editor editor = context.getData(LangDataKeys.EDITOR);
+ if (editor == null) {
+ if (codeTemplate.type.isNeedEditor()) {
+ logger.info("No editor found in context");
+ return;
+ }
+ }
+ final Map contextMap = executePipeline(codeTemplate, file, editor);
+ if (contextMap == null) {
+ return; // early return from pipeline
+ }
+
+ switch (codeTemplate.type) {
+ case CLASS:
+ assert (file instanceof PsiJavaFile);
+ JavaClassWorker.execute(codeTemplate, settings.getIncludes(), (PsiJavaFile) file, contextMap);
+ break;
+ case BODY:
+ assert (editor != null);
+ final PsiClass clazz = getSubjectClass(editor, file);
+ if (clazz == null) {
+ HintManager.getInstance().showErrorHint(editor, "no parent class found for current cursor position");
+ return;
+ }
+ JavaBodyWorker.execute(codeTemplate, settings.getIncludes(), clazz, editor, contextMap);
+ break;
+ case CARET:
+ assert (editor != null);
+ JavaCaretWorker.execute(codeTemplate, settings.getIncludes(), file, editor, contextMap);
+ break;
+ default:
+ throw new IllegalStateException("template type is not recognized: " + codeTemplate.type);
+ }
+ }
+
+ private Map executePipeline(@NotNull final CodeTemplate codeTemplate,
+ @NotNull final PsiFile file, final Editor editor) {
+ final Project project = file.getProject();
+ logger.debug("+++ executePipeline - START +++");
+ if (logger.isDebugEnabled()) {
+ logger.debug("Current project " + project.getName());
+ }
+
+ final Map contextMap = new HashMap<>();
+ PsiClass clazz = getSubjectClass(editor, file);
+ if (clazz == null) {
+ clazz = buildFakeClassForEmptyFile(file);
+ }
+ contextMap.put("class0", TypeEntry.of(clazz));
+
+ if (editor != null) {
+ final int offset = editor.getCaretModel().getOffset();
+ final PsiElement context = file.findElementAt(offset);
+ final PsiMethod parentMethod = PsiTreeUtil.getParentOfType(context, PsiMethod.class, false);
+ contextMap.put("parentMethod", EntryFactory.of(parentMethod));
+ }
+
+ logger.debug("Select member/class through pipeline");
+ for (final PipelineStep step : codeTemplate.pipeline) {
+ if (!step.enabled()) continue;
+ switch (step.type()) {
+ case "class-selection":
+ final PsiClass selectedClass = selectClass(file, (ClassSelectionConfig) step, contextMap);
+ if (selectedClass == null) return null;
+ contextMap.put("class" + step.postfix(), TypeEntry.of(selectedClass));
+ break;
+ case "member-selection":
+ final List selectedMembers = selectMember(file, (MemberSelectionConfig) step, contextMap);
+ if (selectedMembers == null) return null;
+ GenerationUtil.insertMembersToContext(selectedMembers,
+ contextMap,
+ step.postfix(),
+ ((MemberSelectionConfig) step).sortElements);
+ break;
+ default:
+ throw new IllegalStateException("step type not recognized: " + step.type());
+ }
+ }
+ return contextMap;
+ }
+
+ @Nullable
+ private static PsiClass getSubjectClass(final Editor editor, @NotNull final PsiFile file) {
+ if (editor != null) {
+ final int offset = editor.getCaretModel().getOffset();
+ final PsiElement context = file.findElementAt(offset);
+ if (context == null) {
+ return null;
+ }
+ return PsiTreeUtil.getParentOfType(context, PsiClass.class, false);
+ } else {
+ return getFirstClass(file);
+ }
+ }
+
+ private PsiClass selectClass(@NotNull final PsiFile file,
+ final ClassSelectionConfig config, final Map contextMap) {
+ final String initialClassNameTemplate = config.initialClass;
+ final Project project = file.getProject();
+ try {
+ final String className;
+ if (StringUtils.isEmpty(initialClassNameTemplate)) {
+ className = null;
+ } else {
+ className = velocityEvaluate(project, contextMap,
+ contextMap, initialClassNameTemplate, settings.getIncludes());
+ }
+ logger.debug("Initial class name for class selection is ", className);
+ PsiClass initialClass = null;
+ if (!StringUtils.isEmpty(className)) {
+ initialClass = JavaPsiFacade
+ .getInstance(project)
+ .findClass(className, GlobalSearchScope.allScope(project));
+ }
+
+ if (initialClass == null) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("could not found initialClass" + className);
+ }
+ initialClass = getFirstClass(file);
+ }
+
+ final TreeClassChooser chooser = TreeClassChooserFactory
+ .getInstance(project)
+ .createProjectScopeChooser("Select a class", initialClass);
+ chooser.showDialog();
+
+ if (chooser.getSelected() == null) {
+ return null;
+ }
+ return chooser.getSelected();
+ } catch (final GenerateCodeException e) {
+ Messages.showMessageDialog(project, e.getMessage(), "Generate Failed", null);
+ }
+ return null;
+ }
+
+ private List selectMember(@NotNull final PsiFile file,
+ final MemberSelectionConfig config, final Map contextMap) {
+ final String AVAILABLE_MEMBERS = "availableMembers";
+ final String SELECTED_MEMBERS = "selectedMembers";
+ final Project project = file.getProject();
+
+ logger.debug("start to select members by template: ", config.providerTemplate);
+ velocityEvaluate(project, contextMap, contextMap, config.providerTemplate,settings.getIncludes());
+
+ // members should be MemberEntry[] or PsiMember[]
+ List> availableMembers = Collections.emptyList();
+ List> selectedMembers = Collections.emptyList();
+ if (contextMap.containsKey(AVAILABLE_MEMBERS)) {
+ availableMembers = (List>) contextMap.get(AVAILABLE_MEMBERS);
+ selectedMembers = (List>) contextMap.get(SELECTED_MEMBERS);
+ selectedMembers = (selectedMembers == null ? availableMembers : selectedMembers);
+ }
+
+ contextMap.remove(AVAILABLE_MEMBERS);
+ contextMap.remove(SELECTED_MEMBERS);
+
+ // filter the members by configuration
+ final PsiElementClassMember>[] dialogMembers = buildClassMember(filterMembers(availableMembers, config));
+ final PsiElementClassMember>[] membersSelected = buildClassMember(filterMembers(selectedMembers, config));
+
+ if (!config.allowEmptySelection && dialogMembers.length <= 0) {
+ Messages.showMessageDialog(project,
+ "No members are provided to select from.\nAnd template doesn't allow empty selection",
+ "Warning", Messages.getWarningIcon());
+ return null;
+ }
+
+ final MemberChooser> chooser =
+ new MemberChooser<>(dialogMembers, config.allowEmptySelection,
+ config.allowMultiSelection, project,
+ PsiUtil.isLanguageLevel5OrHigher(file),
+ new JPanel(new BorderLayout())) {
+ @NotNull
+ @Override
+ protected String getHelpId() {
+ return "editing.altInsert.codegenerator";
+ }
+ };
+ chooser.setTitle("Selection Fields for Code Generation");
+ chooser.setCopyJavadocVisible(false);
+ chooser.selectElements(membersSelected);
+ chooser.show();
+
+ if (DialogWrapper.OK_EXIT_CODE != chooser.getExitCode()) {
+ return null; // indicate exit
+ }
+ return convertClassMembersToPsiMembers(chooser.getSelectedElements());
+ }
+
+ private static List filterMembers(final List> members,
+ final MemberSelectionConfig config) {
+ final FilterPattern pattern = generatorConfig2Config(config).getFilterPattern();
+ return members.stream()
+ .map(member -> {
+ if (member instanceof PsiMember) {
+ return (PsiMember) member;
+ } else if (member instanceof MemberEntry) {
+ return (((MemberEntry>) member).getRaw());
+ } else {
+ return null;
+ }
+ }).filter(member -> {
+ if (member instanceof PsiField) {
+ return !pattern.fieldMatches((PsiField) member);
+ }
+ if (config.enableMethods && member instanceof PsiMethod) {
+ return !pattern.methodMatches((PsiMethod) member);
+ } else {
+ return false;
+ }
+ }).collect(Collectors.toList());
+ }
+
+ private static PsiElementClassMember>[] buildClassMember(final List members) {
+ return members
+ .stream()
+ .filter(m -> (m instanceof PsiField) || (m instanceof PsiMethod))
+ .map(m -> {
+ if (m instanceof PsiField) {
+ return new PsiFieldMember((PsiField) m);
+ } else {
+ return new PsiMethodMember((PsiMethod) m);
+ }
+ }).toArray(PsiElementClassMember[]::new);
+ }
+
+ private static Config generatorConfig2Config(final MemberSelectionConfig selectionConfig) {
+ final Config config = new Config();
+ config.useFullyQualifiedName = false;
+ config.filterConstantField = selectionConfig.filterConstantField;
+ config.filterEnumField = selectionConfig.filterEnumField;
+ config.filterTransientModifier = selectionConfig.filterTransientModifier;
+ config.filterStaticModifier = selectionConfig.filterStaticModifier;
+ config.filterFieldName = selectionConfig.filterFieldName;
+ config.filterMethodName = selectionConfig.filterMethodName;
+ config.filterMethodType = selectionConfig.filterMethodType;
+ config.filterFieldType = selectionConfig.filterFieldType;
+ config.filterLoggers = selectionConfig.filterLoggers;
+ config.enableMethods = selectionConfig.enableMethods;
+ return config;
+ }
+
+ private static PsiClass getFirstClass(final PsiFile file) {
+ if (file instanceof PsiJavaFile) {
+ final PsiClass[] classes = ((PsiJavaFile) file).getClasses();
+ return (classes.length > 0 ? classes[0] : null);
+ } else {
+ return null;
+ }
+ }
+
+ private static PsiClass buildFakeClassForEmptyFile(@NotNull final PsiFile file) {
+ final Project project = file.getProject();
+ final VirtualFile moduleRoot = ProjectRootManager
+ .getInstance(project)
+ .getFileIndex()
+ .getSourceRootForFile(file.getVirtualFile());
+ if (moduleRoot == null) {
+ return null;
+ }
+ final String fileName = file.getName();
+ final String className = fileName.replace(".java", "");
+ final String packageName = file
+ .getVirtualFile()
+ .getPath()
+ .substring(moduleRoot.getPath().length() + 1)
+ .replace(File.separator + fileName, "")
+ .replace(File.separator, ".");
+
+ try {
+ final PsiFile element = PsiFileFactory
+ .getInstance(project)
+ .createFileFromText("filename", JavaFileType.INSTANCE,
+ "package " + packageName + ";\n" + "class " + className + "{}");
+ return (PsiClass) element.getLastChild();
+ } catch (final IncorrectOperationException ignore) {
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/me/lotabout/codegenerator/action/CodeGeneratorGroup.java b/src/main/java/me/lotabout/codegenerator/action/CodeGeneratorGroup.java
new file mode 100644
index 0000000..b409f4e
--- /dev/null
+++ b/src/main/java/me/lotabout/codegenerator/action/CodeGeneratorGroup.java
@@ -0,0 +1,73 @@
+package me.lotabout.codegenerator.action;
+
+import java.util.List;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import com.intellij.openapi.actionSystem.ActionGroup;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.LangDataKeys;
+import com.intellij.openapi.actionSystem.PlatformDataKeys;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.editor.Caret;
+import com.intellij.openapi.project.DumbAware;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiJavaFile;
+
+import me.lotabout.codegenerator.CodeGeneratorSettings;
+import me.lotabout.codegenerator.config.CodeTemplate;
+
+public class CodeGeneratorGroup extends ActionGroup implements DumbAware {
+
+ private final CodeGeneratorSettings settings;
+
+ public CodeGeneratorGroup() {
+ settings = ApplicationManager.getApplication().getService(CodeGeneratorSettings.class);
+ }
+
+ @Override
+ @NotNull
+ public AnAction[] getChildren(@Nullable final AnActionEvent anActionEvent) {
+ if (anActionEvent == null) {
+ return EMPTY_ARRAY;
+ }
+ final DataContext context = anActionEvent.getDataContext();
+ final Project project = PlatformDataKeys.PROJECT.getData(context);
+ if (project == null) {
+ return EMPTY_ARRAY;
+ }
+ final PsiFile file = context.getData(LangDataKeys.PSI_FILE);
+ if (file == null) {
+ return EMPTY_ARRAY;
+ }
+ final Caret caret = context.getData(LangDataKeys.CARET);
+ final boolean isProjectView = (caret == null);
+
+ final boolean isJavaFile = (file instanceof PsiJavaFile);
+ final List children = settings
+ .getCodeTemplates()
+ .stream()
+ .filter(t -> t.enabled)
+ .filter(t -> !isProjectView || !t.type.isNeedEditor())
+ .filter(t -> isJavaFile || t.type.isSupportNonJavaFile())
+ // .filter(t -> file.getName().matches(t.fileNamePattern))
+ .map(CodeGeneratorGroup::getOrCreateAction)
+ .toList();
+ return children.toArray(new AnAction[0]);
+ }
+
+ private static AnAction getOrCreateAction(final CodeTemplate template) {
+ final String actionId = "CodeMaker.Menu.Action." + template.getId();
+ AnAction action = ActionManager.getInstance().getAction(actionId);
+ if (action == null) {
+ action = new CodeGeneratorAction(template.getId(), template.name);
+ ActionManager.getInstance().registerAction(actionId, action);
+ }
+ return action;
+ }
+}
diff --git a/src/me/lotabout/codegenerator/config/ClassSelectionConfig.java b/src/main/java/me/lotabout/codegenerator/config/ClassSelectionConfig.java
similarity index 67%
rename from src/me/lotabout/codegenerator/config/ClassSelectionConfig.java
rename to src/main/java/me/lotabout/codegenerator/config/ClassSelectionConfig.java
index 6a174c0..210a263 100644
--- a/src/me/lotabout/codegenerator/config/ClassSelectionConfig.java
+++ b/src/main/java/me/lotabout/codegenerator/config/ClassSelectionConfig.java
@@ -1,5 +1,7 @@
package me.lotabout.codegenerator.config;
+import java.util.Objects;
+
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
@@ -7,10 +9,15 @@
@XmlRootElement(name = "classSelection")
@XmlAccessorType(XmlAccessType.FIELD)
public class ClassSelectionConfig implements PipelineStep {
+
public String initialClass = "$class0.qualifiedName";
+
public boolean enabled = true;
+
public String postfix = "";
- @Override public String type() {
+
+ @Override
+ public String type() {
return "class-selection";
}
@@ -20,7 +27,7 @@ public String postfix() {
}
@Override
- public void postfix(String postfix) {
+ public void postfix(final String postfix) {
this.postfix = postfix;
}
@@ -30,20 +37,24 @@ public boolean enabled() {
}
@Override
- public void enabled(boolean enabled) {
+ public void enabled(final boolean enabled) {
this.enabled = enabled;
}
@Override
- public boolean equals(Object o) {
+ public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
- ClassSelectionConfig that = (ClassSelectionConfig) o;
+ final ClassSelectionConfig that = (ClassSelectionConfig) o;
- if (enabled != that.enabled) return false;
- if (initialClass != null ? !initialClass.equals(that.initialClass) : that.initialClass != null) return false;
- return postfix != null ? postfix.equals(that.postfix) : that.postfix == null;
+ if (enabled != that.enabled) {
+ return false;
+ }
+ if (!Objects.equals(initialClass, that.initialClass)) {
+ return false;
+ }
+ return (postfix != null) ? postfix.equals(that.postfix) : (that.postfix == null);
}
@Override
diff --git a/src/me/lotabout/codegenerator/config/CodeTemplate.java b/src/main/java/me/lotabout/codegenerator/config/CodeTemplate.java
similarity index 73%
rename from src/me/lotabout/codegenerator/config/CodeTemplate.java
rename to src/main/java/me/lotabout/codegenerator/config/CodeTemplate.java
index f8161fc..b460e57 100644
--- a/src/me/lotabout/codegenerator/config/CodeTemplate.java
+++ b/src/main/java/me/lotabout/codegenerator/config/CodeTemplate.java
@@ -1,19 +1,27 @@
package me.lotabout.codegenerator.config;
-import com.intellij.openapi.util.io.FileUtil;
-
-import com.intellij.util.xmlb.annotations.AbstractCollection;
-import org.apache.commons.lang.builder.EqualsBuilder;
-import org.apache.commons.lang.builder.HashCodeBuilder;
-import org.jetbrains.java.generate.config.DuplicationPolicy;
-import org.jetbrains.java.generate.config.InsertWhere;
-
-import javax.xml.bind.annotation.*;
import java.io.IOException;
+import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlElements;
+import javax.xml.bind.annotation.XmlRootElement;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.jetbrains.java.generate.config.DuplicationPolicy;
+import org.jetbrains.java.generate.config.InsertWhere;
+
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.util.xmlb.annotations.XCollection;
+
@XmlRootElement(name = "codeTemplate")
@XmlAccessorType(XmlAccessType.FIELD)
public class CodeTemplate {
@@ -22,8 +30,8 @@ public class CodeTemplate {
private UUID id;
public String name = "Untitled";
- public String fileNamePattern = ".*\\.java$";
- public String type = "body";
+ // public String fileNamePattern = ".*\\.java$";
+ public TemplateType type = TemplateType.BODY;
public boolean enabled = true;
public String template = DEFAULT_TEMPLATE;
public String fileEncoding = DEFAULT_ENCODING;
@@ -32,7 +40,7 @@ public class CodeTemplate {
@XmlElement(name="classSelection", type=ClassSelectionConfig.class)
})
@XmlElementWrapper
- @AbstractCollection(elementTypes = {MemberSelectionConfig.class, ClassSelectionConfig.class})
+ @XCollection(elementTypes = {MemberSelectionConfig.class, ClassSelectionConfig.class})
public List pipeline = new ArrayList<>();
public InsertWhere insertNewMethodOption = InsertWhere.AT_CARET;
@@ -43,10 +51,10 @@ public class CodeTemplate {
public String defaultTargetPackage;
public String defaultTargetModule;
- public CodeTemplate(UUID id) {
+ public CodeTemplate(final UUID id) {
this.id = id;
}
- public CodeTemplate(String id) {
+ public CodeTemplate(final String id) {
this.id = UUID.fromString(id);
}
@@ -73,22 +81,26 @@ public boolean isValid() {
static {
String default_template;
try {
- default_template = FileUtil.loadTextAndClose(CodeTemplate.class.getResourceAsStream("/template/default.vm"));
- } catch (IOException e) {
+ final InputStream in = CodeTemplate.class.getResourceAsStream("/template/default.vm");
+ if (in == null) {
+ throw new IOException("Cannot find default template");
+ }
+ default_template = FileUtil.loadTextAndClose(in);
+ } catch (final IOException e) {
default_template = "";
e.printStackTrace();
}
DEFAULT_TEMPLATE = default_template;
}
- @Override public boolean equals(Object o) {
+ @Override public boolean equals(final Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
- CodeTemplate template1 = (CodeTemplate)o;
+ final CodeTemplate template1 = (CodeTemplate)o;
return new EqualsBuilder()
.append(enabled, template1.enabled)
@@ -96,7 +108,7 @@ public boolean isValid() {
.append(alwaysPromptForPackage, template1.alwaysPromptForPackage)
.append(id, template1.id)
.append(name, template1.name)
- .append(fileNamePattern, template1.fileNamePattern)
+ // .append(fileNamePattern, template1.fileNamePattern)
.append(type, template1.type)
.append(template, template1.template)
.append(fileEncoding, template1.fileEncoding)
@@ -113,7 +125,7 @@ public boolean isValid() {
return new HashCodeBuilder(17, 37)
.append(id)
.append(name)
- .append(fileNamePattern)
+ // .append(fileNamePattern)
.append(type)
.append(enabled)
.append(template)
diff --git a/src/me/lotabout/codegenerator/config/CodeTemplateList.java b/src/main/java/me/lotabout/codegenerator/config/CodeTemplateList.java
similarity index 58%
rename from src/me/lotabout/codegenerator/config/CodeTemplateList.java
rename to src/main/java/me/lotabout/codegenerator/config/CodeTemplateList.java
index c5039c2..80ba9f8 100644
--- a/src/me/lotabout/codegenerator/config/CodeTemplateList.java
+++ b/src/main/java/me/lotabout/codegenerator/config/CodeTemplateList.java
@@ -1,14 +1,15 @@
package me.lotabout.codegenerator.config;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.List;
+
import javax.xml.bind.JAXB;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
-import java.io.StringReader;
-import java.io.StringWriter;
-import java.util.ArrayList;
-import java.util.List;
@XmlAccessorType(XmlAccessType.FIELD)
public class CodeTemplateList {
@@ -17,10 +18,12 @@ public class CodeTemplateList {
private List templates = new ArrayList<>();
public CodeTemplateList() {}
- public CodeTemplateList(List templates) {
+
+ public CodeTemplateList(final List templates) {
this.templates.addAll(templates);
}
- public CodeTemplateList(CodeTemplate template) {
+
+ public CodeTemplateList(final CodeTemplate template) {
this.templates.add(template);
}
@@ -29,26 +32,25 @@ public List getTemplates() {
return templates;
}
- public void setTemplates(List templates) {
+ public void setTemplates(final List templates) {
this.templates = templates;
}
- public static List fromXML(String xml) {
- CodeTemplateList list = JAXB.unmarshal(new StringReader(xml), CodeTemplateList.class);
+ public static List fromXML(final String xml) {
+ final CodeTemplateList list = JAXB.unmarshal(new StringReader(xml), CodeTemplateList.class);
return list.getTemplates();
}
- public static String toXML(List templates) {
- CodeTemplateList templateList = new CodeTemplateList(templates);
- StringWriter sw = new StringWriter();
+ public static String toXML(final List templates) {
+ final CodeTemplateList templateList = new CodeTemplateList(templates);
+ final StringWriter sw = new StringWriter();
JAXB.marshal(templateList, sw);
return sw.toString();
}
- public static String toXML(CodeTemplate templates) {
- CodeTemplateList templateList = new CodeTemplateList(templates);
- StringWriter sw = new StringWriter();
+ public static String toXML(final CodeTemplate templates) {
+ final CodeTemplateList templateList = new CodeTemplateList(templates);
+ final StringWriter sw = new StringWriter();
JAXB.marshal(templateList, sw);
return sw.toString();
}
}
-
diff --git a/src/me/lotabout/codegenerator/config/MemberSelectionConfig.java b/src/main/java/me/lotabout/codegenerator/config/MemberSelectionConfig.java
similarity index 95%
rename from src/me/lotabout/codegenerator/config/MemberSelectionConfig.java
rename to src/main/java/me/lotabout/codegenerator/config/MemberSelectionConfig.java
index a48bf84..7b2d7dd 100644
--- a/src/me/lotabout/codegenerator/config/MemberSelectionConfig.java
+++ b/src/main/java/me/lotabout/codegenerator/config/MemberSelectionConfig.java
@@ -34,7 +34,7 @@ public String postfix() {
}
@Override
- public void postfix(String postfix) {
+ public void postfix(final String postfix) {
this.postfix = postfix;
}
@@ -44,16 +44,16 @@ public boolean enabled() {
}
@Override
- public void enabled(boolean enabled) {
+ public void enabled(final boolean enabled) {
this.enabled = enabled;
}
@Override
- public boolean equals(Object o) {
+ public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
- MemberSelectionConfig that = (MemberSelectionConfig) o;
+ final MemberSelectionConfig that = (MemberSelectionConfig) o;
if (filterConstantField != that.filterConstantField) return false;
if (filterEnumField != that.filterEnumField) return false;
diff --git a/src/me/lotabout/codegenerator/config/PipelineStep.java b/src/main/java/me/lotabout/codegenerator/config/PipelineStep.java
similarity index 100%
rename from src/me/lotabout/codegenerator/config/PipelineStep.java
rename to src/main/java/me/lotabout/codegenerator/config/PipelineStep.java
diff --git a/src/main/java/me/lotabout/codegenerator/config/TemplateType.java b/src/main/java/me/lotabout/codegenerator/config/TemplateType.java
new file mode 100644
index 0000000..d4cbf28
--- /dev/null
+++ b/src/main/java/me/lotabout/codegenerator/config/TemplateType.java
@@ -0,0 +1,55 @@
+package me.lotabout.codegenerator.config;
+
+import javax.xml.bind.annotation.XmlEnum;
+import javax.xml.bind.annotation.XmlEnumValue;
+
+/**
+ * The enumeration of types of code templates.
+ *
+ * @author Haixing Hu
+ */
+@XmlEnum
+public enum TemplateType {
+
+ @XmlEnumValue("body")
+ BODY("body", false, true),
+
+ @XmlEnumValue("class")
+ CLASS("class", false, false),
+
+ @XmlEnumValue("caret")
+ CARET("caret", true, true);
+
+ private final String value;
+
+ private final boolean supportNonJavaFile;
+
+ private final boolean needEditor;
+
+ TemplateType(final String value, final boolean supportNonJavaFile, final boolean needEditor) {
+ this.value = value;
+ this.supportNonJavaFile = supportNonJavaFile;
+ this.needEditor = needEditor;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public boolean isSupportNonJavaFile() {
+ return supportNonJavaFile;
+ }
+
+ public boolean isNeedEditor() {
+ return needEditor;
+ }
+
+ public static TemplateType ofValue(final String value) {
+ for (final TemplateType type : values()) {
+ if (type.value.equals(value)) {
+ return type;
+ }
+ }
+ throw new IllegalArgumentException("Invalid value: " + value);
+ }
+}
diff --git a/src/me/lotabout/codegenerator/config/include/Include.java b/src/main/java/me/lotabout/codegenerator/config/include/Include.java
similarity index 60%
rename from src/me/lotabout/codegenerator/config/include/Include.java
rename to src/main/java/me/lotabout/codegenerator/config/include/Include.java
index 4c9b47a..5175a7e 100644
--- a/src/me/lotabout/codegenerator/config/include/Include.java
+++ b/src/main/java/me/lotabout/codegenerator/config/include/Include.java
@@ -1,13 +1,18 @@
package me.lotabout.codegenerator.config.include;
-import com.intellij.openapi.util.io.FileUtil;
-import org.apache.commons.lang.builder.EqualsBuilder;
-import org.apache.commons.lang.builder.HashCodeBuilder;
-
-import javax.xml.bind.annotation.*;
import java.io.IOException;
import java.util.UUID;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlRootElement;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+
+import com.intellij.openapi.util.io.FileUtil;
+
@XmlRootElement(name = "include")
@XmlAccessorType(XmlAccessType.FIELD)
public class Include {
@@ -19,11 +24,11 @@ public class Include {
public String content = DEFAULT_TEMPLATE;
public boolean defaultInclude;
- public Include(UUID id) {
+ public Include(final UUID id) {
this.id = id;
}
- public Include(String id) {
+ public Include(final String id) {
this.id = UUID.fromString(id);
}
@@ -47,15 +52,15 @@ public String getContent() {
return content;
}
- public void setId(UUID id) {
+ public void setId(final UUID id) {
this.id = id;
}
- public void setName(String name) {
+ public void setName(final String name) {
this.name = name;
}
- public void setContent(String content) {
+ public void setContent(final String content) {
this.content = content;
}
@@ -63,7 +68,7 @@ public boolean isDefaultInclude() {
return defaultInclude;
}
- public void setDefaultInclude(boolean defaultInclude) {
+ public void setDefaultInclude(final boolean defaultInclude) {
this.defaultInclude = defaultInclude;
}
@@ -73,7 +78,7 @@ public void setDefaultInclude(boolean defaultInclude) {
String default_template;
try {
default_template = FileUtil.loadTextAndClose(Include.class.getResourceAsStream("/template/default-include.vm"));
- } catch (IOException e) {
+ } catch (final IOException e) {
default_template = "";
e.printStackTrace();
}
@@ -81,18 +86,20 @@ public void setDefaultInclude(boolean defaultInclude) {
}
@Override
- public boolean equals(Object o) {
- if (this == o) return true;
-
- if (o == null || getClass() != o.getClass()) return false;
-
- var include1 = (Include) o;
-
- return new EqualsBuilder().append(id, include1.id)//
- .append(name, include1.name)//
- .append(content, include1.content)//
- .append(defaultInclude, include1.defaultInclude)
- .isEquals();
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final var include1 = (Include) o;
+ return new EqualsBuilder()
+ .append(id, include1.id)//
+ .append(name, include1.name)//
+ .append(content, include1.content)//
+ .append(defaultInclude, include1.defaultInclude)
+ .isEquals();
}
@Override
diff --git a/src/me/lotabout/codegenerator/config/include/IncludeList.java b/src/main/java/me/lotabout/codegenerator/config/include/IncludeList.java
similarity index 62%
rename from src/me/lotabout/codegenerator/config/include/IncludeList.java
rename to src/main/java/me/lotabout/codegenerator/config/include/IncludeList.java
index 4414d9b..c7a6160 100644
--- a/src/me/lotabout/codegenerator/config/include/IncludeList.java
+++ b/src/main/java/me/lotabout/codegenerator/config/include/IncludeList.java
@@ -1,14 +1,15 @@
package me.lotabout.codegenerator.config.include;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.List;
+
import javax.xml.bind.JAXB;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
-import java.io.StringReader;
-import java.io.StringWriter;
-import java.util.ArrayList;
-import java.util.List;
@XmlAccessorType(XmlAccessType.FIELD)
public class IncludeList {
@@ -19,11 +20,11 @@ public class IncludeList {
public IncludeList() {
}
- public IncludeList(List includes) {
+ public IncludeList(final List includes) {
this.includes.addAll(includes);
}
- public IncludeList(Include template) {
+ public IncludeList(final Include template) {
this.includes.add(template);
}
@@ -32,27 +33,26 @@ public List getIncludes() {
return includes;
}
- public void setIncludes(List includes) {
+ public void setIncludes(final List includes) {
this.includes = includes;
}
- public static List fromXML(String xml) {
- var list = JAXB.unmarshal(new StringReader(xml), IncludeList.class);
+ public static List fromXML(final String xml) {
+ final var list = JAXB.unmarshal(new StringReader(xml), IncludeList.class);
return list.getIncludes();
}
- public static String toXML(List templates) {
- var templateList = new IncludeList(templates);
- var sw = new StringWriter();
+ public static String toXML(final List templates) {
+ final var templateList = new IncludeList(templates);
+ final var sw = new StringWriter();
JAXB.marshal(templateList, sw);
return sw.toString();
}
- public static String toXML(Include templates) {
- var templateList = new IncludeList(templates);
- var sw = new StringWriter();
+ public static String toXML(final Include templates) {
+ final var templateList = new IncludeList(templates);
+ final var sw = new StringWriter();
JAXB.marshal(templateList, sw);
return sw.toString();
}
}
-
diff --git a/src/me/lotabout/codegenerator/ui/ClassSelectionPane.form b/src/main/java/me/lotabout/codegenerator/ui/ClassSelectionPane.form
similarity index 100%
rename from src/me/lotabout/codegenerator/ui/ClassSelectionPane.form
rename to src/main/java/me/lotabout/codegenerator/ui/ClassSelectionPane.form
diff --git a/src/me/lotabout/codegenerator/ui/ClassSelectionPane.java b/src/main/java/me/lotabout/codegenerator/ui/ClassSelectionPane.java
similarity index 72%
rename from src/me/lotabout/codegenerator/ui/ClassSelectionPane.java
rename to src/main/java/me/lotabout/codegenerator/ui/ClassSelectionPane.java
index 2d705c6..85af453 100644
--- a/src/me/lotabout/codegenerator/ui/ClassSelectionPane.java
+++ b/src/main/java/me/lotabout/codegenerator/ui/ClassSelectionPane.java
@@ -1,20 +1,21 @@
package me.lotabout.codegenerator.ui;
-import me.lotabout.codegenerator.config.ClassSelectionConfig;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
-import javax.swing.*;
+import me.lotabout.codegenerator.config.ClassSelectionConfig;
public class ClassSelectionPane implements PipelineStepConfig {
private JPanel topPane;
private JTextField initialClassText;
- public ClassSelectionPane(ClassSelectionConfig config) {
+ public ClassSelectionPane(final ClassSelectionConfig config) {
initialClassText.setText(config.initialClass);
}
@Override
public ClassSelectionConfig getConfig() {
- ClassSelectionConfig config = new ClassSelectionConfig();
+ final ClassSelectionConfig config = new ClassSelectionConfig();
config.initialClass = initialClassText.getText();
return config;
}
diff --git a/src/me/lotabout/codegenerator/ui/CodeGeneratorConfig.form b/src/main/java/me/lotabout/codegenerator/ui/CodeGeneratorConfig.form
similarity index 100%
rename from src/me/lotabout/codegenerator/ui/CodeGeneratorConfig.form
rename to src/main/java/me/lotabout/codegenerator/ui/CodeGeneratorConfig.form
diff --git a/src/me/lotabout/codegenerator/ui/CodeGeneratorConfig.java b/src/main/java/me/lotabout/codegenerator/ui/CodeGeneratorConfig.java
similarity index 61%
rename from src/me/lotabout/codegenerator/ui/CodeGeneratorConfig.java
rename to src/main/java/me/lotabout/codegenerator/ui/CodeGeneratorConfig.java
index 15039f7..26afc42 100644
--- a/src/me/lotabout/codegenerator/ui/CodeGeneratorConfig.java
+++ b/src/main/java/me/lotabout/codegenerator/ui/CodeGeneratorConfig.java
@@ -1,33 +1,36 @@
package me.lotabout.codegenerator.ui;
+import java.awt.event.ActionEvent;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+
+import javax.swing.DefaultListModel;
+import javax.swing.JButton;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+
import com.intellij.openapi.fileChooser.FileChooser;
-import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.ui.Messages;
-import java.util.concurrent.CompletableFuture;
-
import me.lotabout.codegenerator.CodeGeneratorSettings;
import me.lotabout.codegenerator.config.CodeTemplate;
import me.lotabout.codegenerator.config.CodeTemplateList;
-import javax.swing.*;
-import java.awt.event.ActionEvent;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.StandardOpenOption;
-import java.util.ArrayList;
-import java.util.List;
-
public class CodeGeneratorConfig {
private JPanel mainPane;
private JButton addTemplateButton;
private JSplitPane splitPane;
private JList templateList;
- private DefaultListModel templateListModel;
+ private final DefaultListModel templateListModel;
private JButton deleteTemplateButton;
private JPanel splitRightPane;
private JScrollPane scrollPane;
@@ -36,9 +39,9 @@ public class CodeGeneratorConfig {
private JButton exportAllButton;
private JButton duplicateTemplateButton;
- private static String DEFAULT_EXPORT_PATH = "code-generator.xml";
+ private static final String DEFAULT_EXPORT_PATH = "code-generator.xml";
- public CodeGeneratorConfig(CodeGeneratorSettings settings) {
+ public CodeGeneratorConfig(final CodeGeneratorSettings settings) {
this.templateListModel = new DefaultListModel<>();
this.templateList.setModel(templateListModel);
@@ -47,8 +50,8 @@ public CodeGeneratorConfig(CodeGeneratorSettings settings) {
return;
}
- var length = templateListModel.getSize();
- var index = templateList.getSelectedIndex();
+ final var length = templateListModel.getSize();
+ final var index = templateList.getSelectedIndex();
if (length < 0 || index < 0 || index >= length) {
splitPane.setRightComponent(splitRightPane);
deleteTemplateButton.setEnabled(false);
@@ -56,52 +59,53 @@ public CodeGeneratorConfig(CodeGeneratorSettings settings) {
return;
}
- var pane = templateListModel.get(templateList.getSelectedIndex());
+ final var pane = templateListModel.get(templateList.getSelectedIndex());
deleteTemplateButton.setEnabled(true);
duplicateTemplateButton.setEnabled(true);
splitPane.setRightComponent(pane.templateEdit());
});
addTemplateButton.addActionListener(e -> {
- var template = new CodeTemplate();
+ final var template = new CodeTemplate();
template.name = "Untitled";
- var editPane = new TemplateEditPane(template);
- var model = (DefaultListModel) templateList.getModel();
+ final var editPane = new TemplateEditPane(template);
+ final var model = (DefaultListModel) templateList.getModel();
model.addElement(editPane);
templateList.setSelectedIndex(model.getSize() - 1);
});
deleteTemplateButton.addActionListener(e -> {
- var index = templateList.getSelectedIndex();
- var size = templateListModel.getSize();
- if (index >= 0 && index < size) {
- var result = Messages.showYesNoDialog("Delete this template?", "Delete", null);
+ final var selectedIndices = templateList.getSelectedIndices(); // Get all selected indices
+ final var size = templateListModel.getSize();
+ if (selectedIndices.length > 0) {
+ final var result = Messages.showYesNoDialog("Delete selected templates?", "Delete", null);
if (result == Messages.OK) {
- var lastIndex = templateList.getAnchorSelectionIndex();
- templateListModel.remove(index);
-
- var nextIndex = -1;
- if (lastIndex >= 0 && lastIndex < index || lastIndex == index && index < size - 1) {
- nextIndex = lastIndex;
- } else if (lastIndex == index || lastIndex > index && lastIndex < size - 1) {
- nextIndex = lastIndex - 1;
- } else if (lastIndex >= index) {
- nextIndex = size - 2; // should not be here
+ // Remove selected items in reverse order to avoid index shifting
+ for (int i = selectedIndices.length - 1; i >= 0; i--) {
+ templateListModel.remove(selectedIndices[i]);
+ }
+ // Update selection logic
+ final var newSize = templateListModel.getSize();
+ if (newSize > 0) {
+ // Attempt to select the first remaining item (if any)
+ templateList.setSelectedIndex(Math.min(selectedIndices[0], newSize - 1));
+ } else {
+ // Clear selection if the list is empty
+ templateList.clearSelection();
}
- templateList.setSelectedIndex(nextIndex);
}
}
});
duplicateTemplateButton.addActionListener(e -> {
- var index = templateList.getSelectedIndex();
+ final var index = templateList.getSelectedIndex();
if (index < 0) {
return;
}
- var template = templateListModel.get(index);
- var xml = CodeTemplateList.toXML(template.getCodeTemplate());
- var currentTemplates = getTabTemplates();
- var templates = CodeTemplateList.fromXML(xml);
+ final var template = templateListModel.get(index);
+ final var xml = CodeTemplateList.toXML(template.getCodeTemplate());
+ final var currentTemplates = getTabTemplates();
+ final var templates = CodeTemplateList.fromXML(xml);
if (templates == null || templates.isEmpty()) {
return;
}
@@ -112,33 +116,33 @@ public CodeGeneratorConfig(CodeGeneratorSettings settings) {
templateList.setSelectedIndex(templateListModel.getSize() - 1);
});
- exportButton.addActionListener((ActionEvent e) -> {
- var index = templateList.getSelectedIndex();
- var template = templateListModel.get(index);
+ exportButton.addActionListener((final ActionEvent e) -> {
+ final var index = templateList.getSelectedIndex();
+ final var template = templateListModel.get(index);
- var xml = CodeTemplateList.toXML(template.getCodeTemplate());
+ final var xml = CodeTemplateList.toXML(template.getCodeTemplate());
saveToFile(xml);
});
- exportAllButton.addActionListener((ActionEvent e) -> {
- List templates = new ArrayList<>();
+ exportAllButton.addActionListener((final ActionEvent e) -> {
+ final List templates = new ArrayList<>();
for (var i = 0; i < templateListModel.getSize(); i++) {
templates.add(templateListModel.get(i).getCodeTemplate());
}
- var xml = CodeTemplateList.toXML(templates);
+ final var xml = CodeTemplateList.toXML(templates);
saveToFile(xml);
});
importButton.addActionListener(e -> {
readFromFile().thenAccept(xml -> {
try {
- var templates = CodeTemplateList.fromXML(xml);
- var currentTemplates = getTabTemplates();
+ final var templates = CodeTemplateList.fromXML(xml);
+ final var currentTemplates = getTabTemplates();
currentTemplates.addAll(templates);
refresh(currentTemplates);
Messages.showMessageDialog("Import finished!", "Import", null);
- } catch (Exception ex) {
+ } catch (final Exception ex) {
ex.printStackTrace();
Messages.showMessageDialog("Fail to import\n" + ex.getMessage(), "Import Error", null);
}
@@ -148,26 +152,26 @@ public CodeGeneratorConfig(CodeGeneratorSettings settings) {
resetTabPane(settings.getCodeTemplates());
}
- public void refresh(List templates) {
+ public void refresh(final List templates) {
templateListModel.removeAllElements();
resetTabPane(templates);
}
- private void saveToFile(String content) {
- final var descriptor = FileChooserDescriptorFactory.createSingleLocalFileDescriptor();
+ private void saveToFile(final String content) {
+ final var descriptor = FileChooserDescriptorFactory.createSingleFileOrFolderDescriptor();
descriptor.setTitle("Choose Directory to Export");
descriptor.setDescription("save to directory/" + DEFAULT_EXPORT_PATH + " or the file to overwrite");
FileChooser.chooseFile(descriptor, null, mainPane, null, virtualFile -> {
- String targetPath;
+ final String targetPath;
if (virtualFile.isDirectory()) {
targetPath = virtualFile.getPath() + '/' + DEFAULT_EXPORT_PATH;
} else {
targetPath = virtualFile.getPath();
}
- var path = Paths.get(targetPath);
+ final var path = Paths.get(targetPath);
if (virtualFile.isDirectory() && Files.exists(path)) {
- var result = Messages.showYesNoDialog("Overwrite the file?\n" + path, "Overwrite", null);
+ final var result = Messages.showYesNoDialog("Overwrite the file?\n" + path, "Overwrite", null);
if (result != Messages.OK) {
return;
}
@@ -176,7 +180,7 @@ private void saveToFile(String content) {
try {
Files.write(path, content.getBytes(), StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE);
Messages.showMessageDialog("Exported to \n" + path, "Export Successful", null);
- } catch (IOException e) {
+ } catch (final IOException e) {
e.printStackTrace();
Messages.showMessageDialog("Error occurred\n" + e.getMessage(), "Export Error", null);
}
@@ -187,14 +191,15 @@ private CompletableFuture readFromFile() {
final var descriptor = FileChooserDescriptorFactory.createSingleFileDescriptor("xml");
descriptor.setTitle("Choose File to Import");
final var result = new CompletableFuture();
- FileChooser.chooseFile(descriptor, null, mainPane, null, virtualFile -> result.complete(FileDocumentManager.getInstance().getDocument(virtualFile).getText()));
+ FileChooser.chooseFile(descriptor, null, mainPane, null,
+ virtualFile -> result.complete(FileDocumentManager.getInstance().getDocument(virtualFile).getText()));
return result;
}
- private void resetTabPane(List templates) {
+ private void resetTabPane(final List templates) {
templates.forEach(template -> {
if (template == null) return;
- var editPane = new TemplateEditPane(template);
+ final var editPane = new TemplateEditPane(template);
templateListModel.addElement(editPane);
});
@@ -203,9 +208,9 @@ private void resetTabPane(List templates) {
}
public List getTabTemplates() {
- List ret = new ArrayList<>();
+ final List ret = new ArrayList<>();
for (var i = 0; i < templateListModel.getSize(); i++) {
- var value = templateListModel.get(i);
+ final var value = templateListModel.get(i);
ret.add(value.getCodeTemplate());
}
diff --git a/src/me/lotabout/codegenerator/ui/CodeGeneratorConfigurable.java b/src/main/java/me/lotabout/codegenerator/ui/CodeGeneratorConfigurable.java
similarity index 65%
rename from src/me/lotabout/codegenerator/ui/CodeGeneratorConfigurable.java
rename to src/main/java/me/lotabout/codegenerator/ui/CodeGeneratorConfigurable.java
index 3f9d9e1..b65d835 100644
--- a/src/me/lotabout/codegenerator/ui/CodeGeneratorConfigurable.java
+++ b/src/main/java/me/lotabout/codegenerator/ui/CodeGeneratorConfigurable.java
@@ -1,24 +1,27 @@
package me.lotabout.codegenerator.ui;
-import com.intellij.openapi.components.ServiceManager;
-import com.intellij.openapi.options.ConfigurationException;
-import com.intellij.openapi.options.SearchableConfigurable;
-import me.lotabout.codegenerator.CodeGeneratorSettings;
-import me.lotabout.codegenerator.ui.include.IncludeConfig;
+import javax.swing.JComponent;
+
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import javax.swing.*;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.options.ConfigurationException;
+import com.intellij.openapi.options.SearchableConfigurable;
+import com.intellij.openapi.util.Disposer;
+
+import me.lotabout.codegenerator.CodeGeneratorSettings;
+import me.lotabout.codegenerator.ui.include.IncludeConfig;
public class CodeGeneratorConfigurable implements SearchableConfigurable {
- private CodeGeneratorSettings settings;
+ private final CodeGeneratorSettings settings;
private CodeGeneratorConfig codeGeneratorConfig;
private IncludeConfig includeConfig;
private MainPaneConfig mainPaneConfig;
public CodeGeneratorConfigurable() {
- this.settings = ServiceManager.getService(CodeGeneratorSettings.class);
+ this.settings = ApplicationManager.getApplication().getService(CodeGeneratorSettings.class);
}
@NotNull
@@ -67,13 +70,13 @@ private boolean isCodeGeneratorModified() {
return false;
}
- var templates = codeGeneratorConfig.getTabTemplates();
+ final var templates = codeGeneratorConfig.getTabTemplates();
if (settings.getCodeTemplates().size() != templates.size()) {
return true;
}
- for (var template : templates) {
- var codeTemplate = settings.getCodeTemplate(template.getId());
+ for (final var template : templates) {
+ final var codeTemplate = settings.getCodeTemplate(template.getId());
if (codeTemplate.isEmpty() || !codeTemplate.get().equals(template)) {
return true;
}
@@ -87,13 +90,13 @@ private boolean isIncludeModified() {
return false;
}
- var includes = includeConfig.getIncludes();
+ final var includes = includeConfig.getIncludes();
if (settings.getIncludes().size() != includes.size()) {
return true;
}
- for (var include : includes) {
- var includesSetting = settings.getInclude(include.getId());
+ for (final var include : includes) {
+ final var includesSetting = settings.getInclude(include.getId());
if (includesSetting.isEmpty() || !includesSetting.get().equals(include)) {
return true;
}
@@ -104,8 +107,12 @@ private boolean isIncludeModified() {
@Override
public void apply() throws ConfigurationException {
- var templates = codeGeneratorConfig.getTabTemplates();
- for (var template : templates) {
+ if (codeGeneratorConfig == null) {
+ return;
+ }
+
+ final var templates = codeGeneratorConfig.getTabTemplates();
+ for (final var template : templates) {
if (!template.isValid()) {
throw new ConfigurationException(
"Not property can be empty and classNumber should be a number");
@@ -113,14 +120,30 @@ public void apply() throws ConfigurationException {
}
settings.setCodeTemplates(templates);
- settings.setIncludes(includeConfig.getIncludes());
+
+ if (includeConfig != null) {
+ settings.setIncludes(includeConfig.getIncludes());
+ includeConfig.refresh(includeConfig.getIncludes());
+ }
codeGeneratorConfig.refresh(templates);
- includeConfig.refresh(includeConfig.getIncludes());
}
@Override
public void reset() {
-
+ if (codeGeneratorConfig != null) {
+ codeGeneratorConfig.refresh(settings.getCodeTemplates());
+ }
+
+ if (includeConfig != null) {
+ includeConfig.refresh(settings.getIncludes());
+ }
+ }
+
+ @Override
+ public void disposeUIResources() {
+ codeGeneratorConfig = null;
+ includeConfig = null;
+ mainPaneConfig = null;
}
}
diff --git a/src/me/lotabout/codegenerator/ui/MainPaneConfig.form b/src/main/java/me/lotabout/codegenerator/ui/MainPaneConfig.form
similarity index 100%
rename from src/me/lotabout/codegenerator/ui/MainPaneConfig.form
rename to src/main/java/me/lotabout/codegenerator/ui/MainPaneConfig.form
diff --git a/src/me/lotabout/codegenerator/ui/MainPaneConfig.java b/src/main/java/me/lotabout/codegenerator/ui/MainPaneConfig.java
similarity index 69%
rename from src/me/lotabout/codegenerator/ui/MainPaneConfig.java
rename to src/main/java/me/lotabout/codegenerator/ui/MainPaneConfig.java
index 632ecd4..2b71375 100644
--- a/src/me/lotabout/codegenerator/ui/MainPaneConfig.java
+++ b/src/main/java/me/lotabout/codegenerator/ui/MainPaneConfig.java
@@ -1,15 +1,17 @@
package me.lotabout.codegenerator.ui;
-import me.lotabout.codegenerator.ui.include.IncludeConfig;
+import javax.swing.JPanel;
+import javax.swing.JTabbedPane;
-import javax.swing.*;
+import me.lotabout.codegenerator.ui.include.IncludeConfig;
public class MainPaneConfig {
private JPanel mainPanel;
private JTabbedPane tabbedPane;
- public MainPaneConfig(CodeGeneratorConfig codeGeneratorConfig, IncludeConfig includeConfig) {
+ public MainPaneConfig(final CodeGeneratorConfig codeGeneratorConfig,
+ final IncludeConfig includeConfig) {
tabbedPane.add("Code Templates", codeGeneratorConfig.getMainPane());
tabbedPane.add("Includes", includeConfig.getMainPane());
}
diff --git a/src/me/lotabout/codegenerator/ui/MemberSelectionPane.form b/src/main/java/me/lotabout/codegenerator/ui/MemberSelectionPane.form
similarity index 100%
rename from src/me/lotabout/codegenerator/ui/MemberSelectionPane.form
rename to src/main/java/me/lotabout/codegenerator/ui/MemberSelectionPane.form
diff --git a/src/me/lotabout/codegenerator/ui/MemberSelectionPane.java b/src/main/java/me/lotabout/codegenerator/ui/MemberSelectionPane.java
similarity index 87%
rename from src/me/lotabout/codegenerator/ui/MemberSelectionPane.java
rename to src/main/java/me/lotabout/codegenerator/ui/MemberSelectionPane.java
index 552144b..56424d2 100644
--- a/src/me/lotabout/codegenerator/ui/MemberSelectionPane.java
+++ b/src/main/java/me/lotabout/codegenerator/ui/MemberSelectionPane.java
@@ -1,16 +1,22 @@
package me.lotabout.codegenerator.ui;
+import java.awt.Dimension;
+
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.fileTypes.FileTypeManager;
import com.intellij.uiDesigner.core.GridConstraints;
+
import me.lotabout.codegenerator.config.MemberSelectionConfig;
import me.lotabout.codegenerator.config.PipelineStep;
-import javax.swing.*;
-import java.awt.*;
-
public class MemberSelectionPane implements PipelineStepConfig {
private JPanel editorPane;
private JCheckBox excludeConstantFieldsCheckBox;
@@ -30,7 +36,7 @@ public class MemberSelectionPane implements PipelineStepConfig {
private JCheckBox allowEmptySelectionCheckBox;
private Editor editor;
- MemberSelectionPane(MemberSelectionConfig config) {
+ MemberSelectionPane(final MemberSelectionConfig config) {
excludeConstantFieldsCheckBox.setSelected(config.filterConstantField);
excludeStaticFieldsCheckBox.setSelected(config.filterStaticModifier);
excludeTransientFieldsCheckBox.setSelected(config.filterTransientModifier);
@@ -57,12 +63,12 @@ private int sortElements() {
return comboBoxSortElements.getSelectedIndex() + 1;
}
- private void addVmEditor(String template) {
- EditorFactory factory = EditorFactory.getInstance();
- Document velocityTemplate = factory.createDocument(template);
+ private void addVmEditor(final String template) {
+ final EditorFactory factory = EditorFactory.getInstance();
+ final Document velocityTemplate = factory.createDocument(template);
editor = factory.createEditor(velocityTemplate, null, FileTypeManager.getInstance()
.getFileTypeByExtension("vm"), false);
- GridConstraints constraints = new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST,
+ final GridConstraints constraints = new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST,
GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_WANT_GROW,
GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(0, 0), null, 0, true);
@@ -71,7 +77,7 @@ private void addVmEditor(String template) {
@Override
public PipelineStep getConfig() {
- MemberSelectionConfig config = new MemberSelectionConfig();
+ final MemberSelectionConfig config = new MemberSelectionConfig();
config.filterConstantField = excludeConstantFieldsCheckBox.isSelected();
config.filterEnumField = excludeEnumFieldsCheckBox.isSelected();
config.filterTransientModifier = excludeTransientFieldsCheckBox.isSelected();
diff --git a/src/me/lotabout/codegenerator/ui/PipelineStepConfig.java b/src/main/java/me/lotabout/codegenerator/ui/PipelineStepConfig.java
similarity index 86%
rename from src/me/lotabout/codegenerator/ui/PipelineStepConfig.java
rename to src/main/java/me/lotabout/codegenerator/ui/PipelineStepConfig.java
index 9ab9538..dcf73bc 100644
--- a/src/me/lotabout/codegenerator/ui/PipelineStepConfig.java
+++ b/src/main/java/me/lotabout/codegenerator/ui/PipelineStepConfig.java
@@ -1,8 +1,8 @@
package me.lotabout.codegenerator.ui;
-import me.lotabout.codegenerator.config.PipelineStep;
+import javax.swing.JComponent;
-import javax.swing.*;
+import me.lotabout.codegenerator.config.PipelineStep;
public interface PipelineStepConfig {
PipelineStep getConfig();
diff --git a/src/me/lotabout/codegenerator/ui/SelectionPane.form b/src/main/java/me/lotabout/codegenerator/ui/SelectionPane.form
similarity index 100%
rename from src/me/lotabout/codegenerator/ui/SelectionPane.form
rename to src/main/java/me/lotabout/codegenerator/ui/SelectionPane.form
diff --git a/src/me/lotabout/codegenerator/ui/SelectionPane.java b/src/main/java/me/lotabout/codegenerator/ui/SelectionPane.java
similarity index 78%
rename from src/me/lotabout/codegenerator/ui/SelectionPane.java
rename to src/main/java/me/lotabout/codegenerator/ui/SelectionPane.java
index c7c0d0d..caf0c5a 100644
--- a/src/me/lotabout/codegenerator/ui/SelectionPane.java
+++ b/src/main/java/me/lotabout/codegenerator/ui/SelectionPane.java
@@ -1,12 +1,18 @@
package me.lotabout.codegenerator.ui;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextField;
+
import com.intellij.openapi.ui.Messages;
+
import me.lotabout.codegenerator.config.ClassSelectionConfig;
import me.lotabout.codegenerator.config.MemberSelectionConfig;
import me.lotabout.codegenerator.config.PipelineStep;
-import javax.swing.*;
-
public class SelectionPane implements PipelineStepConfig {
private JTextField postfixText;
private JCheckBox enableStepCheckBox;
@@ -14,14 +20,14 @@ public class SelectionPane implements PipelineStepConfig {
private JPanel topPanel;
private JScrollPane contentPane;
- private Object selectionPane;
+ private final Object selectionPane;
- public SelectionPane(PipelineStep config, TemplateEditPane parent) {
+ public SelectionPane(final PipelineStep config, final TemplateEditPane parent) {
postfixText.setText(config.postfix());
enableStepCheckBox.setSelected(config.enabled());
removeThisStepButton.addActionListener(e -> {
- int result = Messages.showYesNoDialog("Really remove this step?", "Delete", null);
+ final int result = Messages.showYesNoDialog("Really remove this step?", "Delete", null);
if (result == Messages.OK) {
parent.removePipelineStep(this);
}
@@ -47,12 +53,12 @@ public boolean enabled() {
@Override public PipelineStep getConfig() {
if (selectionPane instanceof MemberSelectionPane) {
- PipelineStep step = ((MemberSelectionPane)selectionPane).getConfig();
+ final PipelineStep step = ((MemberSelectionPane) selectionPane).getConfig();
step.postfix(this.postfix());
step.enabled(this.enabled());
return step;
} else if (selectionPane instanceof ClassSelectionPane) {
- PipelineStep step = ((ClassSelectionPane)selectionPane).getConfig();
+ final PipelineStep step = ((ClassSelectionPane) selectionPane).getConfig();
step.postfix(this.postfix());
step.enabled(this.enabled());
return step;
diff --git a/src/me/lotabout/codegenerator/ui/TemplateEditPane.form b/src/main/java/me/lotabout/codegenerator/ui/TemplateEditPane.form
similarity index 100%
rename from src/me/lotabout/codegenerator/ui/TemplateEditPane.form
rename to src/main/java/me/lotabout/codegenerator/ui/TemplateEditPane.form
diff --git a/src/me/lotabout/codegenerator/ui/TemplateEditPane.java b/src/main/java/me/lotabout/codegenerator/ui/TemplateEditPane.java
similarity index 70%
rename from src/me/lotabout/codegenerator/ui/TemplateEditPane.java
rename to src/main/java/me/lotabout/codegenerator/ui/TemplateEditPane.java
index ac58840..54b4feb 100644
--- a/src/me/lotabout/codegenerator/ui/TemplateEditPane.java
+++ b/src/main/java/me/lotabout/codegenerator/ui/TemplateEditPane.java
@@ -1,28 +1,39 @@
package me.lotabout.codegenerator.ui;
+import java.awt.Dimension;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JScrollPane;
+import javax.swing.JTabbedPane;
+import javax.swing.JTextField;
+
+import org.jetbrains.java.generate.config.DuplicationPolicy;
+import org.jetbrains.java.generate.config.InsertWhere;
+
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.fileTypes.FileTypeManager;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.uiDesigner.core.GridConstraints;
+
import me.lotabout.codegenerator.config.ClassSelectionConfig;
import me.lotabout.codegenerator.config.CodeTemplate;
import me.lotabout.codegenerator.config.MemberSelectionConfig;
import me.lotabout.codegenerator.config.PipelineStep;
-import org.jetbrains.java.generate.config.DuplicationPolicy;
-import org.jetbrains.java.generate.config.InsertWhere;
-
-import javax.swing.*;
-import java.awt.*;
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.List;
-import java.util.stream.Collectors;
+import me.lotabout.codegenerator.config.TemplateType;
public class TemplateEditPane {
private JPanel templateEdit;
- private JComboBox templateTypeCombo;
+ private JComboBox templateTypeCombo;
private JTextField templateIdText;
private JTextField templateNameText;
private JPanel editorPane;
@@ -43,16 +54,16 @@ public class TemplateEditPane {
private JTextField defaultTargetPackageText;
private JTextField defaultTargetModuleText;
private Editor editor;
- private List pipeline = new ArrayList<>();
+ private final List pipeline = new ArrayList<>();
- public TemplateEditPane(CodeTemplate codeTemplate) {
+ public TemplateEditPane(final CodeTemplate codeTemplate) {
settingsPanel.getVerticalScrollBar().setUnitIncrement(16); // scroll speed
templateIdText.setText(codeTemplate.getId());
templateNameText.setText(codeTemplate.name);
templateEnabledCheckBox.setSelected(codeTemplate.enabled);
fileEncodingText.setText(StringUtil.notNullize(codeTemplate.fileEncoding, CodeTemplate.DEFAULT_ENCODING));
- templateTypeCombo.setSelectedItem(codeTemplate.type);
+ templateTypeCombo.setSelectedItem(codeTemplate.type.getValue());
jumpToMethodCheckBox.setSelected(codeTemplate.jumpToMethod);
classNameVmText.setText(codeTemplate.classNameVm);
defaultTargetPackageText.setText(codeTemplate.defaultTargetPackage);
@@ -62,41 +73,52 @@ public TemplateEditPane(CodeTemplate codeTemplate) {
askRadioButton.setSelected(false);
replaceExistingRadioButton.setSelected(false);
generateDuplicateMemberRadioButton.setSelected(false);
- switch (codeTemplate.whenDuplicatesOption) {
- case ASK:
- askRadioButton.setSelected(true);
- break;
- case REPLACE:
- replaceExistingRadioButton.setSelected(true);
- break;
- case DUPLICATE:
- generateDuplicateMemberRadioButton.setSelected(true);
- break;
+
+ if (codeTemplate.whenDuplicatesOption == null) {
+ askRadioButton.setSelected(true);
+ } else {
+ switch (codeTemplate.whenDuplicatesOption) {
+ case ASK:
+ askRadioButton.setSelected(true);
+ break;
+ case REPLACE:
+ replaceExistingRadioButton.setSelected(true);
+ break;
+ case DUPLICATE:
+ generateDuplicateMemberRadioButton.setSelected(true);
+ break;
+ }
}
atCaretRadioButton.setSelected(false);
atEndOfClassRadioButton.setSelected(false);
- switch (codeTemplate.insertNewMethodOption) {
- case AT_CARET:
- atCaretRadioButton.setSelected(true);
- break;
- case AT_THE_END_OF_A_CLASS:
- atEndOfClassRadioButton.setSelected(true);
- break;
- default:
- break;
+
+ if (codeTemplate.insertNewMethodOption == null) {
+ atCaretRadioButton.setSelected(true);
+ } else {
+ switch (codeTemplate.insertNewMethodOption) {
+ case AT_CARET:
+ atCaretRadioButton.setSelected(true);
+ break;
+ case AT_THE_END_OF_A_CLASS:
+ atEndOfClassRadioButton.setSelected(true);
+ break;
+ default:
+ atCaretRadioButton.setSelected(true);
+ break;
+ }
}
codeTemplate.pipeline.forEach(this::addMemberSelection);
addMemberButton.addActionListener(e -> {
- int currentStep = findMaxStepPostfix(pipeline, "member");
- MemberSelectionConfig config = new MemberSelectionConfig();
+ final int currentStep = findMaxStepPostfix(pipeline, "member");
+ final MemberSelectionConfig config = new MemberSelectionConfig();
config.postfix = String.valueOf(currentStep + 1);
addMemberSelection(config);
});
addClassButton.addActionListener(e -> {
- int currentStep = findMaxStepPostfix(pipeline, "class");
- ClassSelectionConfig config = new ClassSelectionConfig();
+ final int currentStep = findMaxStepPostfix(pipeline, "class");
+ final ClassSelectionConfig config = new ClassSelectionConfig();
config.postfix = String.valueOf(currentStep + 1);
addMemberSelection(config);
});
@@ -104,7 +126,7 @@ public TemplateEditPane(CodeTemplate codeTemplate) {
addVmEditor(codeTemplate.template);
}
- private static int findMaxStepPostfix(List pipelinePanes, String type) {
+ private static int findMaxStepPostfix(final List pipelinePanes, final String type) {
return pipelinePanes.stream()
.filter(p -> p.type().equals(type))
.map(SelectionPane::postfix)
@@ -114,7 +136,7 @@ private static int findMaxStepPostfix(List pipelinePanes, String
.orElse(0);
}
- private void addMemberSelection(PipelineStep step) {
+ private void addMemberSelection(final PipelineStep step) {
if (step == null) {
return;
}
@@ -126,17 +148,17 @@ private void addMemberSelection(PipelineStep step) {
title = "Class";
}
- SelectionPane pane = new SelectionPane(step, this);
+ final SelectionPane pane = new SelectionPane(step, this);
pipeline.add(pane);
templateTabbedPane.addTab(title, pane.getComponent());
}
- private void addVmEditor(String template) {
- EditorFactory factory = EditorFactory.getInstance();
- Document velocityTemplate = factory.createDocument(template);
+ private void addVmEditor(final String template) {
+ final EditorFactory factory = EditorFactory.getInstance();
+ final Document velocityTemplate = factory.createDocument(template);
editor = factory.createEditor(velocityTemplate, null, FileTypeManager.getInstance()
.getFileTypeByExtension("vm"), false);
- GridConstraints constraints = new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST,
+ final GridConstraints constraints = new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST,
GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_WANT_GROW,
GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(0, 0), null, 0, true);
@@ -216,9 +238,9 @@ public String toString() {
}
public CodeTemplate getCodeTemplate() {
- CodeTemplate template = new CodeTemplate(this.id());
+ final CodeTemplate template = new CodeTemplate(this.id());
template.name = this.name();
- template.type = this.type();
+ template.type = TemplateType.ofValue(this.type());
template.enabled = this.enabled();
template.fileEncoding = this.fileEncoding();
template.template = this.template();
@@ -234,9 +256,9 @@ public CodeTemplate getCodeTemplate() {
return template;
}
- public void removePipelineStep(PipelineStepConfig stepToRemove) {
- int index = this.pipeline.indexOf(stepToRemove);
- PipelineStepConfig step = this.pipeline.remove(index);
+ public void removePipelineStep(final SelectionPane stepToRemove) {
+ final int index = this.pipeline.indexOf(stepToRemove);
+ final PipelineStepConfig step = this.pipeline.remove(index);
this.templateTabbedPane.remove(step.getComponent());
}
}
diff --git a/src/me/lotabout/codegenerator/ui/include/IncludeConfig.form b/src/main/java/me/lotabout/codegenerator/ui/include/IncludeConfig.form
similarity index 100%
rename from src/me/lotabout/codegenerator/ui/include/IncludeConfig.form
rename to src/main/java/me/lotabout/codegenerator/ui/include/IncludeConfig.form
diff --git a/src/me/lotabout/codegenerator/ui/include/IncludeConfig.java b/src/main/java/me/lotabout/codegenerator/ui/include/IncludeConfig.java
similarity index 70%
rename from src/me/lotabout/codegenerator/ui/include/IncludeConfig.java
rename to src/main/java/me/lotabout/codegenerator/ui/include/IncludeConfig.java
index 86c3cba..297d250 100644
--- a/src/me/lotabout/codegenerator/ui/include/IncludeConfig.java
+++ b/src/main/java/me/lotabout/codegenerator/ui/include/IncludeConfig.java
@@ -1,14 +1,5 @@
package me.lotabout.codegenerator.ui.include;
-import com.intellij.openapi.fileChooser.FileChooser;
-import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
-import com.intellij.openapi.fileEditor.FileDocumentManager;
-import com.intellij.openapi.ui.Messages;
-import me.lotabout.codegenerator.CodeGeneratorSettings;
-import me.lotabout.codegenerator.config.include.Include;
-import me.lotabout.codegenerator.config.include.IncludeList;
-
-import javax.swing.*;
import java.awt.event.ActionEvent;
import java.io.IOException;
import java.nio.file.Files;
@@ -18,12 +9,28 @@
import java.util.List;
import java.util.concurrent.CompletableFuture;
+import javax.swing.DefaultListModel;
+import javax.swing.JButton;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+
+import com.intellij.openapi.fileChooser.FileChooser;
+import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.ui.Messages;
+
+import me.lotabout.codegenerator.CodeGeneratorSettings;
+import me.lotabout.codegenerator.config.include.Include;
+import me.lotabout.codegenerator.config.include.IncludeList;
+
public class IncludeConfig {
private JPanel mainPane;
private JButton addTemplateButton;
private JSplitPane splitPane;
private JList includeList;
- private DefaultListModel includeListModel;
+ private final DefaultListModel includeListModel;
private JButton deleteButton;
private JPanel splitRightPane;
private JScrollPane scrollPane;
@@ -31,9 +38,9 @@ public class IncludeConfig {
private JButton exportButton;
private JButton exportAllButton;
- private static String DEFAULT_EXPORT_PATH = "template.xml";
+ private static final String DEFAULT_EXPORT_PATH = "template.xml";
- public IncludeConfig(CodeGeneratorSettings settings) {
+ public IncludeConfig(final CodeGeneratorSettings settings) {
this.includeListModel = new DefaultListModel<>();
this.includeList.setModel(includeListModel);
@@ -42,35 +49,35 @@ public IncludeConfig(CodeGeneratorSettings settings) {
return;
}
- var length = includeListModel.getSize();
- var index = includeList.getSelectedIndex();
+ final var length = includeListModel.getSize();
+ final var index = includeList.getSelectedIndex();
if (length < 0 || index < 0 || index >= length) {
splitPane.setRightComponent(splitRightPane);
deleteButton.setEnabled(false);
return;
}
- var pane = includeListModel.get(includeList.getSelectedIndex());
+ final var pane = includeListModel.get(includeList.getSelectedIndex());
deleteButton.setEnabled(true);
splitPane.setRightComponent(pane.templateEdit());
});
addTemplateButton.addActionListener(e -> {
- var template = new Include();
+ final var template = new Include();
template.name = "Untitled";
- var editPane = new IncludeEditPane(template);
- var model = (DefaultListModel) includeList.getModel();
+ final var editPane = new IncludeEditPane(template);
+ final var model = (DefaultListModel) includeList.getModel();
model.addElement(editPane);
includeList.setSelectedIndex(model.getSize()-1);
});
deleteButton.addActionListener(e -> {
- var index = includeList.getSelectedIndex();
- var size = includeListModel.getSize();
+ final var index = includeList.getSelectedIndex();
+ final var size = includeListModel.getSize();
if (index >= 0 && index < size) {
- var result = Messages.showYesNoDialog("Delete this template?", "Delete", null);
+ final var result = Messages.showYesNoDialog("Delete this template?", "Delete", null);
if (result == Messages.OK) {
- var lastIndex = includeList.getAnchorSelectionIndex();
+ final var lastIndex = includeList.getAnchorSelectionIndex();
includeListModel.remove(index);
var nextIndex = -1;
@@ -86,31 +93,31 @@ public IncludeConfig(CodeGeneratorSettings settings) {
}
});
- exportButton.addActionListener((ActionEvent e) -> {
- var index = includeList.getSelectedIndex();
- var includeModel = includeListModel.get(index);
- var xml = IncludeList.toXML(includeModel.getInclude());
+ exportButton.addActionListener((final ActionEvent e) -> {
+ final var index = includeList.getSelectedIndex();
+ final var includeModel = includeListModel.get(index);
+ final var xml = IncludeList.toXML(includeModel.getInclude());
saveToFile(xml);
});
- exportAllButton.addActionListener((ActionEvent e) -> {
- List templates = new ArrayList<>();
+ exportAllButton.addActionListener((final ActionEvent e) -> {
+ final List templates = new ArrayList<>();
for (var i = 0; i< includeListModel.getSize(); i++) {
templates.add(includeListModel.get(i).getInclude());
}
- var xml = IncludeList.toXML(templates);
+ final var xml = IncludeList.toXML(templates);
saveToFile(xml);
});
importButton.addActionListener(e -> {
readFromFile().thenAccept(xml -> {
try {
- var templates = IncludeList.fromXML(xml);
- var currentTemplates = getIncludes();
+ final var templates = IncludeList.fromXML(xml);
+ final var currentTemplates = getIncludes();
currentTemplates.addAll(templates);
refresh(currentTemplates);
Messages.showMessageDialog("Import finished!", "Import", null);
- } catch (Exception ex) {
+ } catch (final Exception ex) {
ex.printStackTrace();
Messages.showMessageDialog("Fail to import\n"+ex.getMessage(), "Import Error", null);
}
@@ -120,26 +127,26 @@ public IncludeConfig(CodeGeneratorSettings settings) {
resetTabPane(settings.getIncludes());
}
- public void refresh(List templates) {
+ public void refresh(final List templates) {
includeListModel.removeAllElements();
resetTabPane(templates);
}
- private void saveToFile(String content) {
- final var descriptor = FileChooserDescriptorFactory.createSingleLocalFileDescriptor();
+ private void saveToFile(final String content) {
+ final var descriptor = FileChooserDescriptorFactory.createSingleFileOrFolderDescriptor();
descriptor.setTitle("Choose Directory to Export");
descriptor.setDescription("save to directory/"+DEFAULT_EXPORT_PATH + " or the file to overwrite");
FileChooser.chooseFile(descriptor, null, mainPane, null, virtualFile -> {
- String targetPath;
+ final String targetPath;
if (virtualFile.isDirectory()) {
targetPath = virtualFile.getPath() + '/' + DEFAULT_EXPORT_PATH;
} else {
targetPath = virtualFile.getPath();
}
- var path = Paths.get(targetPath);
+ final var path = Paths.get(targetPath);
if (virtualFile.isDirectory() && Files.exists(path)) {
- var result = Messages.showYesNoDialog("Overwrite the file?\n" + path, "Overwrite", null);
+ final var result = Messages.showYesNoDialog("Overwrite the file?\n" + path, "Overwrite", null);
if (result != Messages.OK) {
return;
}
@@ -148,7 +155,7 @@ private void saveToFile(String content) {
try {
Files.write(path, content.getBytes(), StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE);
Messages.showMessageDialog("Exported to \n"+path, "Export Successful", null);
- } catch (IOException e) {
+ } catch (final IOException e) {
e.printStackTrace();
Messages.showMessageDialog("Error occurred\n"+e.getMessage(), "Export Error", null);
}
@@ -163,10 +170,10 @@ private CompletableFuture readFromFile() {
return result;
}
- private void resetTabPane(List includes) {
+ private void resetTabPane(final List includes) {
includes.forEach(include -> {
if (include == null) return;
- var editPane = new IncludeEditPane(include);
+ final var editPane = new IncludeEditPane(include);
includeListModel.addElement(editPane);
});
@@ -175,9 +182,9 @@ private void resetTabPane(List includes) {
}
public List getIncludes() {
- List ret = new ArrayList<>();
+ final List ret = new ArrayList<>();
for (var i = 0; i< includeListModel.getSize(); i++) {
- var value = includeListModel.get(i);
+ final var value = includeListModel.get(i);
ret.add(value.getInclude());
}
diff --git a/src/me/lotabout/codegenerator/ui/include/IncludeEditPane.form b/src/main/java/me/lotabout/codegenerator/ui/include/IncludeEditPane.form
similarity index 100%
rename from src/me/lotabout/codegenerator/ui/include/IncludeEditPane.form
rename to src/main/java/me/lotabout/codegenerator/ui/include/IncludeEditPane.form
diff --git a/src/me/lotabout/codegenerator/ui/include/IncludeEditPane.java b/src/main/java/me/lotabout/codegenerator/ui/include/IncludeEditPane.java
similarity index 69%
rename from src/me/lotabout/codegenerator/ui/include/IncludeEditPane.java
rename to src/main/java/me/lotabout/codegenerator/ui/include/IncludeEditPane.java
index 5d48d3b..6033b3e 100644
--- a/src/me/lotabout/codegenerator/ui/include/IncludeEditPane.java
+++ b/src/main/java/me/lotabout/codegenerator/ui/include/IncludeEditPane.java
@@ -1,13 +1,17 @@
package me.lotabout.codegenerator.ui.include;
+import java.awt.Dimension;
+
+import javax.swing.JCheckBox;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.fileTypes.FileTypeManager;
import com.intellij.uiDesigner.core.GridConstraints;
-import me.lotabout.codegenerator.config.include.Include;
-import javax.swing.*;
-import java.awt.*;
+import me.lotabout.codegenerator.config.include.Include;
public class IncludeEditPane {
private JPanel templateEdit;
@@ -17,22 +21,22 @@ public class IncludeEditPane {
private JCheckBox defaultInclude;
private Editor editor;
- public IncludeEditPane(Include include) {
+ public IncludeEditPane(final Include include) {
templateIdText.setText(include.getId());
templateNameText.setText(include.getName());
defaultInclude.setSelected(include.isDefaultInclude());
addVmEditor(include.getContent());
}
-
- private void addVmEditor(String template) {
- var factory = EditorFactory.getInstance();
- var velocityTemplate = factory.createDocument(template);
+ private void addVmEditor(final String template) {
+ final var factory = EditorFactory.getInstance();
+ final var velocityTemplate = factory.createDocument(template);
editor = factory.createEditor(velocityTemplate, null, FileTypeManager.getInstance()
.getFileTypeByExtension("vm"), false);
- var constraints = new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST,
- GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_WANT_GROW,
- GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(0, 0), null, 0, true);
+ final var constraints = new GridConstraints(0, 0, 1, 1,
+ GridConstraints.ANCHOR_WEST, GridConstraints.FILL_BOTH,
+ GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED,
+ null, new Dimension(0, 0), null, 0, true);
editorPane.add(editor.getComponent(), constraints);
}
@@ -59,7 +63,7 @@ public String toString() {
}
public Include getInclude() {
- var include = new Include(this.id());
+ final var include = new Include(this.id());
include.setName(this.name());
include.setContent(this.content());
include.setDefaultInclude(defaultInclude.isSelected());
diff --git a/src/me/lotabout/codegenerator/util/AnnotationEntry.java b/src/main/java/me/lotabout/codegenerator/util/AnnotationEntry.java
similarity index 90%
rename from src/me/lotabout/codegenerator/util/AnnotationEntry.java
rename to src/main/java/me/lotabout/codegenerator/util/AnnotationEntry.java
index 5908f74..ec14a2b 100644
--- a/src/me/lotabout/codegenerator/util/AnnotationEntry.java
+++ b/src/main/java/me/lotabout/codegenerator/util/AnnotationEntry.java
@@ -1,13 +1,14 @@
package me.lotabout.codegenerator.util;
-import com.intellij.psi.PsiAnnotation;
import org.jetbrains.annotations.NotNull;
+import com.intellij.psi.PsiAnnotation;
+
public class AnnotationEntry {
private final String qualifiedName;
private final PsiAnnotation psiAnnotation;
- public AnnotationEntry(@NotNull PsiAnnotation annotation) {
+ public AnnotationEntry(@NotNull final PsiAnnotation annotation) {
this.psiAnnotation = annotation;
this.qualifiedName = annotation.getQualifiedName();
}
diff --git a/src/me/lotabout/codegenerator/util/AnnotationUtil.java b/src/main/java/me/lotabout/codegenerator/util/AnnotationUtil.java
similarity index 76%
rename from src/me/lotabout/codegenerator/util/AnnotationUtil.java
rename to src/main/java/me/lotabout/codegenerator/util/AnnotationUtil.java
index 1a02521..1a5c2c3 100644
--- a/src/me/lotabout/codegenerator/util/AnnotationUtil.java
+++ b/src/main/java/me/lotabout/codegenerator/util/AnnotationUtil.java
@@ -1,16 +1,18 @@
package me.lotabout.codegenerator.util;
-import com.intellij.psi.PsiJvmModifiersOwner;
-import org.jetbrains.annotations.NotNull;
-
import java.util.Arrays;
import java.util.Objects;
+import org.jetbrains.annotations.NotNull;
+
+import com.intellij.psi.PsiJvmModifiersOwner;
+
public class AnnotationUtil {
private AnnotationUtil() {
}
- public static boolean isAnnotatedWith(@NotNull PsiJvmModifiersOwner psiJvmModifiersOwner, @NotNull String qualifiedName) {
+ public static boolean isAnnotatedWith(@NotNull final PsiJvmModifiersOwner psiJvmModifiersOwner,
+ @NotNull final String qualifiedName) {
return Arrays.stream(psiJvmModifiersOwner.getAnnotations())
.filter(annotation -> Objects.nonNull(annotation.getQualifiedName()))
.anyMatch(annotation -> annotation.getQualifiedName().equals(qualifiedName));
diff --git a/src/main/java/me/lotabout/codegenerator/util/EntryFactory.java b/src/main/java/me/lotabout/codegenerator/util/EntryFactory.java
new file mode 100644
index 0000000..d6b2999
--- /dev/null
+++ b/src/main/java/me/lotabout/codegenerator/util/EntryFactory.java
@@ -0,0 +1,31 @@
+package me.lotabout.codegenerator.util;
+
+import org.jetbrains.java.generate.element.FieldElement;
+
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiField;
+import com.intellij.psi.PsiMethod;
+
+public class EntryFactory {
+ public static FieldEntry of(final PsiField field, final boolean useAccessor) {
+ if (field == null) {
+ return null;
+ }
+ return FieldEntry.of(field, useAccessor);
+ }
+
+ public static FieldEntry of(final PsiClass clazz, final FieldElement element) {
+ if (clazz == null || element == null) {
+ return null;
+ }
+ final PsiField field = clazz.findFieldByName(element.getName(), true);
+ return field != null ? FieldEntry.of(field, false) : null;
+ }
+
+ public static MethodEntry of(final PsiMethod method) {
+ if (method == null) {
+ return null;
+ }
+ return MethodEntry.of(method);
+ }
+}
diff --git a/src/main/java/me/lotabout/codegenerator/util/EntryUtils.java b/src/main/java/me/lotabout/codegenerator/util/EntryUtils.java
new file mode 100644
index 0000000..90a2052
--- /dev/null
+++ b/src/main/java/me/lotabout/codegenerator/util/EntryUtils.java
@@ -0,0 +1,59 @@
+package me.lotabout.codegenerator.util;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import com.intellij.psi.PsiField;
+import com.intellij.psi.PsiMember;
+import com.intellij.psi.PsiMethod;
+
+public class EntryUtils {
+
+ public static List> getOnlyAsFieldAndMethodElements(
+ final Collection extends PsiMember> members,
+ final boolean useAccessors) {
+ final List> entryList = new ArrayList<>();
+ for (final PsiMember member : members) {
+ MemberEntry> entry = null;
+ if (member instanceof PsiField) {
+ entry = EntryFactory.of((PsiField) member, useAccessors);
+ } else if (member instanceof PsiMethod) {
+ entry = EntryFactory.of((PsiMethod) member);
+ }
+
+ if (entry != null) {
+ entryList.add(entry);
+ }
+ }
+ return entryList;
+ }
+
+ public static List getOnlyAsFieldEntries(
+ final Collection extends PsiMember> members,
+ final boolean useAccessors) {
+ final List fieldEntryList = new ArrayList<>();
+
+ for (final PsiMember member : members) {
+ if (member instanceof final PsiField field) {
+ final FieldEntry fe = EntryFactory.of(field, useAccessors);
+ fieldEntryList.add(fe);
+ }
+ }
+
+ return fieldEntryList;
+ }
+
+ public static List getOnlyAsMethodEntries(final Collection extends PsiMember> members) {
+ final List methodEntryList = new ArrayList<>();
+
+ for (final PsiMember member : members) {
+ if (member instanceof final PsiMethod method) {
+ final MethodEntry me = EntryFactory.of(method);
+ methodEntryList.add(me);
+ }
+ }
+
+ return methodEntryList;
+ }
+}
diff --git a/src/main/java/me/lotabout/codegenerator/util/FieldEntry.java b/src/main/java/me/lotabout/codegenerator/util/FieldEntry.java
new file mode 100644
index 0000000..f405334
--- /dev/null
+++ b/src/main/java/me/lotabout/codegenerator/util/FieldEntry.java
@@ -0,0 +1,163 @@
+package me.lotabout.codegenerator.util;
+
+import java.lang.ref.WeakReference;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.java.generate.element.ElementFactory;
+import org.jetbrains.java.generate.element.FieldElement;
+
+import com.intellij.psi.PsiField;
+import com.intellij.psi.PsiTypeElement;
+
+/**
+ * Wrapper around FieldElement that provides caching and utility methods for field information.
+ * This class is immutable and thread-safe.
+ */
+public class FieldEntry implements MemberEntry {
+ // Use ConcurrentHashMap as cache to ensure thread safety
+ private static final ConcurrentHashMap> CACHE = new ConcurrentHashMap<>();
+
+ /**
+ * Factory method to create or retrieve a FieldEntry instance.
+ * Uses caching to avoid creating duplicate instances for the same PsiField.
+ *
+ * @param field The PsiField to create a FieldEntry for
+ * @param useAccessor Whether to use accessor methods
+ * @return A new or cached FieldEntry instance
+ */
+ public static FieldEntry of(final PsiField field, final boolean useAccessor) {
+ if (field == null) {
+ return null;
+ }
+ // Try to get from cache first
+ final WeakReference ref = CACHE.get(field);
+ FieldEntry entry = ref != null ? ref.get() : null;
+ if (entry != null) {
+ return entry;
+ }
+ // Create new instance if not in cache
+ entry = new FieldEntry(field, ElementFactory.newFieldElement(field, useAccessor));
+ // Use putIfAbsent to ensure thread safety
+ final WeakReference existing = CACHE.putIfAbsent(field, new WeakReference<>(entry));
+ return existing != null && existing.get() != null ? existing.get() : entry;
+ }
+
+ private final PsiField raw;
+ private final FieldElement element;
+ private volatile TypeEntry type; // Cache for field type
+
+ /**
+ * Private constructor to enforce instance creation through factory method.
+ * Initializes all fields and caches type information.
+ *
+ * @param field The PsiField to create a FieldEntry for
+ * @param element The FieldElement wrapper
+ */
+ private FieldEntry(final PsiField field, final FieldElement element) {
+ this.raw = field;
+ this.element = element;
+ }
+
+ @Override
+ public TypeEntry getType() {
+ TypeEntry result = type;
+ if (result == null) {
+ synchronized (this) {
+ result = type;
+ if (result == null) {
+ type = result = initType();
+ }
+ }
+ }
+ return result;
+ }
+
+ private TypeEntry initType() {
+ if (raw == null) {
+ return null;
+ }
+ final PsiTypeElement psiTypeElement = raw.getTypeElement();
+ return TypeEntry.of(psiTypeElement);
+ }
+
+ @Override
+ public PsiField getRaw() {
+ return raw;
+ }
+
+ @Override
+ public FieldElement getElement() {
+ return element;
+ }
+
+ boolean isConstant() {
+ return element.isConstant();
+ }
+
+ public boolean isEnum() {
+ return element.isEnum();
+ }
+
+ public boolean matchName(final String s) throws IllegalArgumentException {
+ return element.matchName(s);
+ }
+
+ @Override
+ public boolean isAnnotatedWith(@NotNull final String qualifiedName) {
+ return AnnotationUtil.isAnnotatedWith(raw, qualifiedName);
+ }
+
+ public boolean isModifierTransient() {
+ return element.isModifierTransient();
+ }
+
+ public boolean isModifierVolatile() {
+ return element.isModifierVolatile();
+ }
+
+ @Nullable
+ public TypeEntry getElementType() {
+ return type != null ? type.getElementType() : null;
+ }
+
+ @Nullable
+ public TypeEntry getKeyType() {
+ return type != null ? type.getKeyType() : null;
+ }
+
+ @Nullable
+ public TypeEntry getValueType() {
+ return type != null ? type.getValueType() : null;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final FieldEntry that = (FieldEntry) o;
+ return new EqualsBuilder().append(raw, that.raw).isEquals();
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder(17, 37).append(raw).toHashCode();
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .append("raw", raw)
+ .append("element", element)
+ .append("type", type)
+ .toString();
+ }
+}
diff --git a/src/main/java/me/lotabout/codegenerator/util/GenerationUtil.java b/src/main/java/me/lotabout/codegenerator/util/GenerationUtil.java
new file mode 100644
index 0000000..61fa6cd
--- /dev/null
+++ b/src/main/java/me/lotabout/codegenerator/util/GenerationUtil.java
@@ -0,0 +1,365 @@
+package me.lotabout.codegenerator.util;
+
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.context.Context;
+import org.apache.velocity.tools.ToolManager;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.java.generate.element.GenerationHelper;
+import org.jetbrains.java.generate.exception.GenerateCodeException;
+import org.jetbrains.java.generate.exception.PluginException;
+import org.jetbrains.java.generate.velocity.VelocityFactory;
+
+import com.intellij.application.options.CodeStyle;
+import com.intellij.codeInsight.generation.PsiElementClassMember;
+import com.intellij.codeInsight.generation.PsiFieldMember;
+import com.intellij.codeInsight.generation.PsiMethodMember;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.progress.ProcessCanceledException;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.psi.JavaPsiFacade;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiField;
+import com.intellij.psi.PsiImportStatement;
+import com.intellij.psi.PsiJavaFile;
+import com.intellij.psi.PsiMember;
+import com.intellij.psi.PsiMethod;
+import com.intellij.psi.PsiNamedElement;
+import com.intellij.psi.codeStyle.NameUtil;
+import com.intellij.psi.search.GlobalSearchScope;
+import com.intellij.psi.search.PsiShortNamesCache;
+
+import me.lotabout.codegenerator.config.include.Include;
+
+public class GenerationUtil {
+
+ public static final String VELOCITY_TOOLS_CONFIG = "/velocity-tools.xml";
+
+ private static final Logger logger = Logger.getInstance(GenerationUtil.class);
+
+ /**
+ * Combines the two lists into one list of members.
+ *
+ * @param filteredFields fields to be included in the dialog
+ * @param filteredMethods methods to be included in the dialog
+ * @return the combined list
+ */
+ public static PsiElementClassMember>[] combineToClassMemberList(final PsiField[] filteredFields,
+ final PsiMethod[] filteredMethods) {
+ final PsiElementClassMember>[] members =
+ new PsiElementClassMember[filteredFields.length + filteredMethods.length];
+ // first add fields
+ for (var i = 0; i < filteredFields.length; i++) {
+ members[i] = new PsiFieldMember(filteredFields[i]);
+ }
+ // then add methods
+ for (var i = 0; i < filteredMethods.length; i++) {
+ members[filteredFields.length + i] = new PsiMethodMember(filteredMethods[i]);
+ }
+ return members;
+ }
+
+ public static List convertClassMembersToPsiMembers(@Nullable final List> classMemberList) {
+ if (classMemberList == null || classMemberList.isEmpty()) {
+ return Collections.emptyList();
+ }
+ final List psiMemberList = new ArrayList<>();
+ for (final var classMember : classMemberList) {
+ psiMemberList.add(classMember.getElement());
+ }
+ return psiMemberList;
+ }
+
+ public static void insertMembersToContext(final List members,
+ final Map context,
+ final String postfix, final int sortElements) {
+ logger.debug("insertMembersToContext - adding fields");
+ // field information
+ final List fieldElements = EntryUtils.getOnlyAsFieldEntries(members, false);
+ context.put("fields" + postfix, fieldElements);
+ context.put("fields", fieldElements);
+ if (fieldElements.size() == 1) {
+ context.put("field" + postfix, fieldElements.get(0));
+ context.put("field", fieldElements.get(0));
+ }
+
+ // method information
+ logger.debug("insertMembersToContext - adding members");
+ context.put("methods" + postfix, EntryUtils.getOnlyAsMethodEntries(members));
+ context.put("methods", EntryUtils.getOnlyAsMethodEntries(members));
+
+ // element information (both fields and methods)
+ logger.debug("Velocity Context - adding members (fields and methods)");
+ final List> elements = EntryUtils.getOnlyAsFieldAndMethodElements(members, false);
+ // sort elements if enabled and not using chooser dialog
+ if (sortElements != 0) {
+ elements.sort(new MemberEntryComparator(sortElements));
+ }
+ context.put("members" + postfix, elements);
+ context.put("members", elements);
+ }
+
+ public static String velocityEvaluate(
+ @NotNull final Project project,
+ @NotNull final Map contextMap,
+ @Nullable final Map outputContext,
+ @Nullable final String template,
+ @NotNull final List includes) throws GenerateCodeException {
+ contextMap.put("settings", CodeStyle.getSettings(project));
+ contextMap.put("project", project);
+ contextMap.put("helper", GenerationHelper.class);
+ contextMap.put("StringUtil", StringUtil.class);
+ contextMap.put("StringUtilEx", StringUtilEx.class);
+ contextMap.put("NameUtil", NameUtil.class);
+ contextMap.put("NameUtilEx", NameUtilEx.class);
+ contextMap.put("PsiShortNamesCache", PsiShortNamesCache.class);
+ contextMap.put("JavaPsiFacade", JavaPsiFacade.class);
+ contextMap.put("GlobalSearchScope", GlobalSearchScope.class);
+ contextMap.put("EntryFactory", EntryFactory.class);
+ return velocityEvaluate(contextMap, outputContext, template, includes);
+ }
+
+ // split this method to make unit testing easier
+ public static String velocityEvaluate(
+ @NotNull final Map contextMap,
+ @Nullable final Map outputContext,
+ @Nullable String template,
+ @NotNull final List includes) throws GenerateCodeException {
+ if (template == null) {
+ return null;
+ }
+ final StringWriter sw = new StringWriter();
+ try {
+ final ToolManager toolManager = new ToolManager(false, true);
+ toolManager.configure(VELOCITY_TOOLS_CONFIG);
+ final Context vc = toolManager.createContext();
+ for (final var paramName : contextMap.keySet()) {
+ vc.put(paramName, contextMap.get(paramName));
+ }
+ template = updateTemplateWithIncludes(template, includes);
+ logger.debug("Velocity Template:\n", template);
+ // velocity
+ final VelocityEngine velocity = VelocityFactory.getVelocityEngine();
+ logger.debug("Executing velocity +++ START +++");
+ velocity.evaluate(vc, sw, GenerationUtil.class.getName(), template);
+ logger.debug("Executing velocity +++ END +++");
+ if (outputContext != null) {
+ for (final String key : vc.getKeys()) {
+ if (key != null) {
+ outputContext.put(key, vc.get(key));
+ }
+ }
+ }
+ } catch (final ProcessCanceledException e) {
+ logger.error("Error in Velocity code generator: " + e.getMessage(), e);
+ throw e;
+ } catch (final Exception e) {
+ logger.error("Error in Velocity code generator: " + e.getMessage(), e);
+ throw new GenerateCodeException("Error in Velocity code generator", e);
+ }
+ final String result = StringUtil.convertLineSeparators(sw.toString());
+ logger.debug("Velocity Result:\n", result);
+ return result;
+ }
+
+ @NotNull
+ private static String updateTemplateWithIncludes(final String template, final List includes) {
+ final var includeLookups = getParsedIncludeLookupItems(includes);
+ final var defaultImportParseExpression = includeLookups
+ .stream()
+ .filter(IncludeLookupItem::isDefaultInclude)
+ .map(i -> String.format("#parse(%s)", i.getName()))
+ .collect(Collectors.joining(System.lineSeparator()));
+ final var templateWithDefaultImports = defaultImportParseExpression + System.lineSeparator() + template;
+ return replaceParseExpressions(templateWithDefaultImports, includeLookups);
+ }
+
+ @NotNull
+ private static List getParsedIncludeLookupItems(final List includes) {
+ final var includeLookups = includes.stream()
+ .map(include -> new IncludeLookupItem(include.getName(), include.getContent(), include.isDefaultInclude()))
+ .collect(Collectors.toList());
+
+ return includeLookups.stream()
+ .map(i -> new IncludeLookupItem(i.getName(), replaceParseExpressions(i.getContent(), includeLookups), i.isDefaultInclude()))
+ .collect(Collectors.toList());
+ }
+
+ @NotNull
+ private static String replaceParseExpressions(@NotNull String template, @NotNull final List includeLookupItems) {
+ template = template.lines()//
+ .map(line -> replaceParseExpression(line, includeLookupItems))//
+ .collect(Collectors.joining(System.lineSeparator()));
+ return template;
+ }
+
+ private static String replaceParseExpression(final String line, final List includeLookupItems) {
+ if (line.trim().startsWith("#parse")) {
+ final var includeName = line.trim().replace("#parse(", "")
+ .replace(")", "")
+ .replaceAll("\"", "");
+ final var includeContent = includeLookupItems.stream()
+ .filter(m -> m.getName().equals(includeName))
+ .map(IncludeLookupItem::getContent)
+ .findFirst();
+ if (includeContent.isPresent()) {
+ return includeContent.get();
+ }
+ }
+ return line;
+ }
+
+
+ /**
+ * Handles any exception during the executing on this plugin.
+ *
+ * @param project PSI project
+ * @param e the caused exception.
+ * @throws RuntimeException is thrown for severe exceptions
+ */
+ public static void handleException(final Project project, final Exception e) throws RuntimeException {
+ logger.info(e);
+
+ if (e instanceof GenerateCodeException) {
+ // code generation error - display velocity error in error dialog so user can identify problem quicker
+ Messages.showMessageDialog(project,
+ "Velocity error generating code - see IDEA log for more details (stacktrace should be in idea.log):\n" +
+ e.getMessage(), "Warning", Messages.getWarningIcon());
+ } else if (e instanceof PluginException) {
+ // plugin related error - could be recoverable.
+ Messages.showMessageDialog(project,
+ "A PluginException was thrown while performing the action - see IDEA log for details (stacktrace should be in idea.log):\n"
+ + e.getMessage(), "Warning", Messages.getWarningIcon());
+ } else if (e instanceof RuntimeException) {
+ // unknown error (such as NPE) - not recoverable
+ Messages.showMessageDialog(project,
+ "An unrecoverable exception was thrown while performing the action - see IDEA log for details (stacktrace should be in idea.log):\n"
+ + e.getMessage(), "Error", Messages.getErrorIcon());
+ throw (RuntimeException) e; // throw to make IDEA alert user
+ } else {
+ // unknown error (such as NPE) - not recoverable
+ Messages.showMessageDialog(project,
+ "An unrecoverable exception was thrown while performing the action - see IDEA log for details (stacktrace should be in idea.log):\n"
+ + e.getMessage(), "Error", Messages.getErrorIcon());
+ throw new RuntimeException(e); // rethrow as runtime to make IDEA alert user
+ }
+ }
+
+ static List getFields(final PsiClass clazz) {
+ return Arrays.stream(clazz.getFields())
+ .map(f -> EntryFactory.of(f, false))
+ .collect(Collectors.toList());
+ }
+
+ static List getAllFields(final PsiClass clazz) {
+ return Arrays.stream(clazz.getAllFields())
+ .map(f -> EntryFactory.of(f, false))
+ .collect(Collectors.toList());
+ }
+
+ static List getMethods(final PsiClass clazz) {
+ return Arrays.stream(clazz.getMethods())
+ .map(EntryFactory::of)
+ .collect(Collectors.toList());
+ }
+
+ static List getAllMethods(final PsiClass clazz) {
+ return Arrays.stream(clazz.getAllMethods())
+ .map(EntryFactory::of)
+ .collect(Collectors.toList());
+ }
+
+ static List getImportList(final PsiJavaFile javaFile) {
+ final var importList = javaFile.getImportList();
+ if (importList == null) {
+ return new ArrayList<>();
+ }
+ return Arrays.stream(importList.getImportStatements())
+ .map(PsiImportStatement::getQualifiedName)
+ .collect(Collectors.toList());
+ }
+
+ static List getInnerClasses(final PsiClass clazz) {
+ return Arrays
+ .stream((clazz).getInnerClasses())
+ .map(TypeEntry::of)
+ .collect(Collectors.toList());
+ }
+
+ static List getAllInnerClasses(final PsiClass clazz) {
+ return Arrays
+ .stream((clazz).getAllInnerClasses())
+ .map(TypeEntry::of)
+ .collect(Collectors.toList());
+ }
+
+ static List getClassTypeParameters(final PsiClass psiClass) {
+ return Arrays.stream(psiClass.getTypeParameters()).map(PsiNamedElement::getName).collect(Collectors.toList());
+ }
+
+ static Set getAllInterfaces(final PsiClass clazz) {
+ final Set result = new HashSet<>();
+ // 1. Check direct interfaces
+ for (final PsiClass iface : clazz.getInterfaces()) {
+ result.add(iface);
+ result.addAll(getAllInterfaces(iface)); // recursive
+ }
+ // 2. check super class
+ final PsiClass superClass = clazz.getSuperClass();
+ if (superClass != null) {
+ result.addAll(getAllInterfaces(superClass)); // recursive
+ }
+ return result;
+ }
+
+ static String getPackageName(final String qualifiedName) {
+ final int pos = qualifiedName.lastIndexOf('.');
+ if (pos == -1) {
+ return "";
+ } else {
+ return qualifiedName.substring(0, pos);
+ }
+ }
+
+
+ static final class IncludeLookupItem {
+ @NotNull
+ private final String name;
+ @NotNull
+ private final String content;
+ private final boolean defaultInclude;
+
+ IncludeLookupItem(@NotNull final String name, @NotNull final String content, final boolean defaultInclude) {
+ this.name = name;
+ this.content = content;
+ this.defaultInclude = defaultInclude;
+ }
+
+ @NotNull
+ public String getName() {
+ return name;
+ }
+
+ @NotNull
+ public String getContent() {
+ return content;
+ }
+
+ public boolean isDefaultInclude() {
+ return defaultInclude;
+ }
+ }
+
+}
diff --git a/src/main/java/me/lotabout/codegenerator/util/MemberEntry.java b/src/main/java/me/lotabout/codegenerator/util/MemberEntry.java
new file mode 100644
index 0000000..d276fa6
--- /dev/null
+++ b/src/main/java/me/lotabout/codegenerator/util/MemberEntry.java
@@ -0,0 +1,495 @@
+package me.lotabout.codegenerator.util;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.java.generate.element.AbstractElement;
+
+import com.intellij.psi.PsiAnnotation;
+import com.intellij.psi.PsiAnnotationMemberValue;
+import com.intellij.psi.PsiArrayInitializerMemberValue;
+import com.intellij.psi.PsiLiteralExpression;
+import com.intellij.psi.PsiMember;
+
+/**
+ * A generic interface representing a member (field or method) in a class.
+ * Provides common functionality for accessing and manipulating member properties.
+ *
+ * @param The specific type of PsiMember (PsiField or PsiMethod)
+ * @author lotabout, Haixing Hu
+ */
+public interface MemberEntry {
+ /**
+ * Get the raw PSI member representation.
+ *
+ * @return The underlying PSI member object
+ */
+ T getRaw();
+
+ /**
+ * Get the AbstractElement wrapper for this member.
+ *
+ * @return The AbstractElement wrapper
+ */
+ AbstractElement getElement();
+
+ /**
+ * Get the name of this member.
+ *
+ * @return The member name
+ */
+ default String getName() {
+ return getElement().getName();
+ }
+
+ /**
+ * Get the accessor (getter/setter) name for this member.
+ *
+ * @return The accessor name
+ */
+ default String getAccessor() {
+ return getElement().getAccessor();
+ }
+
+ /**
+ * Check if this member's type is an array.
+ *
+ * @return true if array type, false otherwise
+ */
+ default boolean isArray() {
+ return getElement().isArray();
+ }
+
+ /**
+ * Check if this member's type is a nested array (array of arrays).
+ *
+ * @return true if nested array type, false otherwise
+ */
+ default boolean isNestedArray() {
+ return getElement().isNestedArray();
+ }
+
+ /**
+ * Check if this member's type is a primitive array.
+ *
+ * @return true if primitive array type, false otherwise
+ */
+ default boolean isPrimitiveArray() {
+ return getElement().isPrimitiveArray();
+ }
+
+ /**
+ * Check if this member's type is a String array.
+ *
+ * @return true if String array type, false otherwise
+ */
+ default boolean isStringArray() {
+ return getElement().isStringArray();
+ }
+
+ /**
+ * Check if this member's type is an Object array.
+ *
+ * @return true if Object array type, false otherwise
+ */
+ default boolean isObjectArray() {
+ return getElement().isObjectArray();
+ }
+
+ /**
+ * Check if this member's type is a Collection.
+ *
+ * @return true if Collection type, false otherwise
+ */
+ default boolean isCollection() {
+ return getElement().isCollection();
+ }
+
+ /**
+ * Check if this member's type is a List.
+ *
+ * @return true if List type, false otherwise
+ */
+ default boolean isList() {
+ return getElement().isList();
+ }
+
+ /**
+ * Check if this member's type is a Set.
+ *
+ * @return true if Set type, false otherwise
+ */
+ default boolean isSet() {
+ return getElement().isSet();
+ }
+
+ /**
+ * Check if this member's type is a Map.
+ *
+ * @return true if Map type, false otherwise
+ */
+ default boolean isMap() {
+ return getElement().isMap();
+ }
+
+ /**
+ * Check if this member's type is a primitive type.
+ *
+ * @return true if primitive type, false otherwise
+ */
+ default boolean isPrimitive() {
+ return getElement().isPrimitive();
+ }
+
+ /**
+ * Check if this member's type is a numeric type.
+ *
+ * @return true if numeric type, false otherwise
+ */
+ default boolean isNumeric() {
+ return getElement().isNumeric();
+ }
+
+ /**
+ * Check if this member's type is boolean.
+ *
+ * @return true if boolean type, false otherwise
+ */
+ default boolean isBoolean() {
+ return getElement().isBoolean();
+ }
+
+ /**
+ * Check if this member's type is char.
+ *
+ * @return true if char type, false otherwise
+ */
+ default boolean isChar() {
+ return getElement().isChar();
+ }
+
+ /**
+ * Check if this member's type is byte.
+ *
+ * @return true if byte type, false otherwise
+ */
+ default boolean isByte() {
+ return getElement().isByte();
+ }
+
+ /**
+ * Check if this member's type is short.
+ *
+ * @return true if short type, false otherwise
+ */
+ default boolean isShort() {
+ return getElement().isShort();
+ }
+
+ /**
+ * Check if this member's type is an integer type.
+ * This method will return true if the type is either primitive int or java.lang.Integer.
+ *
+ * @return true if integer type, false otherwise
+ * @author Haixing Hu
+ */
+ default boolean isInt() {
+ final String type = getElement().getType();
+ return type.equals("int") || type.equals("java.lang.Integer");
+ }
+
+ /**
+ * Check if this member's type is a long type.
+ * This method will return true if the type is either primitive long or java.lang.Long.
+ *
+ * @return true if long type, false otherwise
+ * @author Haixing Hu
+ */
+ default boolean isLong() {
+ final String type = getElement().getType();
+ return type.equals("long") || type.equals("java.lang.Long");
+ }
+
+ /**
+ * Check if this member's type is float.
+ *
+ * @return true if float type, false otherwise
+ */
+ default boolean isFloat() {
+ return getElement().isFloat();
+ }
+
+ /**
+ * Check if this member's type is double.
+ *
+ * @return true if double type, false otherwise
+ */
+ default boolean isDouble() {
+ return getElement().isDouble();
+ }
+
+ /**
+ * Check if this member's type is void.
+ *
+ * @return true if void type, false otherwise
+ */
+ default boolean isVoid() {
+ return getElement().isVoid();
+ }
+
+ /**
+ * Check if this member's type is String.
+ *
+ * @return true if String type, false otherwise
+ */
+ default boolean isString() {
+ return getElement().isString();
+ }
+
+ /**
+ * Check if this member's type is Object.
+ *
+ * @return true if Object type, false otherwise
+ */
+ default boolean isObject() {
+ return getElement().isObject();
+ }
+
+ /**
+ * Check if this member's type is java.util.Date.
+ *
+ * @return true if Date type, false otherwise
+ */
+ default boolean isDate() {
+ return getElement().isDate();
+ }
+
+ /**
+ * Check if this member's type is java.util.Calendar.
+ *
+ * @return true if Calendar type, false otherwise
+ */
+ default boolean isCalendar() {
+ return getElement().isCalendar();
+ }
+
+ /**
+ * Check if this member's type is java.time.Instant.
+ *
+ * @return true if Instant type, false otherwise
+ */
+ default boolean isInstant() {
+ final String type = getElement().getType();
+ return type.equals("java.time.Instant");
+ }
+
+ /**
+ * Check if this member's type is java.time.LocalDate.
+ *
+ * @return true if LocalDate type, false otherwise
+ */
+ default boolean isLocalDate() {
+ final String type = getElement().getType();
+ return type.equals("java.time.LocalDate");
+ }
+
+ /**
+ * Check if this member's type is java.time.LocalTime.
+ *
+ * @return true if LocalTime type, false otherwise
+ */
+ default boolean isLocalTime() {
+ final String type = getElement().getType();
+ return type.equals("java.time.LocalTime");
+ }
+
+ /**
+ * Check if this member's type is java.time.LocalDateTime.
+ *
+ * @return true if LocalDateTime type, false otherwise
+ */
+ default boolean isLocalDateTime() {
+ final String type = getElement().getType();
+ return type.equals("java.time.LocalDateTime");
+ }
+
+ /**
+ * Get the simple name of this member's type.
+ *
+ * @return The type name with any trailing '>' removed
+ */
+ default String getTypeName() {
+ return getElement().getTypeName().replace(">", "");
+ }
+
+ /**
+ * Get the fully qualified name of this member's type.
+ *
+ * @return The qualified type name
+ */
+ default String getTypeQualifiedName() {
+ return getElement().getTypeQualifiedName();
+ }
+
+ /**
+ * Check if this member is annotated with the specified annotation.
+ *
+ * @param qualifiedName The fully qualified name of the annotation
+ * @return true if the member has the annotation, false otherwise
+ */
+ boolean isAnnotatedWith(@NotNull final String qualifiedName);
+
+ /**
+ * Get the type of this member as a ClassEntry.
+ * For fields, this is the field type.
+ * For methods, this is the return type.
+ *
+ * @return the type as a ClassEntry, or null if the type cannot be resolved
+ */
+ TypeEntry getType();
+
+ /**
+ * Check if this member is annotated with @NotNull or similar annotations.
+ *
+ * @return true if the member is marked as not null, false otherwise
+ */
+ default boolean isNotNull() {
+ return getElement().isNotNull();
+ }
+
+ /**
+ * Check if this member is static.
+ *
+ * @return true if static, false otherwise
+ */
+ default boolean isModifierStatic() {
+ return getElement().isModifierStatic();
+ }
+
+ /**
+ * Check if this member has public visibility.
+ *
+ * @return true if public, false otherwise
+ */
+ default boolean isModifierPublic() {
+ return getElement().isModifierPublic();
+ }
+
+ /**
+ * Check if this member has protected visibility.
+ *
+ * @return true if protected, false otherwise
+ */
+ default boolean isModifierProtected() {
+ return getElement().isModifierProtected();
+ }
+
+ /**
+ * Check if this member has package-private visibility.
+ *
+ * @return true if package-private, false otherwise
+ */
+ default boolean isModifierPackageLocal() {
+ return getElement().isModifierPackageLocal();
+ }
+
+ /**
+ * Check if this member has private visibility.
+ *
+ * @return true if private, false otherwise
+ */
+ default boolean isModifierPrivate() {
+ return getElement().isModifierPrivate();
+ }
+
+ /**
+ * Check if this member is final.
+ *
+ * @return true if final, false otherwise
+ */
+ default boolean isModifierFinal() {
+ return getElement().isModifierFinal();
+ }
+
+ /**
+ * Check if the member has the specified annotation.
+ * This method will check both fully qualified name and simple name of the annotation.
+ *
+ * @param fullyQualifiedName The fully qualified name of the annotation
+ * @return true if the member has the annotation, false otherwise
+ * @author Haixing Hu
+ */
+ default boolean hasAnnotation(final String fullyQualifiedName) {
+ final String simpleName = fullyQualifiedName.substring(fullyQualifiedName.lastIndexOf('.') + 1);
+ final T raw = getRaw();
+ return raw.hasAnnotation(fullyQualifiedName) || raw.hasAnnotation(simpleName);
+ }
+
+ /**
+ * Get the annotation with the specified name.
+ * If the annotation with the fully qualified name does not exist,
+ * this method will try to get the annotation with its simple name.
+ *
+ * @param fullyQualifiedName The fully qualified name of the annotation
+ * @return The annotation if found, null otherwise
+ * @author Haixing Hu
+ */
+ @Nullable
+ default PsiAnnotation getAnnotation(final String fullyQualifiedName) {
+ final T raw = getRaw();
+ PsiAnnotation result = raw.getAnnotation(fullyQualifiedName);
+ if (result == null) {
+ final String simpleName = fullyQualifiedName.substring(fullyQualifiedName.lastIndexOf('.') + 1);
+ result = raw.getAnnotation(simpleName);
+ }
+ return result;
+ }
+
+ /**
+ * Get the value of an annotation attribute.
+ * If the attribute does not exist, this method will try to get the attribute
+ * using the simple name of the annotation.
+ *
+ * @param fullyQualifiedName The fully qualified name of the annotation
+ * @param attributeName The name of the attribute
+ * @return The attribute value as a String, or null if not found
+ */
+ @Nullable
+ default String getAnnotationAttribute(final String fullyQualifiedName, final String attributeName) {
+ final PsiAnnotation annotation = getAnnotation(fullyQualifiedName);
+ if (annotation != null) {
+ final PsiAnnotationMemberValue value = annotation.findAttributeValue(attributeName);
+ if (value != null) {
+ return value.getText();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get the values of an annotation attribute.
+ * If the attribute does not exist, this method will try to get the attribute
+ * using the simple name of the annotation.
+ * For single values, returns an array with one element.
+ * For array values, returns an array with all elements.
+ *
+ * @param fullyQualifiedName The fully qualified name of the annotation
+ * @param attributeName The name of the attribute
+ * @return Array of attribute values as Strings, or null if not found
+ * @author Haixing Hu
+ */
+ @Nullable
+ default String[] getAnnotationAttributes(final String fullyQualifiedName, final String attributeName) {
+ final PsiAnnotation annotation = getAnnotation(fullyQualifiedName);
+ if (annotation != null) {
+ final PsiAnnotationMemberValue value = annotation.findAttributeValue(attributeName);
+ if (value != null) {
+ if (value instanceof PsiLiteralExpression) {
+ return new String[]{value.getText()};
+ } else if (value instanceof PsiArrayInitializerMemberValue) {
+ return StringUtilEx.parseArrayInitializerText(value.getText());
+ }
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/me/lotabout/codegenerator/util/MemberEntryComparator.java b/src/main/java/me/lotabout/codegenerator/util/MemberEntryComparator.java
new file mode 100644
index 0000000..78a1e07
--- /dev/null
+++ b/src/main/java/me/lotabout/codegenerator/util/MemberEntryComparator.java
@@ -0,0 +1,31 @@
+package me.lotabout.codegenerator.util;
+
+import java.util.Comparator;
+
+public class MemberEntryComparator implements Comparator> {
+ private final int sort;
+
+ public MemberEntryComparator(final int sort) {
+ this.sort = sort;
+ }
+
+ public int compare(final MemberEntry> e1, final MemberEntry> e2) {
+ if (this.sort == 0) {
+ return 0;
+ } else {
+ final String name1 = getElementNameNoLeadingUnderscore(e1);
+ final String name2 = getElementNameNoLeadingUnderscore(e2);
+ int res = name1.compareToIgnoreCase(name2);
+ if (this.sort == 2) {
+ res = -1 * res;
+ }
+
+ return res;
+ }
+ }
+
+ private static String getElementNameNoLeadingUnderscore(final MemberEntry> e) {
+ final String name = e.getName();
+ return name.startsWith("_") ? name.substring(1) : name;
+ }
+}
diff --git a/src/main/java/me/lotabout/codegenerator/util/MethodEntry.java b/src/main/java/me/lotabout/codegenerator/util/MethodEntry.java
new file mode 100644
index 0000000..ccdfac2
--- /dev/null
+++ b/src/main/java/me/lotabout/codegenerator/util/MethodEntry.java
@@ -0,0 +1,162 @@
+package me.lotabout.codegenerator.util;
+
+import java.lang.ref.WeakReference;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.java.generate.element.ElementFactory;
+import org.jetbrains.java.generate.element.MethodElement;
+
+import com.intellij.psi.PsiMethod;
+import com.intellij.psi.PsiTypeElement;
+
+/**
+ * Wrapper around MethodElement that provides caching and utility methods for method information.
+ * This class is immutable and thread-safe.
+ */
+public class MethodEntry implements MemberEntry {
+ // Use ConcurrentHashMap as cache to ensure thread safety
+ private static final ConcurrentHashMap> CACHE = new ConcurrentHashMap<>();
+
+ /**
+ * Factory method to create or retrieve a MethodEntry instance.
+ * Uses caching to avoid creating duplicate instances for the same PsiMethod.
+ *
+ * @param method The PsiMethod to create a MethodEntry for
+ * @return A new or cached MethodEntry instance
+ */
+ public static MethodEntry of(final PsiMethod method) {
+ if (method == null) {
+ return null;
+ }
+ // Try to get from cache first
+ final WeakReference ref = CACHE.get(method);
+ MethodEntry entry = ref != null ? ref.get() : null;
+ if (entry != null) {
+ return entry;
+ }
+ // Create new instance if not in cache
+ entry = new MethodEntry(method, ElementFactory.newMethodElement(method));
+ // Use putIfAbsent to ensure thread safety
+ final WeakReference existing = CACHE.putIfAbsent(method, new WeakReference<>(entry));
+ return existing != null && existing.get() != null ? existing.get() : entry;
+ }
+
+ private final PsiMethod raw;
+ private final MethodElement element;
+ private volatile TypeEntry type;
+
+ /**
+ * Private constructor to enforce instance creation through factory method.
+ * Initializes all fields and caches type information.
+ *
+ * @param method The PsiMethod to create a MethodEntry for
+ * @param element The MethodElement wrapper
+ */
+ private MethodEntry(final PsiMethod method, final MethodElement element) {
+ this.raw = method;
+ this.element = element;
+ }
+
+ @Override
+ public TypeEntry getType() {
+ TypeEntry result = type;
+ if (result == null) {
+ synchronized (this) {
+ result = type;
+ if (result == null) {
+ type = result = initType();
+ }
+ }
+ }
+ return result;
+ }
+
+ private TypeEntry initType() {
+ if (raw == null) {
+ return null;
+ }
+ final PsiTypeElement psiTypeElement = raw.getReturnTypeElement();
+ return TypeEntry.of(psiTypeElement);
+ }
+
+ @Override
+ public MethodElement getElement() {
+ return element;
+ }
+
+ @Override
+ public PsiMethod getRaw() {
+ return raw;
+ }
+
+ public String getMethodName() {
+ return element.getMethodName();
+ }
+
+ public String getFieldName() {
+ return element.getFieldName();
+ }
+
+ public boolean isModifierAbstract() {
+ return element.isModifierAbstract();
+ }
+
+ public boolean isModifierSynchronzied() {
+ return element.isModifierSynchronzied();
+ }
+
+ public boolean isModifierSynchronized() {
+ return element.isModifierSynchronized();
+ }
+
+ public boolean isReturnTypeVoid() {
+ return element.isReturnTypeVoid();
+ }
+
+ public boolean isGetter() {
+ return element.isGetter();
+ }
+
+ public boolean isDeprecated() {
+ return element.isDeprecated();
+ }
+
+ @Override
+ public boolean isAnnotatedWith(@NotNull final String qualifiedName) {
+ return AnnotationUtil.isAnnotatedWith(raw, qualifiedName);
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final MethodEntry that = (MethodEntry) o;
+ return new EqualsBuilder()
+ .append(raw, that.raw)
+ .isEquals();
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder(17, 37)
+ .append(raw)
+ .toHashCode();
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .append("raw", raw)
+ .append("element", element)
+ .append("type", type)
+ .toString();
+ }
+}
diff --git a/src/main/java/me/lotabout/codegenerator/util/NameUtilEx.java b/src/main/java/me/lotabout/codegenerator/util/NameUtilEx.java
new file mode 100644
index 0000000..2d4c4b8
--- /dev/null
+++ b/src/main/java/me/lotabout/codegenerator/util/NameUtilEx.java
@@ -0,0 +1,110 @@
+package me.lotabout.codegenerator.util;
+
+import java.util.List;
+
+import org.jetbrains.annotations.NotNull;
+
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.util.text.Strings;
+import com.intellij.psi.codeStyle.NameUtil;
+import com.intellij.util.text.Matcher;
+
+/**
+ * Provides functions in {@link NameUtil}, with some additional functions.
+ *
+ * @author Haixing Hu
+ */
+public final class NameUtilEx {
+
+ @NotNull
+ public static List nameToWordsLowerCase(@NotNull final String name) {
+ return NameUtil.nameToWordsLowerCase(name);
+ }
+
+ @NotNull
+ public static String buildRegexp(@NotNull final String pattern,
+ final int exactPrefixLen, final boolean allowToUpper, final boolean allowToLower) {
+ return NameUtil.buildRegexp(pattern, exactPrefixLen, allowToUpper, allowToLower);
+ }
+
+ @NotNull
+ public static String buildRegexp(@NotNull final String pattern, final int exactPrefixLen,
+ final boolean allowToUpper, final boolean allowToLower, final boolean lowerCaseWords,
+ final boolean forCompletion) {
+ return NameUtil.buildRegexp(pattern, exactPrefixLen, allowToUpper, allowToLower, lowerCaseWords, forCompletion);
+ }
+
+ @NotNull
+ public static List getSuggestionsByName(@NotNull final String name,
+ @NotNull final String prefix, @NotNull final String suffix,
+ final boolean upperCaseStyle, final boolean preferLongerNames, final boolean isArray) {
+ return NameUtil.getSuggestionsByName(name, prefix, suffix, upperCaseStyle, preferLongerNames, isArray);
+ }
+
+ @NotNull
+ public static String[] splitNameIntoWords(@NotNull final String name) {
+ return NameUtil.splitNameIntoWords(name);
+ }
+
+ @NotNull
+ public static String[] nameToWords(@NotNull final String name) {
+ return NameUtil.nameToWords(name);
+ }
+
+ public static Matcher buildMatcher(@NotNull final String pattern,
+ final int exactPrefixLen, final boolean allowToUpper, final boolean allowToLower) {
+ return NameUtil.buildMatcher(pattern, exactPrefixLen, allowToUpper, allowToLower);
+ }
+
+ @NotNull
+ public static String capitalizeAndUnderscore(@NotNull final String name) {
+ return NameUtil.capitalizeAndUnderscore(name);
+ }
+
+ /**
+ * Convert a name to lowercase and underscore style.
+ *
+ * @param name
+ * the name to convert.
+ * @return
+ * the converted name.
+ * @author Haixing Hu
+ */
+ @NotNull
+ public static String lowercaseAndUnderscore(@NotNull final String name) {
+ return NameUtil.splitWords(name, '_', Strings::toLowerCase);
+ }
+
+ /**
+ * Get the getter name of a field.
+ *
+ * @param field
+ * the field.
+ * @return
+ * the getter name of the field.
+ * @author Haixing Hu
+ */
+ @NotNull
+ public static String getGetterName(@NotNull final FieldEntry field) {
+ final String name = StringUtil.capitalizeWithJavaBeanConvention(field.getName());
+ if (field.isBoolean()) {
+ return "is" + name;
+ } else {
+ return "get" + name;
+ }
+ }
+
+ /**
+ * Get the setter name of a field.
+ *
+ * @param field
+ * the field.
+ * @return
+ * the setter name of the field.
+ * @author Haixing Hu
+ */
+ @NotNull
+ public static String getSetterName(@NotNull final FieldEntry field) {
+ return "set" + StringUtil.capitalizeWithJavaBeanConvention(field.getName());
+ }
+}
diff --git a/src/me/lotabout/codegenerator/util/PackageUtil.java b/src/main/java/me/lotabout/codegenerator/util/PackageUtil.java
similarity index 58%
rename from src/me/lotabout/codegenerator/util/PackageUtil.java
rename to src/main/java/me/lotabout/codegenerator/util/PackageUtil.java
index 717bfe2..4ecc189 100644
--- a/src/me/lotabout/codegenerator/util/PackageUtil.java
+++ b/src/main/java/me/lotabout/codegenerator/util/PackageUtil.java
@@ -1,8 +1,15 @@
package me.lotabout.codegenerator.util;
+import java.io.File;
+import java.util.Arrays;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
import com.intellij.ide.IdeBundle;
import com.intellij.ide.util.DirectoryChooserUtil;
import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtil;
@@ -11,34 +18,30 @@
import com.intellij.openapi.roots.ModulePackageIndex;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.psi.*;
-import com.intellij.psi.impl.file.PsiDirectoryFactory;
+import com.intellij.psi.JavaDirectoryService;
+import com.intellij.psi.JavaPsiFacade;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiManager;
+import com.intellij.psi.PsiPackage;
import com.intellij.psi.search.GlobalSearchScope;
-import com.intellij.util.ActionRunner;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.Query;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Arrays;
// customize my package Util based on Intellij's built-in Package Util
public class PackageUtil {
- private static final Logger LOG = Logger.getInstance("me.lotabout.codegenerator.util.PackageUtil");
+ private static final Logger LOG = Logger.getInstance(PackageUtil.class);
@Nullable
- public static PsiDirectory findOrCreateDirectoryForPackage(@NotNull Project project,
- @Nullable Module module,
- String packageName,
- @Nullable PsiDirectory baseDir,
- boolean alwaysPrompt) throws IncorrectOperationException {
+ public static PsiDirectory findOrCreateDirectoryForPackage(@NotNull final Project project,
+ @Nullable final Module module, final String packageName,
+ @Nullable final PsiDirectory baseDir, final boolean alwaysPrompt)
+ throws IncorrectOperationException {
return findOrCreateDirectoryForPackage(project, module, packageName, baseDir, true, alwaysPrompt);
}
@Nullable
- public static PsiDirectory findSourceDirectoryByModuleName(@NotNull Project project, @Nullable String moduleName) {
+ public static PsiDirectory findSourceDirectoryByModuleName(@NotNull final Project project,
+ @Nullable final String moduleName) {
return Arrays.stream(ProjectRootUtil.getSourceRootDirectories(project))
.filter(psiDirectory -> psiDirectory.getVirtualFile().getPath().contains(moduleName))
.findFirst()
@@ -47,34 +50,32 @@ public static PsiDirectory findSourceDirectoryByModuleName(@NotNull Project proj
@Nullable
- public static PsiDirectory findOrCreateDirectoryForPackage(@NotNull Project project,
- @Nullable Module module,
- String packageName,
- PsiDirectory baseDir,
- boolean askUserToCreate,
- boolean alwaysPrompt) throws IncorrectOperationException {
+ public static PsiDirectory findOrCreateDirectoryForPackage(@NotNull final Project project,
+ @Nullable final Module module, String packageName, final PsiDirectory baseDir,
+ final boolean askUserToCreate, final boolean alwaysPrompt)
+ throws IncorrectOperationException {
PsiDirectory psiDirectory = null;
if (!alwaysPrompt && !packageName.isEmpty()) {
PsiPackage rootPackage = findLongestExistingPackage(module, packageName);
rootPackage = rootPackage == null ? findLongestExistingPackage(project, packageName) : rootPackage;
if (rootPackage != null) {
- int beginIndex = rootPackage.getQualifiedName().length() + 1;
+ final int beginIndex = rootPackage.getQualifiedName().length() + 1;
packageName = beginIndex < packageName.length() ? packageName.substring(beginIndex) : "";
String postfixToShow = packageName.replace('.', File.separatorChar);
- if (packageName.length() > 0) {
+ if (!packageName.isEmpty()) {
postfixToShow = File.separatorChar + postfixToShow;
}
- PsiDirectory[] moduleDirectories = getPackageDirectoriesInModule(rootPackage, module);
- PsiDirectory initDir = findDirectory(moduleDirectories, baseDir);
+ final PsiDirectory[] moduleDirectories = getPackageDirectoriesInModule(rootPackage, module);
+ final PsiDirectory initDir = findDirectory(moduleDirectories, baseDir);
psiDirectory = DirectoryChooserUtil.selectDirectory(project, moduleDirectories, initDir, postfixToShow);
if (psiDirectory == null) return null;
}
}
if (psiDirectory == null) {
- PsiDirectory[] sourceDirectories = ProjectRootUtil.getSourceRootDirectories(project);
- PsiDirectory initDir = findDirectory(sourceDirectories, baseDir);
+ final PsiDirectory[] sourceDirectories = ProjectRootUtil.getSourceRootDirectories(project);
+ final PsiDirectory initDir = findDirectory(sourceDirectories, baseDir);
psiDirectory = DirectoryChooserUtil.selectDirectory(project, sourceDirectories, initDir,
File.separatorChar + packageName.replace('.', File.separatorChar));
if (psiDirectory == null) return null;
@@ -82,13 +83,13 @@ public static PsiDirectory findOrCreateDirectoryForPackage(@NotNull Project proj
String restOfName = packageName;
boolean askedToCreate = false;
- while (restOfName.length() > 0) {
+ while (!restOfName.isEmpty()) {
final String name = getLeftPart(restOfName);
- PsiDirectory foundExistingDirectory = psiDirectory.findSubdirectory(name);
+ final PsiDirectory foundExistingDirectory = psiDirectory.findSubdirectory(name);
if (foundExistingDirectory == null) {
if (!askedToCreate && askUserToCreate) {
if (!ApplicationManager.getApplication().isUnitTestMode()) {
- int toCreate = Messages.showYesNoDialog(project,
+ final int toCreate = Messages.showYesNoDialog(project,
IdeBundle.message("prompt.create.non.existing.package", packageName),
IdeBundle.message("title.package.not.found"),
Messages.getQuestionIcon());
@@ -101,16 +102,10 @@ public static PsiDirectory findOrCreateDirectoryForPackage(@NotNull Project proj
final PsiDirectory psiDirectory1 = psiDirectory;
try {
- psiDirectory = ActionRunner.runInsideWriteAction(new ActionRunner.InterruptibleRunnableWithResult() {
- public PsiDirectory run() throws Exception {
- return psiDirectory1.createSubdirectory(name);
- }
- });
- } catch (IncorrectOperationException e) {
+ psiDirectory = WriteAction.compute(() -> psiDirectory1.createSubdirectory(name));
+ } catch (final IncorrectOperationException e) {
throw e;
- } catch (IOException e) {
- throw new IncorrectOperationException(e);
- } catch (Exception e) {
+ } catch (final Exception e) {
LOG.error(e);
}
} else {
@@ -121,17 +116,17 @@ public PsiDirectory run() throws Exception {
return psiDirectory;
}
- private static PsiDirectory[] getPackageDirectoriesInModule(PsiPackage rootPackage, Module module) {
+ private static PsiDirectory[] getPackageDirectoriesInModule(final PsiPackage rootPackage, final Module module) {
return rootPackage.getDirectories(GlobalSearchScope.moduleScope(module));
}
- private static PsiPackage findLongestExistingPackage(Project project, String packageName) {
- PsiManager manager = PsiManager.getInstance(project);
+ private static PsiPackage findLongestExistingPackage(final Project project, final String packageName) {
+ final PsiManager manager = PsiManager.getInstance(project);
String nameToMatch = packageName;
while (true) {
- PsiPackage aPackage = JavaPsiFacade.getInstance(manager.getProject()).findPackage(nameToMatch);
+ final PsiPackage aPackage = JavaPsiFacade.getInstance(manager.getProject()).findPackage(nameToMatch);
if (aPackage != null && isWritablePackage(aPackage)) return aPackage;
- int lastDotIndex = nameToMatch.lastIndexOf('.');
+ final int lastDotIndex = nameToMatch.lastIndexOf('.');
if (lastDotIndex >= 0) {
nameToMatch = nameToMatch.substring(0, lastDotIndex);
} else {
@@ -140,9 +135,9 @@ private static PsiPackage findLongestExistingPackage(Project project, String pac
}
}
- private static boolean isWritablePackage(PsiPackage aPackage) {
- PsiDirectory[] directories = aPackage.getDirectories();
- for (PsiDirectory directory : directories) {
+ private static boolean isWritablePackage(final PsiPackage aPackage) {
+ final PsiDirectory[] directories = aPackage.getDirectories();
+ for (final PsiDirectory directory : directories) {
if (directory.isValid() && directory.isWritable()) {
return true;
}
@@ -150,10 +145,11 @@ private static boolean isWritablePackage(PsiPackage aPackage) {
return false;
}
- private static PsiDirectory getWritableModuleDirectory(@NotNull Query vFiles, @NotNull Module module, PsiManager manager) {
- for (VirtualFile vFile : vFiles) {
+ private static PsiDirectory getWritableModuleDirectory(@NotNull final Query vFiles,
+ @NotNull final Module module, final PsiManager manager) {
+ for (final VirtualFile vFile : vFiles) {
if (ModuleUtil.findModuleForFile(vFile, module.getProject()) != module) continue;
- PsiDirectory directory = manager.findDirectory(vFile);
+ final PsiDirectory directory = manager.findDirectory(vFile);
if (directory != null && directory.isValid() && directory.isWritable()) {
return directory;
}
@@ -161,7 +157,7 @@ private static PsiDirectory getWritableModuleDirectory(@NotNull Query vFiles = ModulePackageIndex.getInstance(module).getDirsByPackageName(nameToMatch, false);
- PsiDirectory directory = getWritableModuleDirectory(vFiles, module, manager);
+ final Query vFiles = ModulePackageIndex
+ .getInstance(module)
+ .getDirsByPackageName(nameToMatch, false);
+ final PsiDirectory directory = getWritableModuleDirectory(vFiles, module, manager);
if (directory != null) return JavaDirectoryService.getInstance().getPackage(directory);
- int lastDotIndex = nameToMatch.lastIndexOf('.');
+ final int lastDotIndex = nameToMatch.lastIndexOf('.');
if (lastDotIndex >= 0) {
nameToMatch = nameToMatch.substring(0, lastDotIndex);
} else {
@@ -183,19 +181,19 @@ private static PsiPackage findLongestExistingPackage(Module module, String packa
}
}
- private static String getLeftPart(String packageName) {
- int index = packageName.indexOf('.');
+ private static String getLeftPart(final String packageName) {
+ final int index = packageName.indexOf('.');
return index > -1 ? packageName.substring(0, index) : packageName;
}
- private static String cutLeftPart(String packageName) {
- int index = packageName.indexOf('.');
+ private static String cutLeftPart(final String packageName) {
+ final int index = packageName.indexOf('.');
return index > -1 ? packageName.substring(index + 1) : "";
}
- private static PsiDirectory findDirectory(PsiDirectory[] directories, PsiDirectory baseDir) {
+ private static PsiDirectory findDirectory(final PsiDirectory[] directories, final PsiDirectory baseDir) {
final VirtualFile baseFile = baseDir.getVirtualFile();
- for (PsiDirectory directory : directories) {
+ for (final PsiDirectory directory : directories) {
if (directory.getVirtualFile().equals(baseFile)) {
return directory;
}
@@ -203,4 +201,3 @@ private static PsiDirectory findDirectory(PsiDirectory[] directories, PsiDirecto
return null;
}
}
-
diff --git a/src/main/java/me/lotabout/codegenerator/util/StringUtilEx.java b/src/main/java/me/lotabout/codegenerator/util/StringUtilEx.java
new file mode 100644
index 0000000..6945830
--- /dev/null
+++ b/src/main/java/me/lotabout/codegenerator/util/StringUtilEx.java
@@ -0,0 +1,201 @@
+package me.lotabout.codegenerator.util;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.util.text.LineColumn;
+import com.intellij.openapi.util.text.StringUtil;
+
+/**
+ * Provides functions in {@link StringUtil}, with some additional functions.
+ *
+ * @author Haixing Hu
+ */
+public class StringUtilEx {
+
+ private static final Logger logger = Logger.getInstance(StringUtilEx.class);
+
+ @NotNull
+ public static List getWordsInStringLongestFirst(@NotNull final String find) {
+ return StringUtil.getWordsInStringLongestFirst(find);
+ }
+
+ @NotNull
+ public static String escapePattern(@NotNull final String text) {
+ return StringUtil.escapePattern(text);
+ }
+
+ @NotNull
+ public static String replace(@NotNull final String text, @NotNull final String oldS, @NotNull final String newS) {
+ return StringUtil.replace(text, oldS, newS);
+ }
+
+ public static @NotNull String replaceIgnoreCase(@NotNull final String text, @NotNull final String oldS, @NotNull final String newS) {
+ return StringUtil.replaceIgnoreCase(text, oldS, newS);
+ }
+
+ public static String replace(@NotNull final String text, @NotNull final String oldS,
+ @NotNull final String newS, final boolean ignoreCase) {
+ return StringUtil.replace(text, oldS, newS, ignoreCase);
+ }
+
+ public static int indexOfIgnoreCase(@NotNull final String where,
+ @NotNull final String what, final int fromIndex) {
+ return StringUtil.indexOfIgnoreCase(where, what, fromIndex);
+ }
+
+ public static int indexOfIgnoreCase(@NotNull final CharSequence where,
+ @NotNull final CharSequence what, final int fromIndex) {
+ return StringUtil.indexOfIgnoreCase(where, what, fromIndex);
+ }
+
+ public static int lastIndexOfIgnoreCase(@NotNull final String where,
+ final char c, final int fromIndex) {
+ return StringUtil.lastIndexOfIgnoreCase(where, c, fromIndex);
+ }
+
+ public static boolean containsIgnoreCase(@NotNull final String where,
+ @NotNull final String what) {
+ return StringUtil.containsIgnoreCase(where, what);
+ }
+
+ public static boolean endsWithIgnoreCase(@NotNull final String str, @NotNull final String suffix) {
+ return StringUtil.endsWithIgnoreCase(str, suffix);
+ }
+
+ public static boolean startsWithIgnoreCase(@NotNull final String str, @NotNull final String prefix) {
+ return StringUtil.startsWithIgnoreCase(str, prefix);
+ }
+
+ @NotNull
+ public static String stripHtml(@NotNull final String html, final boolean convertBreaks) {
+ return StringUtil.stripHtml(html, convertBreaks);
+ }
+
+ @NotNull
+ public static String stripHtml(@NotNull final String html, @Nullable final String breaks) {
+ return StringUtil.stripHtml(html, breaks);
+ }
+
+ public static String toLowerCase(@Nullable final String str) {
+ return StringUtil.toLowerCase(str);
+ }
+
+ @NotNull
+ public static String getPackageName(@NotNull final String fqName) {
+ return StringUtil.getPackageName(fqName);
+ }
+
+ @NotNull
+ public static String getPackageName(@NotNull final String fqName, final char separator) {
+ return StringUtil.getPackageName(fqName, separator);
+ }
+
+ public static int getLineBreakCount(@NotNull final CharSequence text) {
+ return StringUtil.getLineBreakCount(text);
+ }
+
+ public static boolean containsLineBreak(@NotNull final CharSequence text) {
+ return StringUtil.containsLineBreak(text);
+ }
+
+ public static boolean isLineBreak(final char c) {
+ return StringUtil.isLineBreak(c);
+ }
+
+ public static @NotNull String escapeLineBreak(@NotNull final String text) {
+ return StringUtil.escapeLineBreak(text);
+ }
+
+ public static boolean endsWithLineBreak(@NotNull final CharSequence text) {
+ return StringUtil.endsWithLineBreak(text);
+ }
+
+ public static int lineColToOffset(@NotNull final CharSequence text, final int line, final int col) {
+ return StringUtil.lineColToOffset(text, line, col);
+ }
+
+ public static int offsetToLineNumber(@NotNull final CharSequence text, final int offset) {
+ return StringUtil.offsetToLineNumber(text, offset);
+ }
+
+ public static LineColumn offsetToLineColumn(@NotNull final CharSequence text, final int offset) {
+ return StringUtil.offsetToLineColumn(text, offset);
+ }
+
+ public static int difference(@NotNull final String s1, @NotNull final String s2) {
+ return StringUtil.difference(s1, s2);
+ }
+
+ @NotNull
+ public static String wordsToBeginFromUpperCase(@NotNull final String s) {
+ return StringUtil.wordsToBeginFromUpperCase(s);
+ }
+
+ @NotNull
+ public static String wordsToBeginFromLowerCase(@NotNull final String s) {
+ return StringUtil.wordsToBeginFromLowerCase(s);
+ }
+
+ @NotNull
+ public static String toTitleCase(@NotNull final String s) {
+ return StringUtil.toTitleCase(s);
+ }
+
+ /**
+ * Parse the text representation of an array initializer into an array of strings.
+ *
+ * Note that this function will ignore the leading and trailing white spaces of
+ * the text, and the leading and trailing white spaces of each item, and the empty
+ * items.
+ *
+ * @param text
+ * the text representation of a array to parse, which has the form
+ * of "{item1, item2, item3, ...}".
+ * @return
+ * the array of strings parsed from the text.
+ * @author Haixing Hu
+ */
+ public static String[] parseArrayInitializerText(final String text) {
+ if (text == null) {
+ return new String[0];
+ }
+ final String trimmed = text.trim();
+ if ((trimmed.length() < 2)
+ || (trimmed.charAt(0) != '{')
+ || (trimmed.charAt(trimmed.length() - 1) != '}')) {
+ logger.error("Invalid array initializer text: " + text);
+ return new String[0];
+ }
+ final String[] items = trimmed.substring(1, trimmed.length() - 1).split(",");
+ final List result = new ArrayList<>();
+ for (int i = 0; i < items.length; ++i) {
+ final String item = items[i].trim();
+ if (!item.isEmpty()) {
+ result.add(item);
+ }
+ }
+ return result.toArray(new String[0]);
+ }
+
+ /**
+ * Unquote an array of strings.
+ *
+ * @param array
+ * the array of strings to unquote.
+ * @return
+ * the array of strings unquoted.
+ * @author Haixing Hu
+ */
+ public static String[] unquoteStringArray(final String[] array) {
+ final String[] result = new String[array.length];
+ for (int i = 0; i < array.length; ++i) {
+ result[i] = StringUtil.unquoteString(array[i]);
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/me/lotabout/codegenerator/util/TypeEntry.java b/src/main/java/me/lotabout/codegenerator/util/TypeEntry.java
new file mode 100644
index 0000000..d6d036c
--- /dev/null
+++ b/src/main/java/me/lotabout/codegenerator/util/TypeEntry.java
@@ -0,0 +1,991 @@
+package me.lotabout.codegenerator.util;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.java.generate.psi.PsiAdapter;
+
+import com.intellij.psi.JavaPsiFacade;
+import com.intellij.psi.PsiArrayType;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiClassType;
+import com.intellij.psi.PsiElementFactory;
+import com.intellij.psi.PsiModifier;
+import com.intellij.psi.PsiType;
+import com.intellij.psi.PsiTypeElement;
+import com.intellij.psi.PsiTypeParameter;
+
+/**
+ * A wrapper for PsiType that provides caching and utility methods for type information.
+ * This class is immutable and thread-safe.
+ *
+ * This class can represents Class, primitive types, and array types.
+ *
+ * @author lotabout, Haixing Hu
+ */
+public class TypeEntry {
+
+ // Use ConcurrentHashMap as cache to ensure thread safety
+ // private static final Map CACHE = new ConcurrentHashMap<>();
+
+ /**
+ * Factory method to create or retrieve a {@link TypeEntry} instance.
+ *
+ * @param type The PsiType to create a {@link TypeEntry} for
+ * @return A new or cached {@link TypeEntry} instance
+ */
+ public static TypeEntry of(final PsiType type, final JavaPsiFacade facade) {
+ if (type == null) {
+ return null;
+ }
+ // return CACHE.computeIfAbsent(type, (t) -> new TypeEntry(t, facade));
+ return new TypeEntry(type, facade);
+ }
+
+ /**
+ * Factory method to create or retrieve a {@link TypeEntry} instance.
+ *
+ * @param psiClass
+ * The PsiClass to create a {@link TypeEntry} for
+ * @return
+ * A new or cached {@link TypeEntry} instance
+ */
+ public static TypeEntry of(final PsiClass psiClass) {
+ if (psiClass == null) {
+ return null;
+ }
+ final JavaPsiFacade facade = JavaPsiFacade.getInstance(psiClass.getProject());
+ final PsiElementFactory factory = facade.getElementFactory();
+ final PsiType type = factory.createType(psiClass);
+ // return CACHE.computeIfAbsent(type, (t) -> new TypeEntry(t, facade));
+ return new TypeEntry(type, facade);
+ }
+
+ /**
+ * Factory method to create or retrieve a {@link TypeEntry} instance.
+ *
+ * @param psiTypeElement
+ * The PsiTypeElement to create a {@link TypeEntry} for
+ * @return
+ * A new or cached {@link TypeEntry} instance
+ */
+ public static TypeEntry of(final PsiTypeElement psiTypeElement) {
+ if (psiTypeElement == null) {
+ return null;
+ }
+ final PsiType type = psiTypeElement.getType();
+ final JavaPsiFacade facade = JavaPsiFacade.getInstance(psiTypeElement.getProject());
+ // return CACHE.computeIfAbsent(type, (t) -> new TypeEntry(t, facade));
+ return new TypeEntry(type, facade);
+ }
+
+ private transient final Map implementsCache = new ConcurrentHashMap<>();
+ private final PsiType type; // Store the actual type information including generics
+ private final JavaPsiFacade facade;
+ @Nullable
+ private final PsiClass psiClass; // Store the resolved PsiClass for this type, which may be null
+ private final boolean primitiveArray;
+ private final boolean objectArray;
+ private final boolean stringArray;
+ private final boolean array;
+ private final boolean collection;
+ private final boolean set;
+ private final boolean map;
+ private final boolean primitive;
+ private final boolean voidType;
+ private final boolean enumType;
+ private final boolean exceptionType;
+ private final boolean abstractType;
+ private final boolean deprecated;
+ private final boolean interfaceType;
+ private final boolean recordType;
+ private final String simpleName;
+ private final String qualifiedName;
+ private final String packageName;
+
+ private volatile TypeEntry superClass;
+ private volatile TypeEntry elementType;
+ private volatile TypeEntry keyType;
+ private volatile TypeEntry valueType;
+ private volatile List fields;
+ private volatile List allFields;
+ private volatile List methods;
+ private volatile List allMethods;
+ private volatile List> members;
+ private volatile List> allMembers;
+ private volatile Set interfaces;
+ private volatile List superClasses;
+ private volatile List typeParameters;
+
+ /**
+ * Private constructor to enforce instance creation through factory method.
+ * Initializes all fields and caches type information.
+ *
+ * @param type
+ * The PsiType to create a {@link TypeEntry} for
+ */
+ private TypeEntry(final PsiType type, final JavaPsiFacade facade) {
+ this.type = type;
+ this.facade = facade;
+ final PsiElementFactory factory = facade.getElementFactory();
+ this.psiClass = ((type instanceof PsiClassType) ? ((PsiClassType) type).resolve() : null);
+ // this.raw = element;
+ // this.element = (raw == null ? null : ElementFactory.newClassElement(raw));
+ this.primitiveArray = PsiAdapter.isPrimitiveArrayType(type);
+ this.objectArray = PsiAdapter.isObjectArrayType(type);
+ this.stringArray = PsiAdapter.isStringArrayType(type);
+ this.array = (type instanceof PsiArrayType);
+ this.collection = PsiAdapter.isCollectionType(factory, type);
+ this.set = PsiAdapter.isSetType(factory, type);
+ this.map = PsiAdapter.isMapType(factory, type);
+ this.primitive = PsiAdapter.isPrimitiveType(type);
+ this.voidType = PsiAdapter.isTypeOfVoid(type);
+ this.enumType = (psiClass != null && psiClass.isEnum());
+ this.exceptionType = PsiAdapter.isExceptionClass(psiClass);
+ this.abstractType = (psiClass != null && psiClass.hasModifierProperty(PsiModifier.ABSTRACT));
+ this.deprecated = (psiClass != null && psiClass.isDeprecated());
+ this.interfaceType = (psiClass != null && psiClass.isInterface());
+ this.recordType = (psiClass != null && psiClass.isRecord());
+ this.simpleName = type.getPresentableText();
+ this.qualifiedName = type.getCanonicalText();
+ this.packageName = GenerationUtil.getPackageName(this.qualifiedName);
+ // Initialize collections as null for lazy initialization
+ this.fields = null;
+ this.allFields = null;
+ this.methods = null;
+ this.allMethods = null;
+ this.members = null;
+ this.allMembers = null;
+ this.superClasses = null;
+ this.interfaces = null;
+ this.typeParameters = null;
+ }
+
+ @Nullable
+ private TypeEntry getOrInitValueType() {
+ TypeEntry result = valueType;
+ if (result == null) {
+ synchronized (this) {
+ result = valueType;
+ if (result == null) {
+ valueType = result = initValueType();
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Get the element type if this class is a collection or array.
+ *
+ * @return a {@link TypeEntry} representing the element type if this class is a collection or array,
+ * null otherwise
+ */
+ @Nullable
+ public TypeEntry getElementType() {
+ if (array || collection) {
+ TypeEntry result = elementType;
+ if (result == null) {
+ synchronized (this) {
+ result = elementType;
+ if (result == null) {
+ elementType = result = initElementType();
+ }
+ }
+ }
+ return result;
+ }
+ return null;
+ }
+
+ @Nullable
+ private TypeEntry initElementType() {
+ if (array) {
+ return initArrayElementType();
+ }
+ if (collection) {
+ return initCollectionElementType();
+ }
+ return null;
+ }
+
+ @Nullable
+ private TypeEntry initArrayElementType() {
+ final String componentName = type.getCanonicalText();
+ final String baseType = componentName.substring(0, componentName.length() - 2);
+ final PsiClass componentClass = facade.findClass(baseType, psiClass.getResolveScope());
+ return componentClass != null ? of(componentClass) : null;
+ }
+
+ @Nullable
+ private TypeEntry initCollectionElementType() {
+ if ((type instanceof PsiClassType) && (psiClass != null)) {
+ final PsiClassType classType = (PsiClassType) type;
+ final PsiType[] parameters = classType.getParameters();
+ if (parameters.length > 0) {
+ return of(parameters[0], facade);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get the key type if this class is a map.
+ *
+ * @return a {@link TypeEntry} representing the key type if this class is a map,
+ * null otherwise
+ */
+ @Nullable
+ public TypeEntry getKeyType() {
+ if (map && (psiClass != null)) {
+ TypeEntry result = keyType;
+ if (result == null) {
+ synchronized (this) {
+ result = keyType;
+ if (result == null) {
+ keyType = result = initKeyType();
+ }
+ }
+ }
+ return result;
+ }
+ return null;
+ }
+
+ @Nullable
+ private TypeEntry initKeyType() {
+ final PsiClassType classType = (PsiClassType) type;
+ final PsiType[] parameters = classType.getParameters();
+ if (parameters.length > 0) {
+ return of(parameters[0], facade);
+ }
+ return null;
+ }
+
+ /**
+ * Get the value type if this class is a map.
+ *
+ * @return a {@link TypeEntry} representing the value type if this class is a map,
+ * null otherwise
+ */
+ @Nullable
+ public TypeEntry getValueType() {
+ if (map && (psiClass != null)) {
+ TypeEntry result = valueType;
+ if (result == null) {
+ synchronized (this) {
+ result = valueType;
+ if (result == null) {
+ valueType = result = initValueType();
+ }
+ }
+ }
+ return result;
+ }
+ return null;
+ }
+
+ @Nullable
+ private TypeEntry initValueType() {
+ final PsiClassType classType = (PsiClassType) type;
+ final PsiType[] parameters = classType.getParameters();
+ if (parameters.length > 1) {
+ return of(parameters[1], facade);
+ }
+ return null;
+ }
+
+ /**
+ * Get the list of fields declared in this class.
+ *
+ * @return List of FieldEntry objects
+ */
+ public List getFields() {
+ List result = fields;
+ if (result == null) {
+ synchronized (this) {
+ result = fields;
+ if (result == null) {
+ fields = result = initFields();
+ }
+ }
+ }
+ return result;
+ }
+
+ private List initFields() {
+ if (psiClass != null) {
+ return GenerationUtil.getFields(psiClass);
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * Get all fields including inherited ones.
+ *
+ * @return List of all FieldEntry objects
+ */
+ public List getAllFields() {
+ List result = allFields;
+ if (result == null) {
+ synchronized (this) {
+ result = allFields;
+ if (result == null) {
+ allFields = result = initAllFields();
+ }
+ }
+ }
+ return result;
+ }
+
+ private List initAllFields() {
+ if (psiClass != null) {
+ return GenerationUtil.getAllFields(psiClass);
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * Get methods declared in this class.
+ *
+ * @return List of MethodEntry objects
+ */
+ public List getMethods() {
+ List result = methods;
+ if (result == null) {
+ synchronized (this) {
+ result = methods;
+ if (result == null) {
+ methods = result = initMethods();
+ }
+ }
+ }
+ return result;
+ }
+
+ private List initMethods() {
+ if (psiClass != null) {
+ return GenerationUtil.getMethods(psiClass);
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * Get all methods including inherited ones.
+ *
+ * @return List of all MethodEntry objects
+ */
+ public List getAllMethods() {
+ List result = allMethods;
+ if (result == null) {
+ synchronized (this) {
+ result = allMethods;
+ if (result == null) {
+ allMethods = result = initAllMethods();
+ }
+ }
+ }
+ return result;
+ }
+
+ private List initAllMethods() {
+ if (psiClass != null) {
+ return GenerationUtil.getAllMethods(psiClass);
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * Get members (fields and methods) declared in this class.
+ *
+ * @return List of MemberEntry objects
+ */
+ public List> getMembers() {
+ List> result = members;
+ if (result == null) {
+ synchronized (this) {
+ result = members;
+ if (result == null) {
+ members = result = initMembers();
+ }
+ }
+ }
+ return result;
+ }
+
+ private List> initMembers() {
+ if (psiClass != null) {
+ final List fieldMembers = GenerationUtil.getFields(psiClass);
+ final List methodMembers = GenerationUtil.getMethods(psiClass);
+ final List> result = new ArrayList<>();
+ result.addAll(fieldMembers);
+ result.addAll(methodMembers);
+ return result;
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * Get all members including inherited ones.
+ *
+ * @return List of all MemberEntry objects
+ */
+ public List> getAllMembers() {
+ List> result = allMembers;
+ if (result == null) {
+ synchronized (this) {
+ result = allMembers;
+ if (result == null) {
+ allMembers = result = initAllMembers();
+ }
+ }
+ }
+ return result;
+ }
+
+
+ private List> initAllMembers() {
+ if (psiClass != null) {
+ final List fieldMembers = GenerationUtil.getAllFields(psiClass);
+ final List methodMembers = GenerationUtil.getAllMethods(psiClass);
+ final List> result = new ArrayList<>();
+ result.addAll(fieldMembers);
+ result.addAll(methodMembers);
+ return result;
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * Get the simple name of this type.
+ *
+ * @return The simple type name
+ */
+ public String getName() {
+ return simpleName;
+ }
+
+ /**
+ * Get the qualified name of this type.
+ *
+ * @return The qualified type name
+ */
+ public String getQualifiedName() {
+ return qualifiedName;
+ }
+
+ /**
+ * Get the package name of this class.
+ *
+ * @return The package name as a string
+ */
+ public String getPackageName() {
+ return packageName;
+ }
+
+ /**
+ * Get inner classes declared in this class.
+ *
+ * @return
+ * List of {@link TypeEntry} objects for inner classes
+ */
+ public List getInnerClasses() {
+ if (psiClass != null) {
+ return GenerationUtil.getInnerClasses(psiClass);
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * Get all inner classes including inherited ones.
+ *
+ * @return
+ * List of all {@link TypeEntry} objects for inner classes
+ */
+ public List getAllInnerClasses() {
+ if (psiClass != null) {
+ return GenerationUtil.getAllInnerClasses(psiClass);
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * Get the superclass of this class.
+ *
+ * @return
+ * {@link TypeEntry} for the superclass, or null if none exists
+ */
+ @Nullable
+ public TypeEntry getSuperClass() {
+ if (psiClass == null) {
+ return null;
+ }
+ if (psiClass.getSuperClass() == null) {
+ return null;
+ }
+ TypeEntry result = superClass;
+ if (result == null) {
+ synchronized (this) {
+ result = superClass;
+ if (result == null) {
+ superClass = result = of(psiClass.getSuperClass());
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Check if this class has a superclass.
+ *
+ * @return true if has superclass, false otherwise
+ */
+ public boolean hasSuper() {
+ return getSuperClass() != null;
+ }
+
+ /**
+ * Get the simple name of the superclass.
+ *
+ * @return
+ * The superclass simple name
+ */
+ @Nullable
+ public String getSuperName() {
+ final TypeEntry theSuperClass = getSuperClass();
+ return (theSuperClass == null ? null : theSuperClass.getName());
+ }
+
+ /**
+ * Get the qualified name of the superclass.
+ *
+ * @return The superclass qualified name
+ */
+ @Nullable
+ public String getSuperQualifiedName() {
+ final TypeEntry theSuperClass = getSuperClass();
+ return (theSuperClass == null ? null : theSuperClass.getQualifiedName());
+ }
+
+ /**
+ * Check if this class implements the specified interface.
+ *
+ * This function checks direct implementations, interface hierarchy, and
+ * superclass implementations.
+ *
+ * @param interfaceName
+ * The interface name to check, it must be fully qualified.
+ * @return
+ * true if the class implements the interface, false otherwise
+ */
+ public boolean isImplements(final String interfaceName) {
+ return implementsCache.computeIfAbsent(interfaceName, this::checkImplements);
+ }
+
+ private boolean checkImplements(final String interfaceName) {
+ if (psiClass != null) {
+ final Set allInterfaces = getInterfaces();
+ for (final TypeEntry iface : allInterfaces) {
+ if (interfaceName.equals(iface.getQualifiedName())) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Get the set of interfaces implemented by this class.
+ *
+ * @return
+ * the set of interfaces implemented by this class, or an empty set if
+ * this type is not a class type.
+ */
+ public Set getInterfaces() {
+ if (psiClass == null) {
+ return Collections.emptySet();
+ }
+ Set result = interfaces;
+ if (result == null) {
+ synchronized (this) {
+ result = interfaces;
+ if (result == null) {
+ interfaces = result = initInterfaces();
+ }
+ }
+ }
+ return result;
+ }
+
+ private Set initInterfaces() {
+ assert psiClass != null;
+ final Set allInterfaces = GenerationUtil.getAllInterfaces(psiClass);
+ final Set result = new HashSet<>();
+ for (final PsiClass iface : allInterfaces) {
+ result.add(of(iface));
+ }
+ return result;
+ }
+
+ /**
+ * Get the names of interfaces implemented by this class.
+ *
+ * @return Array of interface names
+ */
+ public String[] getInterfaceNames() {
+ final Set allInterfaces = getInterfaces();
+ final String[] result = new String[allInterfaces.size()];
+ int i = 0;
+ for (final TypeEntry iface : allInterfaces) {
+ result[i++] = iface.getQualifiedName();
+ }
+ return result;
+ }
+
+ /**
+ * Check if this class extends any of the given classnames.
+ *
+ * @param classNames
+ * list of class names seperated by comma.
+ * @return
+ * true if this class extends one of the given classnames.
+ */
+ public boolean isExtends(final String classNames) {
+ final String superName = getSuperName();
+ final String[] superNames = classNames.split(",");
+ for (final String name : superNames) {
+ if (name.equals(superName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check if this class extends any of the given classnames.
+ *
+ * @param classNames
+ * list of class names.
+ * @return
+ * true if this class extends one of the given classnames.
+ */
+ public boolean isExtends(final String... classNames) {
+ final String superName = getSuperName();
+ for (final String name : classNames) {
+ if (name.equals(superName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public List getSuperClasses() {
+ List result = superClasses;
+ if (result == null) {
+ synchronized (this) {
+ result = superClasses;
+ if (result == null) {
+ superClasses = result = initSuperClasses();
+ }
+ }
+ }
+ return result;
+ }
+
+ private List initSuperClasses() {
+ if (psiClass != null) {
+ final PsiClass[] supers = psiClass.getSupers();
+ final List result = new ArrayList<>();
+ for (final PsiClass s : supers) {
+ result.add(of(s));
+ }
+ return result;
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * Get the simple names of super classes of this class.
+ *
+ * @return
+ * Array of super class simple names of this class, or an empty array
+ * if this type is not a class or it has no super classes.
+ */
+ public String[] getSuperClassNames() {
+ final List supers = getSuperClasses();
+ final String[] result = new String[supers.size()];
+ int i = 0;
+ for (final TypeEntry s : supers) {
+ result[i++] = s.getName();
+ }
+ return result;
+ }
+
+ /**
+ * Get the qualified names of super classes of this class.
+ *
+ * @return
+ * Array of super class qualified names of this class, or an empty array
+ * if this type is not a class or it has no super classes.
+ */
+ public String[] getSuperClassQualifiedNames() {
+ final List supers = getSuperClasses();
+ final String[] result = new String[supers.size()];
+ int i = 0;
+ for (final TypeEntry s : supers) {
+ result[i++] = s.getQualifiedName();
+ }
+ return result;
+ }
+
+ /**
+ * Get the type parameters of this class.
+ *
+ * @return
+ * List of type parameters of this class, or an empty list if this
+ * type is not a class or it has no type parameters.
+ */
+ public List getTypeParameters() {
+ if (psiClass == null) {
+ return Collections.emptyList();
+ }
+ List result = typeParameters;
+ if (result == null) {
+ synchronized (this) {
+ result = typeParameters;
+ if (result == null) {
+ typeParameters = result = initTypeParameters();
+ }
+ }
+ }
+ return result;
+ }
+
+ private List initTypeParameters() {
+ assert psiClass != null;
+ final PsiTypeParameter[] parameters = psiClass.getTypeParameters();
+ final List result = new ArrayList<>();
+ for (final PsiTypeParameter param : parameters) {
+ result.add(of(param));
+ }
+ return result;
+ }
+
+ /**
+ * Get the names of type parameters of this class.
+ *
+ * @return
+ * Array of type parameter names of this class, or an empty array if
+ * this type is not a class or it has no type parameters.
+ */
+ public String[] getTypeParameterNames() {
+ final List params = getTypeParameters();
+ final String[] result = new String[params.size()];
+ int i = 0;
+ for (final TypeEntry param : params) {
+ result[i++] = param.getName();
+ }
+ return result;
+ }
+
+ /**
+ * Check if this class is a primitive type.
+ *
+ * @return true if primitive, false otherwise
+ */
+ public boolean isPrimitive() {
+ return primitive;
+ }
+
+ /**
+ * Check if this class is a void type.
+ *
+ * @return true if void, false otherwise
+ */
+ public boolean isVoid() {
+ return voidType;
+ }
+
+ /**
+ * Check if this class is an array type.
+ *
+ * @return true if array, false otherwise
+ */
+ public boolean isArray() {
+ return array;
+ }
+
+ /**
+ * Check if this class is a primitive array type.
+ *
+ * @return true if primitive array, false otherwise
+ */
+ public boolean isPrimitiveArray() {
+ return primitiveArray;
+ }
+
+ /**
+ * Check if this class is an object array type.
+ *
+ * @return true if object array, false otherwise
+ */
+ public boolean isObjectArray() {
+ return objectArray;
+ }
+
+ /**
+ * Check if this class is an string array type.
+ *
+ * @return true if string array, false otherwise
+ */
+ public boolean isStringArray() {
+ return stringArray;
+ }
+
+ /**
+ * Check if this class is a collection type.
+ *
+ * @return true if collection, false otherwise
+ */
+ public boolean isCollection() {
+ return collection;
+ }
+
+ /**
+ * Check if this class is a set type.
+ *
+ * @return true if set, false otherwise
+ */
+ public boolean isSet() {
+ return set;
+ }
+
+ /**
+ * Check if this class is a map type.
+ *
+ * @return true if map, false otherwise
+ */
+ public boolean isMap() {
+ return map;
+ }
+
+ /**
+ * Check if this class is deprecated.
+ *
+ * @return true if deprecated, false otherwise
+ */
+ public boolean isDeprecated() {
+ return deprecated;
+ }
+
+ /**
+ * Check if this class is an interface.
+ *
+ * @return true if it is an interface, false otherwise
+ */
+ public boolean isInterface() {
+ return interfaceType;
+ }
+
+ /**
+ * Check if this class is a record.
+ *
+ * @return true if it is a record, false otherwise
+ */
+ public boolean isRecord() {
+ return recordType;
+ }
+
+ /**
+ * Check if this class is an enum.
+ *
+ * @return true if enum, false otherwise
+ */
+ public boolean isEnum() {
+ return enumType;
+ }
+
+ /**
+ * Check if this class is an exception.
+ *
+ * @return true if exception, false otherwise
+ */
+ public boolean isException() {
+ return exceptionType;
+ }
+
+ /**
+ * Check if this class is abstract.
+ *
+ * @return true if abstract, false otherwise
+ */
+ public boolean isAbstract() {
+ return abstractType;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final TypeEntry typeEntry = (TypeEntry) o;
+ return new EqualsBuilder().append(type, typeEntry.type).isEquals();
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder(17, 37).append(type).toHashCode();
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .append("type", type)
+ .append("facade", facade)
+ .append("psiClass", psiClass)
+ .append("primitiveArray", primitiveArray)
+ .append("objectArray", objectArray)
+ .append("stringArray", stringArray)
+ .append("array", array)
+ .append("collection", collection)
+ .append("set", set)
+ .append("map", map)
+ .append("primitive", primitive)
+ .append("voidType", voidType)
+ .append("enumType", enumType)
+ .append("exceptionType", exceptionType)
+ .append("abstractType", abstractType)
+ .append("deprecated", deprecated)
+ .append("interfaceType", interfaceType)
+ .append("recordType", recordType)
+ .append("simpleName", simpleName)
+ .append("qualifiedName", qualifiedName)
+ .append("packageName", packageName)
+ .append("superClass", superClass)
+ .append("elementType", elementType)
+ .append("keyType", keyType)
+ .append("valueType", valueType)
+ .append("fields", fields)
+ .append("allFields", allFields)
+ .append("methods", methods)
+ .append("allMethods", allMethods)
+ .append("members", members)
+ .append("allMembers", allMembers)
+ .append("interfaces", interfaces)
+ .append("superClasses", superClasses)
+ .append("typeParameters", typeParameters)
+ .toString();
+ }
+}
diff --git a/src/me/lotabout/codegenerator/worker/JavaBodyWorker.java b/src/main/java/me/lotabout/codegenerator/worker/JavaBodyWorker.java
similarity index 59%
rename from src/me/lotabout/codegenerator/worker/JavaBodyWorker.java
rename to src/main/java/me/lotabout/codegenerator/worker/JavaBodyWorker.java
index 7bd2d5a..cdbeebf 100644
--- a/src/me/lotabout/codegenerator/worker/JavaBodyWorker.java
+++ b/src/main/java/me/lotabout/codegenerator/worker/JavaBodyWorker.java
@@ -1,6 +1,13 @@
package me.lotabout.codegenerator.worker;
-import com.intellij.codeInsight.generation.GenerateMembersUtil;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.java.generate.config.DuplicationPolicy;
+
import com.intellij.codeInsight.generation.GenerationInfo;
import com.intellij.codeInsight.generation.PsiGenerationInfo;
import com.intellij.codeInsight.hint.HintManager;
@@ -11,53 +18,59 @@
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogBuilder;
import com.intellij.openapi.ui.Messages;
-import com.intellij.psi.*;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiField;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiFileFactory;
+import com.intellij.psi.PsiMember;
+import com.intellij.psi.PsiMethod;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.util.IncorrectOperationException;
+
import me.lotabout.codegenerator.ConflictResolutionPolicy;
import me.lotabout.codegenerator.config.CodeTemplate;
import me.lotabout.codegenerator.config.include.Include;
import me.lotabout.codegenerator.util.GenerationUtil;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.java.generate.config.DuplicationPolicy;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
+import static com.intellij.codeInsight.generation.GenerateMembersUtil.insertMembersAtOffset;
+
+import static me.lotabout.codegenerator.util.GenerationUtil.velocityEvaluate;
public class JavaBodyWorker {
private static final Logger logger = Logger.getInstance(JavaBodyWorker.class);
- public static void execute(@NotNull CodeTemplate codeTemplate, List includes, @NotNull PsiClass parentClass,
- @NotNull Editor editor, @NotNull Map context) {
+ public static void execute(@NotNull final CodeTemplate codeTemplate,
+ final List includes, @NotNull final PsiClass parentClass,
+ @NotNull final Editor editor, @NotNull final Map context) {
final Project project = parentClass.getProject();
- String body = GenerationUtil.velocityEvaluate(project, context, null, codeTemplate.template, includes);
- if (logger.isDebugEnabled()) logger.debug("Method body generated from Velocity:\n" + body);
+ final String body = velocityEvaluate(project, context, null, codeTemplate.template, includes);
+ logger.debug("Method body generated from Velocity:\n", body);
final PsiClass fakeClass;
try {
final PsiFile element = PsiFileFactory
- .getInstance(parentClass.getProject()).createFileFromText("filename", JavaFileType.INSTANCE, "class X {" + body + "}");
+ .getInstance(parentClass.getProject())
+ .createFileFromText("filename", JavaFileType.INSTANCE, "class X {" + body + "}");
fakeClass = (PsiClass) element.getLastChild();
CodeStyleManager.getInstance(parentClass.getProject()).reformat(fakeClass);
- } catch (IncorrectOperationException ignore) {
+ } catch (final IncorrectOperationException ignore) {
HintManager.getInstance().showErrorHint(editor, "fail to generate code from template");
return;
}
- List generationInfoList = new ArrayList<>();
- List membersToDelete = new ArrayList<>();
+ final List generationInfoList = new ArrayList<>();
+ final List membersToDelete = new ArrayList<>();
- List allMembers = new ArrayList<>();
+ final List allMembers = new ArrayList<>();
allMembers.addAll(Arrays.asList(fakeClass.getFields()));
allMembers.addAll(Arrays.asList(fakeClass.getMethods()));
allMembers.addAll(Arrays.asList(fakeClass.getInnerClasses()));
- boolean notAskAgain = false;
+ boolean askAgain = true;
ConflictResolutionPolicy policy = ConflictResolutionPolicy.DUPLICATE;
- for (PsiMember member : allMembers) {
+ for (final PsiMember member : allMembers) {
PsiMember existingMember = null;
if (member instanceof PsiField) {
existingMember = parentClass.findFieldByName(member.getName(), false);
@@ -66,12 +79,11 @@ public static void execute(@NotNull CodeTemplate codeTemplate, List inc
} else if (member instanceof PsiClass) {
existingMember = parentClass.findInnerClassByName(member.getName(), false);
}
-
- if (!notAskAgain) {
+ if (askAgain) {
policy = handleExistedMember(codeTemplate, member, existingMember);
- notAskAgain = policy == ConflictResolutionPolicy.DUPLICATE_ALL || policy == me.lotabout.codegenerator.ConflictResolutionPolicy.REPLACE_ALL;
+ askAgain = (policy != ConflictResolutionPolicy.DUPLICATE_ALL)
+ && (policy != ConflictResolutionPolicy.REPLACE_ALL);
}
-
switch (policy) {
case CANCEL:
return;
@@ -90,53 +102,46 @@ public static void execute(@NotNull CodeTemplate codeTemplate, List inc
// delete all members
membersToDelete.forEach(PsiElement::delete);
- int offset = 0;
- switch (codeTemplate.insertNewMethodOption) {
- case AT_CARET:
- offset = editor.getCaretModel().getOffset();
- break;
- case AT_THE_END_OF_A_CLASS:
- offset = parentClass.getTextRange().getEndOffset() - 1;
- }
- GenerateMembersUtil.insertMembersAtOffset(parentClass.getContainingFile(), offset, generationInfoList);
+ final int offset = switch (codeTemplate.insertNewMethodOption) {
+ case AT_CARET -> editor.getCaretModel().getOffset();
+ case AT_THE_END_OF_A_CLASS -> parentClass.getTextRange().getEndOffset() - 1;
+ default -> 0;
+ };
+ insertMembersAtOffset(parentClass.getContainingFile(), offset, generationInfoList);
// auto import
- JavaCodeStyleManager.getInstance(parentClass.getProject()).shortenClassReferences(parentClass.getContainingFile());
- } catch (Exception e) {
- e.printStackTrace();
+ JavaCodeStyleManager
+ .getInstance(parentClass.getProject())
+ .shortenClassReferences(parentClass.getContainingFile());
+ } catch (final Exception e) {
+ logger.error(e.getMessage(), e);
GenerationUtil.handleException(parentClass.getProject(), e);
}
});
}
- private static ConflictResolutionPolicy handleExistedMember(@NotNull CodeTemplate codeTemplate, PsiMember member, PsiMember existingMember) {
+ private static ConflictResolutionPolicy handleExistedMember(@NotNull final CodeTemplate codeTemplate,
+ final PsiMember member, final PsiMember existingMember) {
final DuplicationPolicy dupPolicy = codeTemplate.whenDuplicatesOption;
if (dupPolicy == DuplicationPolicy.ASK && existingMember != null) {
- DialogBuilder builder = new DialogBuilder();
+ final DialogBuilder builder = new DialogBuilder();
builder.setTitle("Replace existing member: " + member.getName() + "?");
builder.addOkAction();
builder.addCancelAction();
-
- int exit = Messages.showDialog("Replace existing member: " + member.getName() + "?",
+ final int exit = Messages.showDialog("Replace existing member: " + member.getName() + "?",
"Member Already Exists",
new String[]{"Yes for All", "Yes", "Cancel", "No", "No for all"},
1, 3,
Messages.getQuestionIcon(),
null);
- switch (exit) {
- case 0:
- return ConflictResolutionPolicy.REPLACE_ALL;
- case 1:
- return ConflictResolutionPolicy.REPLACE;
- case 2:
- return ConflictResolutionPolicy.CANCEL;
- case 3:
- return ConflictResolutionPolicy.DUPLICATE;
- case 4:
- return ConflictResolutionPolicy.DUPLICATE_ALL;
- default:
- return ConflictResolutionPolicy.DUPLICATE;
- }
+ return switch (exit) {
+ case 0 -> ConflictResolutionPolicy.REPLACE_ALL;
+ case 1 -> ConflictResolutionPolicy.REPLACE;
+ case 2 -> ConflictResolutionPolicy.CANCEL;
+ case 3 -> ConflictResolutionPolicy.DUPLICATE;
+ case 4 -> ConflictResolutionPolicy.DUPLICATE_ALL;
+ default -> ConflictResolutionPolicy.DUPLICATE;
+ };
} else if (dupPolicy == DuplicationPolicy.REPLACE) {
return ConflictResolutionPolicy.REPLACE;
}
diff --git a/src/me/lotabout/codegenerator/worker/JavaCaretWorker.java b/src/main/java/me/lotabout/codegenerator/worker/JavaCaretWorker.java
similarity index 53%
rename from src/me/lotabout/codegenerator/worker/JavaCaretWorker.java
rename to src/main/java/me/lotabout/codegenerator/worker/JavaCaretWorker.java
index 177dcd7..603908d 100644
--- a/src/me/lotabout/codegenerator/worker/JavaCaretWorker.java
+++ b/src/main/java/me/lotabout/codegenerator/worker/JavaCaretWorker.java
@@ -1,5 +1,11 @@
package me.lotabout.codegenerator.worker;
+import java.util.List;
+import java.util.Map;
+
+import org.jetbrains.annotations.NotNull;
+
+import com.intellij.codeInsight.hint.HintManager;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
@@ -9,26 +15,25 @@
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiJavaFile;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.util.PsiTreeUtil;
+
import me.lotabout.codegenerator.config.CodeTemplate;
import me.lotabout.codegenerator.config.include.Include;
-import me.lotabout.codegenerator.util.GenerationUtil;
-import org.jetbrains.annotations.NotNull;
-import java.util.List;
-import java.util.Map;
+import static me.lotabout.codegenerator.util.GenerationUtil.velocityEvaluate;
public class JavaCaretWorker {
private static final Logger logger = Logger.getInstance(JavaCaretWorker.class);
- public static void execute(@NotNull CodeTemplate codeTemplate, List includes, @NotNull PsiJavaFile file, @NotNull Editor editor, @NotNull Map context) {
+ public static void execute(@NotNull final CodeTemplate codeTemplate,
+ final List includes, @NotNull final PsiFile file,
+ @NotNull final Editor editor, @NotNull final Map context) {
final Project project = file.getProject();
- String content = GenerationUtil.velocityEvaluate(project, context, null, codeTemplate.template, includes);
- if (logger.isDebugEnabled())
- logger.debug("Method body generated from Velocity:\n" + content);
-
+ final String content = velocityEvaluate(project, context, null, codeTemplate.template, includes);
+ logger.debug("Method body generated from Velocity:\n{}", content);
//Access document, caret, and selection
final Document document = editor.getDocument();
final SelectionModel selectionModel = editor.getSelectionModel();
@@ -38,9 +43,20 @@ public static void execute(@NotNull CodeTemplate codeTemplate, List inc
WriteCommandAction.runWriteCommandAction(project, () -> {
document.replaceString(start, end, content);
PsiDocumentManager.getInstance(project).commitDocument(document);
- PsiElement element = file.findElementAt(editor.getCaretModel().getOffset());
- PsiClass clazz = PsiTreeUtil.getParentOfType(element, PsiClass.class, false);
- JavaCodeStyleManager.getInstance(project).shortenClassReferences(clazz.getContainingFile());
+ if (file instanceof PsiJavaFile) {
+ final PsiElement element = file.findElementAt(editor.getCaretModel().getOffset());
+ final PsiClass clazz = PsiTreeUtil.getParentOfType(element, PsiClass.class, false);
+ if (clazz == null) {
+ logger.error("Cannot find PsiClass from the current caret position");
+ HintManager
+ .getInstance()
+ .showErrorHint(editor, "Cannot find PsiClass from the current caret position");
+ return;
+ }
+ JavaCodeStyleManager
+ .getInstance(project)
+ .shortenClassReferences(clazz.getContainingFile());
+ }
});
selectionModel.removeSelection();
}
diff --git a/src/me/lotabout/codegenerator/worker/JavaClassWorker.java b/src/main/java/me/lotabout/codegenerator/worker/JavaClassWorker.java
similarity index 67%
rename from src/me/lotabout/codegenerator/worker/JavaClassWorker.java
rename to src/main/java/me/lotabout/codegenerator/worker/JavaClassWorker.java
index 3a6ed57..07fa1fa 100644
--- a/src/me/lotabout/codegenerator/worker/JavaClassWorker.java
+++ b/src/main/java/me/lotabout/codegenerator/worker/JavaClassWorker.java
@@ -1,5 +1,12 @@
package me.lotabout.codegenerator.worker;
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang3.StringUtils;
+import org.jetbrains.annotations.NotNull;
+
import com.intellij.ide.highlighter.JavaFileType;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.WriteCommandAction;
@@ -13,39 +20,40 @@
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
-import com.intellij.psi.*;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiFileFactory;
+import com.intellij.psi.PsiJavaFile;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.impl.PsiManagerImpl;
import com.intellij.psi.impl.file.PsiDirectoryFactory;
import com.intellij.psi.impl.file.PsiDirectoryImpl;
+
import me.lotabout.codegenerator.config.CodeTemplate;
import me.lotabout.codegenerator.config.include.Include;
import me.lotabout.codegenerator.util.GenerationUtil;
-import me.lotabout.codegenerator.util.PackageUtil;
-import org.apache.commons.lang.StringUtils;
-import org.jetbrains.annotations.NotNull;
-import java.io.File;
-import java.util.List;
-import java.util.Map;
+import static me.lotabout.codegenerator.util.PackageUtil.findOrCreateDirectoryForPackage;
+import static me.lotabout.codegenerator.util.PackageUtil.findSourceDirectoryByModuleName;
public class JavaClassWorker {
private static final Logger logger = Logger.getInstance(JavaClassWorker.class);
- public static void execute(@NotNull CodeTemplate codeTemplate, @NotNull List includes, @NotNull PsiJavaFile selectedFile, @NotNull
- Map context) {
+ public static void execute(@NotNull final CodeTemplate codeTemplate,
+ @NotNull final List includes,
+ @NotNull final PsiJavaFile selectedFile,
+ @NotNull final Map context) {
try {
final Project project = selectedFile.getProject();
-
// fetch necessary parameters
-
final String className;
final String packageName;
- final String FQClass = GenerationUtil.velocityEvaluate(project, context, context, codeTemplate.classNameVm, includes);
+ final String FQClass = GenerationUtil.velocityEvaluate(project,
+ context, context, codeTemplate.classNameVm, includes);
if (logger.isDebugEnabled()) logger.debug("FQClass generated\n" + FQClass);
- int index = FQClass.lastIndexOf(".");
+ final int index = FQClass.lastIndexOf(".");
if (index >= 0) {
packageName = FQClass.substring(0, index);
className = FQClass.substring(index + 1);
@@ -69,18 +77,25 @@ public static void execute(@NotNull CodeTemplate codeTemplate, @NotNull List FileEditorManager.getInstance(project).openFile(addedFile.getVirtualFile(), true, true));
- } catch (Exception e) {
- e.printStackTrace();
+ .invokeLater(() -> FileEditorManager
+ .getInstance(project)
+ .openFile(addedFile.getVirtualFile(), true, true));
+ } catch (final Exception e) {
+ logger.error("Failed to create file: {}", e.getMessage());
GenerationUtil.handleException(project, e);
}
});
- }catch (Exception e){
- e.printStackTrace();
+ } catch (final Exception e){
+ logger.error("Failed to generate code: " + e.getMessage(), e);
}
}
private static boolean userConfirmedOverride() {
- return Messages.showYesNoDialog("Overwrite?", "File Exists", null) == Messages.OK;
+ return Messages.showYesNoDialog("Overwrite?", "File Exists", null) == Messages.YES;
}
}
diff --git a/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml
similarity index 54%
rename from resources/META-INF/plugin.xml
rename to src/main/resources/META-INF/plugin.xml
index 87c20e4..7770f67 100644
--- a/resources/META-INF/plugin.xml
+++ b/src/main/resources/META-INF/plugin.xml
@@ -1,8 +1,8 @@
me.lotabout.codegenerator
Code Generator
- 1.5.2
- Jinzhou Zhang
+ 2.0.4
+ Haixing Hu
+ Fix the bug that the plugin configuration panel cannot be opened in Intellij IDEA 251.*.
+ version 2.0.3 Support Intellij IDEA 253.*.
+ version 2.0.2 Fix the potential bugs that comparing two `PsiClassType` may cause `PsiInvalidElementAccessException`.
+ version 2.0.1 Minor refactor.
+ version 2.0.0 Refactor a lot of codes.
+ version 1.8.1 Add the utility module `NameUtilEx`, which is an extension of `com.intellij.psi.codeStyle.NameUtil`;
+ Add the utility module `StringUtilEx`, which is an extension of `com.intellij.openapi.util.text.StringUtil`;
+ Add some utility methods to `MemberEntry`; Support remove multiple selected templates.
+
+ version 1.8.0 Support generating code in non-Java files
+ version 1.7.2 Intellij 2024.3 support
+ version 1.7.0 Refactor
+ version 1.6.0 Intellij 2023.2 support
version 1.5.2 Default loaded include files - to add complex logic to use for className generation
version 1.5.1 Sub include parsing
- version 1.5.0 Intellij 2021.3 support
- version 1.5.0 Adds isAnnotatedWith method for field and method entities
- version 1.5.0 Adds default target module path and default target package name for class generation
- version 1.5.0 Macro support - Adds include support to define common shared macros
- version 1.5.0 Adds copy template functionality
- version 1.4.1 Fix "Replace existing" policy when no existing member is found
- version 1.4.0 compatible with 2020.1
- version 1.3.3 fix: pipeline settings could not be saved.
- version 1.3.2 support idea 2016.3 and after
- version 1.3.1 move Code Generator Menu to Generate menu
- version 1.3 support insert at caret. Fix bugs
- version 1.2 Able to import/export settings
- version 1.1 Able to define workflows for selecting members/classes
- version 1.0 First release
+ version 1.5.0 Intellij 2021.3 support
+ version 1.5.0 Adds isAnnotatedWith method for field and method entities
+ version 1.5.0 Adds default target module path and default target package name for class generation
+ version 1.5.0 Macro support - Adds include support to define common shared macros
+ version 1.5.0 Adds copy template functionality
+ version 1.4.1 Fix "Replace existing" policy when no existing member is found
+ version 1.4.0 compatible with 2020.1
+ version 1.3.3 fix: pipeline settings could not be saved.
+ version 1.3.2 support idea 2016.3 and after
+ version 1.3.1 move Code Generator Menu to Generate menu
+ version 1.3 Support insert at caret. Fix bugs
+ version 1.2 Able to import/export settings
+ version 1.1 Able to define workflows for selecting members/classes
+ version 1.0 First release
]]>
-
+
@@ -56,6 +69,8 @@
+
@@ -63,20 +78,17 @@
-
-
- me.lotabout.codegenerator.CodeGenerator
-
-
+ com.intellij.modules.java
- com.intellij.modules.java
-
-
+
\ No newline at end of file
diff --git a/resources/template/HUE-Serialization.xml b/src/main/resources/template/HUE-Serialization.xml
similarity index 100%
rename from resources/template/HUE-Serialization.xml
rename to src/main/resources/template/HUE-Serialization.xml
diff --git a/resources/template/default-include.vm b/src/main/resources/template/default-include.vm
similarity index 100%
rename from resources/template/default-include.vm
rename to src/main/resources/template/default-include.vm
diff --git a/src/main/resources/template/default.vm b/src/main/resources/template/default.vm
new file mode 100644
index 0000000..963c621
--- /dev/null
+++ b/src/main/resources/template/default.vm
@@ -0,0 +1,185 @@
+## Tutorial for writing your templates
+##
+## 1. First you need to know basic syntax of velocity[1].
+## 2. Then it is necessary to understand the variable that CodeGenerator provides
+## and its inner structure for retrieving the information you need for generating code.
+## 3. Learn to use the utils provided so that you can ask for further information
+## or reduce your workload.
+##
+## Variables Provided (Class Mode)
+## -------------------------------
+## Class mode means you want to create new classes(file).
+##
+## - ClassName: String The name spcified by `Target Class Name`
+## - PackageName: String The package name specified by `Target Class Name`
+## - class0: ClassEntry The class that the action is triggered upon
+## - raw: PsiClass
+## - type: PsiClassType
+## - elementType: ClassEntry The type of the element of the collection/array if the class is a collection/array
+## - keyType: ClassEntry The type of the key of the map if the class is a map
+## - valueType: ClassEntry The type of the value of the map if the class is a map
+## - array: boolean
+## - colleciton: boolean
+## - set: boolean
+## - map: boolean
+## - packageName: String
+## - importList: List
+## - fields: List
+## - allFields: List
+## - methods: List
+## - allMethods: List
+## - innerClasses: List
+## - allInnerClasses: List
+## - typeParamList: List
+## - name: String The simple name of this class.
+## - qualifiedName: String The qualified name of this class.
+## - superClass: ClassEntry The superclass of this class
+## - superName: String The simple name of the superclass of this class
+## - superQualifiedName: String The qualified name of the superclass of this class
+## - typeParams: int The number of parameters of this type
+## - hasSuper: boolean
+## - deprecated: boolean
+## - enum: boolean
+## - exception: boolean
+## - abstract: boolean
+## - implementNames: String[]
+## - isImplements(String): bool Whether this class implements the specified interface, include directly implements, or indirectly implements.
+## - isExtends(String): bool
+## - matchName(String): bool Whether the class name matches the specified name.
+##
+## - class1: ClassEntry The first selected class, where `1` is the postfix
+## you specify in pipeline
+## ...
+##
+## - MemberEntry (FieldEntry/MethodEntry common properties)
+## - raw: PsiField (for field), PsiMethod (for method)
+## - element: FieldElement (for field), MethodElement (for method)
+## - name: String
+## - accessor: String
+## - array: boolean
+## - nestedArray: boolean
+## - primitiveArray: boolean
+## - stringArray: boolean
+## - objectArray: boolean
+## - collection: boolean
+## - list: boolean
+## - set: boolean
+## - map: boolean
+## - primitive: boolean
+## - numeric: boolean
+## - boolean: boolean
+## - char: boolean
+## - byte: boolean
+## - short: boolean
+## - int: boolean
+## Check whether the field is a primitive int type or a Integer type. This
+## property is abscent in the Element interface. We have add this property
+## in the MemberEntry interface.
+## - long: boolean
+## Check whether the field is a primitive long type or a Long type. This
+## property has a BUG in the default implementation of AbstractElement class.
+## We have fixed this bug in the MemberEntry interface.
+## - float: boolean
+## - double: boolean
+## - void: boolean
+## - string: boolean
+## - object: boolean
+## - date: boolean
+## - calendar: boolean
+## - instant: boolean
+## Check whether the field is a java.time.Instant type. This property is
+## abscent in the Element interface. We have add this property in the
+## MemberEntry interface.
+## - localDate: boolean
+## Check whether the field is a java.time.LocalDate type. This property is
+## abscent in the Element interface. We have add this property in the
+## MemberEntry interface.
+## - localTime: boolean
+## Check whether the field is a java.time.LocalTime type. This property is
+## abscent in the Element interface. We have add this property in the
+## MemberEntry interface.
+## - localDateTime: boolean
+## Check whether the field is a java.time.LocalDateTime type. This property is
+## abscent in the Element interface. We have add this property in the
+## MemberEntry interface.
+## - type: String
+## - typeName: String
+## - typeQualifiedName: String
+## - notNull: boolean
+## - modifierStatic: boolean
+## - modifierPublic: boolean
+## - modifierProtected: boolean
+## - modifierPackageLocal: boolean
+## - modifierPrivate: boolean
+## - modifierFinal: boolean
+## - hasAnnotation(String fullyQualifiedName): bool
+## Check if the member has the annotation with the fully qualified name.
+## If the member does has the annotation with the fully qualified name,
+## this method will also check the simple name of the annotation.
+## - getAnnotation(String fullyQualifiedName): PsiAnnotation
+## Get the annotation with the fully qualified name.
+## If the annotation with the fully qualified name does not exist, this method
+## will try to get the annotation with the simple name of the fully qualified name.
+## - getAnnotationAttribute(String fullyQualifiedName, String attributeName): String
+## Get the value of the attribute of the annotation with the fully qualified name.
+## If the attribute does not exist, this method will try to get the attribute of the
+## annotation with the simple name of the fully qualified name.
+## - getAnnotationAttributes(final String fullyQualifiedName, final String attributeName): String[]
+## Get the values of the attribute of the annotation with the fully qualified name.
+## If the attribute does not exist, this method will try to get the attribute of the
+## annotation with the simple name of the fully qualified name.
+## If the attribute has a single value, this method will return an array with the
+## text representation of the single value; if the attribute has multiple values, this
+## method will return an array with the text representation of the multiple values.
+##
+## - FieldEntry
+## - constant: boolean
+## - modifierTransient: boolean
+## - modifierVolatile: boolean
+## - enum: boolean
+## - matchName(String): bool
+##
+## - MethodEntry
+## - methodName: String
+## - fieldName: String
+## - modifierAbstract: boolean
+## - modifierSynchronzied: boolean
+## - modifierSynchronized: boolean
+## - returnTypeVoid: boolean
+## - getter: boolean
+## - deprecated: boolean
+## - matchName(String): bool
+##
+## Variables for Body Mode
+## -----------------------
+## - class0: ClassEntry The current class
+## - fields: List All selected fields
+## - methods: List All selected methods
+## - members: List selected fields+methods
+## - parentMethod: MethodEntry The nearest method that surround the current cursor
+##
+## Utilities
+## ---------
+## - settings: CodeStyleSettings settings of code style
+## - project: Project The project instance, normally used by Psi related utilities
+## - helper: GenerationHelper
+## - StringUtil: Class The class com.intellij.openapi.util.text.StringUtil
+## - StringUtilEx: Class Extension of StringUtil, including all static functions in StrinUtil, with additional
+## functions: parseArrayInitializerText(text), unquoteStringArray(array)
+## - NameUtil: Class The class com.intellij.psi.codeStyle.NameUtil
+## - NameUtilEx: Class Extension of NameUtil, including all static funtions in NameUtil, with additional
+## functions: lowercaseAndUnderscore(name), getGetterName(field), getSetterName(field)
+## - PsiShortNamesCache: Class utility to search classes
+## - PsiJavaPsiFacade: Class Java specific utility to search classes
+## - GlobalSearchScope: Class class to create search scopes, used by above utilities
+## - EntryFactory: Class EntryFactory.of(...) to turn PsiXXX to XXXEntry.
+##
+## Other feature
+## -------------
+## - Auto import. If the generated code contains full qualified name, Code Generator will try to
+## import the packages automatically and shorten the name.
+## For example `java.util.List<>` -> `List<>`
+##
+## References
+## ----------
+## - Velocity syntax: http://velocity.apache.org/engine/1.7/user-guide.html
diff --git a/resources/template/getters-and-setters.xml b/src/main/resources/template/getters-and-setters.xml
similarity index 100%
rename from resources/template/getters-and-setters.xml
rename to src/main/resources/template/getters-and-setters.xml
diff --git a/resources/template/to-string.xml b/src/main/resources/template/to-string.xml
similarity index 100%
rename from resources/template/to-string.xml
rename to src/main/resources/template/to-string.xml
diff --git a/src/main/resources/velocity-tools.xml b/src/main/resources/velocity-tools.xml
new file mode 100644
index 0000000..ddbb9bd
--- /dev/null
+++ b/src/main/resources/velocity-tools.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/me/lotabout/codegenerator/CodeGenerator.java b/src/me/lotabout/codegenerator/CodeGenerator.java
deleted file mode 100644
index 89222a9..0000000
--- a/src/me/lotabout/codegenerator/CodeGenerator.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package me.lotabout.codegenerator;
-
-import com.intellij.openapi.components.ApplicationComponent;
-import org.jetbrains.annotations.NotNull;
-
-public class CodeGenerator implements ApplicationComponent {
- public CodeGenerator() {
- }
-
- @Override
- public void initComponent() {}
-
- @Override
- public void disposeComponent() {
- }
-
- @Override
- @NotNull
- public String getComponentName() {
- return "me.lotabout.codegenerator.CodeGenerator";
- }
-}
diff --git a/src/me/lotabout/codegenerator/CodeGeneratorSettings.java b/src/me/lotabout/codegenerator/CodeGeneratorSettings.java
deleted file mode 100644
index cc11e63..0000000
--- a/src/me/lotabout/codegenerator/CodeGeneratorSettings.java
+++ /dev/null
@@ -1,99 +0,0 @@
-package me.lotabout.codegenerator;
-
-import com.intellij.openapi.components.PersistentStateComponent;
-import com.intellij.openapi.components.State;
-import com.intellij.openapi.components.Storage;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.util.io.FileUtil;
-import com.intellij.util.xmlb.XmlSerializerUtil;
-import me.lotabout.codegenerator.config.CodeTemplate;
-import me.lotabout.codegenerator.config.CodeTemplateList;
-import me.lotabout.codegenerator.config.include.Include;
-import org.jetbrains.annotations.Nullable;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Optional;
-
-@State(name = "CodeGeneratorSettings", storages = {@Storage("$APP_CONFIG$/CodeGenerator-settings.xml")})
-public class CodeGeneratorSettings implements PersistentStateComponent {
-
- private static final Logger LOGGER = Logger.getInstance(CodeGeneratorSettings.class);
- private List codeTemplates;
- private List includes;
-
- public CodeGeneratorSettings() {
-
- }
-
- public List getIncludes() {
- if (includes == null) {
- includes = new ArrayList<>();
- }
- return includes;
- }
-
- public void setIncludes(List includes) {
- this.includes = includes;
- }
-
- public CodeGeneratorSettings setCodeTemplates(List codeTemplates) {
- this.codeTemplates = codeTemplates;
- return this;
- }
-
-
- @Nullable
- @Override
- public CodeGeneratorSettings getState() {
- if (codeTemplates == null) {
- codeTemplates = loadDefaultTemplates();
- }
- return this;
- }
-
- @Override
- public void loadState(CodeGeneratorSettings codeGeneratorSettings) {
- XmlSerializerUtil.copyBean(codeGeneratorSettings, this);
- }
-
- public List getCodeTemplates() {
- if (codeTemplates == null) {
- codeTemplates = loadDefaultTemplates();
- }
- return codeTemplates;
- }
-
- public Optional getCodeTemplate(String templateId) {
- return codeTemplates.stream()
- .filter(t -> t != null && t.getId().equals(templateId))
- .findFirst();
- }
-
- public Optional getInclude(String includeId) {
- return includes.stream()
- .filter(t -> t != null && t.getId().equals(includeId))
- .findFirst();
- }
-
- public void removeCodeTemplate(String templateId) {
- codeTemplates.removeIf(template -> template.name.equals(templateId));
- }
-
- private List loadDefaultTemplates() {
- List templates = new ArrayList<>();
- try {
- templates.addAll(loadTemplates("getters-and-setters.xml"));
- templates.addAll(loadTemplates("to-string.xml"));
- templates.addAll(loadTemplates("HUE-Serialization.xml"));
- } catch (Exception e) {
- LOGGER.error("loadDefaultTemplates failed", e);
- }
- return templates;
- }
-
- private List loadTemplates(String templateFileName) throws IOException {
- return CodeTemplateList.fromXML(FileUtil.loadTextAndClose(CodeGeneratorSettings.class.getResourceAsStream("/template/" + templateFileName)));
- }
-}
diff --git a/src/me/lotabout/codegenerator/action/CodeGeneratorAction.java b/src/me/lotabout/codegenerator/action/CodeGeneratorAction.java
deleted file mode 100644
index ff1e503..0000000
--- a/src/me/lotabout/codegenerator/action/CodeGeneratorAction.java
+++ /dev/null
@@ -1,351 +0,0 @@
-package me.lotabout.codegenerator.action;
-
-import com.intellij.codeInsight.generation.PsiElementClassMember;
-import com.intellij.codeInsight.generation.PsiFieldMember;
-import com.intellij.codeInsight.generation.PsiMethodMember;
-import com.intellij.codeInsight.hint.HintManager;
-import com.intellij.ide.highlighter.JavaFileType;
-import com.intellij.ide.util.MemberChooser;
-import com.intellij.ide.util.TreeClassChooser;
-import com.intellij.ide.util.TreeClassChooserFactory;
-import com.intellij.openapi.actionSystem.AnAction;
-import com.intellij.openapi.actionSystem.AnActionEvent;
-import com.intellij.openapi.actionSystem.DataKeys;
-import com.intellij.openapi.actionSystem.Presentation;
-import com.intellij.openapi.components.ServiceManager;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.editor.Editor;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.roots.ProjectRootManager;
-import com.intellij.openapi.ui.DialogWrapper;
-import com.intellij.openapi.ui.Messages;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.psi.*;
-import com.intellij.psi.search.GlobalSearchScope;
-import com.intellij.psi.util.PsiTreeUtil;
-import com.intellij.psi.util.PsiUtil;
-import com.intellij.util.IncorrectOperationException;
-import me.lotabout.codegenerator.CodeGeneratorSettings;
-import me.lotabout.codegenerator.config.ClassSelectionConfig;
-import me.lotabout.codegenerator.config.CodeTemplate;
-import me.lotabout.codegenerator.config.MemberSelectionConfig;
-import me.lotabout.codegenerator.config.PipelineStep;
-import me.lotabout.codegenerator.util.EntryFactory;
-import me.lotabout.codegenerator.util.GenerationUtil;
-import me.lotabout.codegenerator.util.MemberEntry;
-import me.lotabout.codegenerator.worker.JavaBodyWorker;
-import me.lotabout.codegenerator.worker.JavaCaretWorker;
-import me.lotabout.codegenerator.worker.JavaClassWorker;
-import org.apache.commons.lang.StringUtils;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-import org.jetbrains.java.generate.config.Config;
-import org.jetbrains.java.generate.config.FilterPattern;
-import org.jetbrains.java.generate.exception.GenerateCodeException;
-
-import javax.swing.*;
-import java.awt.*;
-import java.io.File;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
-
-public class CodeGeneratorAction extends AnAction {
- private static final Logger logger = Logger.getInstance(CodeGeneratorAction.class);
- private final String templateKey;
- private final CodeGeneratorSettings settings;
-
- public CodeGeneratorAction(String templateKey, String templateName) {
- getTemplatePresentation().setDescription("description");
- getTemplatePresentation().setText(templateName, false);
-
- this.settings = ServiceManager.getService(CodeGeneratorSettings.class);;
- this.templateKey = templateKey;
- }
-
- @Override
- public boolean startInTransaction() {
- return true;
- }
-
- @Override
- public void update(AnActionEvent e) {
- // Code Generation action could run without editor
- Presentation presentation = e.getPresentation();
- Project project = e.getProject();
- if (project == null) {
- presentation.setEnabled(false);
- }
-
- PsiFile file = e.getDataContext().getData(DataKeys.PSI_FILE);
- if (file == null || !(file instanceof PsiJavaFile)) {
- presentation.setEnabled(false);
- }
-
- presentation.setEnabled(true);
- }
-
- @Override public void actionPerformed(AnActionEvent e) {
- final CodeTemplate codeTemplate = settings.getCodeTemplate(templateKey).orElseThrow(IllegalStateException::new);
- Project project = e.getProject();
- assert project != null;
-
- PsiFile file = e.getDataContext().getData(DataKeys.PSI_FILE);
- assert file != null && file instanceof PsiJavaFile;
-
- PsiJavaFile javaFile = (PsiJavaFile)file;
-
- Editor editor = e.getDataContext().getData(DataKeys.EDITOR);
-
- Map contextMap = executePipeline(codeTemplate, javaFile, editor);
- if (contextMap == null) {
- // early return from pipeline
- return;
- }
-
- switch (codeTemplate.type) {
- case "class":
- JavaClassWorker.execute(codeTemplate, settings.getIncludes(), javaFile, contextMap);
- break;
- case "body":
- assert editor != null;
- PsiClass clazz = getSubjectClass(editor, javaFile);
- if (clazz == null) {
- HintManager.getInstance().showErrorHint(editor, "no parent class found for current cursor position");
- return;
- }
-
- JavaBodyWorker.execute(codeTemplate, settings.getIncludes(), clazz, editor, contextMap);
- break;
- case "caret":
- assert editor != null;
- JavaCaretWorker.execute(codeTemplate, settings.getIncludes(), javaFile, editor, contextMap);
- break;
- default:
- throw new IllegalStateException("template type is not recognized: " + codeTemplate.type);
- }
- }
-
- private Map executePipeline(@NotNull CodeTemplate codeTemplate, @NotNull final PsiJavaFile file, final Editor editor) {
- final Project project = file.getProject();
- logger.debug("+++ executePipeline - START +++");
- if (logger.isDebugEnabled()) {
- logger.debug("Current project " + project.getName());
- }
-
- Map contextMap = new HashMap<>();
- PsiClass clazz = getSubjectClass(editor, file);
- if (clazz == null) {
- clazz = buildFakeClassForEmptyFile(file);
- }
- contextMap.put("class0", EntryFactory.of(clazz));
-
- if (editor != null) {
- int offset = editor.getCaretModel().getOffset();
- PsiElement context = file.findElementAt(offset);
- PsiMethod parentMethod = PsiTreeUtil.getParentOfType(context, PsiMethod.class, false);
- contextMap.put("parentMethod", EntryFactory.of(parentMethod));
- }
-
- logger.debug("Select member/class through pipeline");
- for (PipelineStep step : codeTemplate.pipeline) {
- if (!step.enabled()) continue;
- switch (step.type()) {
- case "class-selection":
- PsiClass selectedClass = selectClass(file, (ClassSelectionConfig) step, contextMap);
- if (selectedClass == null) return null;
- contextMap.put("class" + step.postfix(), EntryFactory.of(selectedClass));
- break;
- case "member-selection":
- List selectedMembers = selectMember(file, (MemberSelectionConfig) step, contextMap);
- if (selectedMembers == null) return null;
- GenerationUtil.insertMembersToContext(selectedMembers,
- Collections.emptyList(),
- contextMap,
- step.postfix(),
- ((MemberSelectionConfig) step).sortElements);
- break;
- default:
- throw new IllegalStateException("step type not recognized: " + step.type());
- }
- }
-
- return contextMap;
- }
-
- @Nullable
- private static PsiClass getSubjectClass(Editor editor, @NotNull final PsiJavaFile file) {
- PsiClass clazz = null;
- if (editor != null) {
- int offset = editor.getCaretModel().getOffset();
- PsiElement context = file.findElementAt(offset);
- if (context == null)
- return null;
-
- clazz = PsiTreeUtil.getParentOfType(context, PsiClass.class, false);
- } else if (file.getClasses().length > 0) {
- clazz = file.getClasses()[0];
- }
-
- return clazz;
- }
-
- private PsiClass selectClass(@NotNull PsiJavaFile file, ClassSelectionConfig config, Map contextMap) {
- String initialClassNameTemplate = config.initialClass;
- Project project = file.getProject();
- try {
- String className = GenerationUtil.velocityEvaluate(project, contextMap, contextMap, initialClassNameTemplate, settings.getIncludes());
- if (logger.isDebugEnabled()) logger.debug("Initial class name for class selection is" + className);
-
- PsiClass initialClass = null;
- if (!StringUtils.isEmpty(className)) {
- initialClass = JavaPsiFacade.getInstance(project).findClass(className, GlobalSearchScope.allScope(project));
- }
-
- if (initialClass == null) {
- if (logger.isDebugEnabled()) logger.debug("could not found initialClass" + className);
- initialClass = file.getClasses().length > 0 ? file.getClasses()[0] : null;
- }
-
- TreeClassChooser chooser = TreeClassChooserFactory.getInstance(project)
- .createProjectScopeChooser("Select a class", initialClass);
- chooser.showDialog();
-
- if (chooser.getSelected() == null) {
- return null;
- }
- return chooser.getSelected();
- } catch (GenerateCodeException e) {
- Messages.showMessageDialog(project, e.getMessage(), "Generate Failed", null);
- }
- return null;
- }
-
- private List selectMember(@NotNull PsiJavaFile file, MemberSelectionConfig config, Map contextMap) {
- final String AVAILABLE_MEMBERS = "availableMembers";
- final String SELECTED_MEMBERS = "selectedMembers";
- final Project project = file.getProject();
-
- if (logger.isDebugEnabled()) logger.debug("start to select members by template: ", config.providerTemplate);
- GenerationUtil.velocityEvaluate(project, contextMap, contextMap, config.providerTemplate,settings.getIncludes());
-
- // members should be MemberEntry[] or PsiMember[]
- List availableMembers = Collections.emptyList();
- List selectedMembers = Collections.emptyList();
- if (contextMap.containsKey(AVAILABLE_MEMBERS)) {
- availableMembers = (List) contextMap.get(AVAILABLE_MEMBERS);
- selectedMembers = (List) contextMap.get(SELECTED_MEMBERS);
- selectedMembers = selectedMembers == null ? availableMembers : selectedMembers;
- }
-
- contextMap.remove(AVAILABLE_MEMBERS);
- contextMap.remove(SELECTED_MEMBERS);
-
- // filter the members by configuration
- PsiElementClassMember[] dialogMembers = buildClassMember(filterMembers(availableMembers, config));
- PsiElementClassMember[] membersSelected = buildClassMember(filterMembers(selectedMembers, config));
-
- if (!config.allowEmptySelection && dialogMembers.length <= 0) {
- Messages.showMessageDialog(project, "No members are provided to select from.\nAnd template doesn't allow empty selection",
- "Warning", Messages.getWarningIcon());
- return null;
- }
-
- final MemberChooser chooser =
- new MemberChooser(dialogMembers, config.allowEmptySelection, config.allowMultiSelection, project, PsiUtil.isLanguageLevel5OrHigher(file), new JPanel(new BorderLayout())) {
- @Nullable
- @Override
- protected String getHelpId() {
- return "editing.altInsert.codegenerator";
- }
- };
- chooser.setTitle("Selection Fields for Code Generation");
- chooser.setCopyJavadocVisible(false);
- chooser.selectElements(membersSelected);
- chooser.show();
-
- if (DialogWrapper.OK_EXIT_CODE != chooser.getExitCode()) {
- return null; // indicate exit
- }
-
- return GenerationUtil.convertClassMembersToPsiMembers(chooser.getSelectedElements());
- }
-
- private static List filterMembers(List