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(""" + + """) + } + + 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 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 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 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 @@ + class="me.lotabout.codegenerator.action.CodeGeneratorGroup" + text="CodeGenerator" + description="Code Generator" + popup="true"> - + + - - - 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 members, final MemberSelectionConfig config) { - 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(List members) { - List ret = members.stream() - .filter(m -> (m instanceof PsiField) || (m instanceof PsiMethod)) - .map(m -> { - if (m instanceof PsiField) { - return new PsiFieldMember((PsiField) m); - } else if (m instanceof PsiMethod) { - return new PsiMethodMember((PsiMethod) m); - } else { - return null; - } - }).collect(Collectors.toList()); - - return ret.toArray(new PsiElementClassMember[ret.size()]); - } - - private static Config generatorConfig2Config(MemberSelectionConfig selectionConfig) { - 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 buildFakeClassForEmptyFile(@NotNull PsiJavaFile file) { - final Project project = file.getProject(); - final VirtualFile moduleRoot = ProjectRootManager.getInstance(project).getFileIndex().getSourceRootForFile(file.getVirtualFile()); - 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, "") - .replaceAll(File.separator, "."); - - try { - final PsiFile element = PsiFileFactory.getInstance(project) - .createFileFromText("filename", JavaFileType.INSTANCE, - "package " + packageName + ";\n" + - "class " + className + "{}"); - return (PsiClass) element.getLastChild(); - } catch (IncorrectOperationException ignore) { - } - return null; - } -} diff --git a/src/me/lotabout/codegenerator/action/CodeGeneratorGroup.java b/src/me/lotabout/codegenerator/action/CodeGeneratorGroup.java deleted file mode 100644 index 416dd27..0000000 --- a/src/me/lotabout/codegenerator/action/CodeGeneratorGroup.java +++ /dev/null @@ -1,80 +0,0 @@ -package me.lotabout.codegenerator.action; - -import com.intellij.openapi.actionSystem.*; -import com.intellij.openapi.components.ServiceManager; -import com.intellij.openapi.editor.Caret; -import com.intellij.openapi.project.DumbAware; -import com.intellij.openapi.project.Project; -import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.intellij.psi.util.PsiTreeUtil; -import me.lotabout.codegenerator.CodeGeneratorSettings; -import me.lotabout.codegenerator.config.CodeTemplate; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.List; -import java.util.stream.Collectors; - -public class CodeGeneratorGroup extends ActionGroup implements DumbAware { - private CodeGeneratorSettings settings; - - public CodeGeneratorGroup() { - settings = ServiceManager.getService(CodeGeneratorSettings.class); - } - - @Override - public boolean hideIfNoVisibleChildren() { - return false; - } - - @NotNull @Override public AnAction[] getChildren(@Nullable AnActionEvent anActionEvent) { - if (anActionEvent == null) { - return AnAction.EMPTY_ARRAY; - } - - Project project = PlatformDataKeys.PROJECT.getData(anActionEvent.getDataContext()); - if (project == null) { - return AnAction.EMPTY_ARRAY; - } - - PsiFile file = anActionEvent.getDataContext().getData(DataKeys.PSI_FILE); - if (file == null) { - return AnAction.EMPTY_ARRAY; - } - - Caret caret = anActionEvent.getDataContext().getData(LangDataKeys.CARET); - boolean isProjectView = caret == null; - - if (!isProjectView) { - // EditorPopup menu - PsiElement element = file.findElementAt(caret.getOffset()); - PsiClass clazz = PsiTreeUtil.getParentOfType(element, PsiClass.class, false); - if (clazz == null) { - // not inside a class - return AnAction.EMPTY_ARRAY; - } - } - - - String fileName = file.getName(); - final List children = settings.getCodeTemplates().stream() - .filter(t -> !isProjectView || (t.type.equals("class") && isProjectView)) - .filter(t -> t.enabled && fileName.matches(t.fileNamePattern)) - .map(CodeGeneratorGroup::getOrCreateAction) - .collect(Collectors.toList()); - - return children.toArray(new AnAction[children.size()]); - } - - private static AnAction getOrCreateAction(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/util/ClassEntry.java b/src/me/lotabout/codegenerator/util/ClassEntry.java deleted file mode 100644 index 6a293d3..0000000 --- a/src/me/lotabout/codegenerator/util/ClassEntry.java +++ /dev/null @@ -1,275 +0,0 @@ -package me.lotabout.codegenerator.util; - -import com.intellij.psi.*; -import java.util.Arrays; -import java.util.stream.Collectors; -import org.jetbrains.java.generate.element.ClassElement; - -import java.util.ArrayList; -import java.util.List; - -public class ClassEntry { - private PsiClass raw; - private ClassElement element; - - private String packageName; - private List importList; - private List fields = new ArrayList<>(); - private List allFields = new ArrayList<>(); - private List methods = new ArrayList<>(); - private List allMethods = new ArrayList<>(); - private List members = new ArrayList<>(); - private List allMembers = new ArrayList<>(); - private List typeParamList; - - public static ClassEntry of(PsiClass clazz, ClassElement element) { - PsiFile psiFile = clazz.getContainingFile(); - ClassEntry entry = new ClassEntry(); - entry.setRaw(clazz); - entry.setElement(element); - entry.setPackageName(((PsiClassOwner)psiFile).getPackageName()); - entry.setImportList(GenerationUtil.getImportList((PsiJavaFile) psiFile)); - entry.addFields(GenerationUtil.getFields(clazz)); - entry.addAllFields(GenerationUtil.getAllFields(clazz)); - entry.addMethod(GenerationUtil.getMethods(clazz)); - entry.addAllMethods(GenerationUtil.getAllMethods(clazz)); - entry.setTypeParamList(GenerationUtil.getClassTypeParameters(clazz)); - return entry; - } - - public List getFields() { - return fields; - } - - public void setFields(List fields) { - this.fields = fields; - } - - public void setAllFields(List allFields) { - this.allFields = allFields; - } - - public void setMethods(List methods) { - this.methods = methods; - } - - public void setAllMethods(List allMethods) { - this.allMethods = allMethods; - } - - public void setMembers(List members) { - this.members = members; - } - - public void setAllMembers(List allMembers) { - this.allMembers = allMembers; - } - - public PsiClass getRaw() { - return raw; - } - - public void setRaw(PsiClass raw) { - this.raw = raw; - } - - public ClassElement getElement() { - return element; - } - - public void setElement(ClassElement element) { - this.element = element; - } - - public String getPackageName() { - return packageName; - } - - public void setPackageName(String packageName) { - this.packageName = packageName; - } - - public List getImportList() { - return importList; - } - - public void setImportList(List importList) { - this.importList = importList; - } - - public void addFields(List fields) { - this.fields.addAll(fields); - this.members.addAll(fields); - } - - public List getAllFields() { - return allFields; - } - - public void addAllFields(List allFields) { - this.allFields = allFields; - this.allMembers.addAll(fields); - } - - public List getMethods() { - return methods; - } - - public void addMethod(List methods) { - this.methods.addAll(methods); - this.members.addAll(methods); - } - - public List getAllMethods() { - return allMethods; - } - - public void addAllMethods(List allMethods) { - this.allMethods.addAll(allMethods); - this.allMembers.addAll(allMethods); - } - - public List getInnerClasses() { - return Arrays.stream(raw.getInnerClasses()) - .map(EntryFactory::of) - .collect(Collectors.toList()); - } - - public List getMembers() { - return members; - } - - public List getAllMembers() { - return allMembers; - } - - public List getAllInnerClasses() { - // lazily turn all inner classes into class entry - return Arrays.stream(raw.getAllInnerClasses()) - .map(EntryFactory::of) - .collect(Collectors.toList()); - } - - public List getTypeParamList() { - return typeParamList; - } - - public void setTypeParamList(List typeParamList) { - this.typeParamList = typeParamList; - } - - public boolean isImplements(String s) { - return element.isImplements(s); - } - - public boolean isExtends(String s) { - return element.isExtends(s); - } - - public boolean matchName(String s) throws IllegalArgumentException { - return element.matchName(s); - } - - public String[] getImplementNames() { - return element.getImplementNames(); - } - - public void setImplementNames(String[] strings) { - element.setImplementNames(strings); - } - - public String getSuperQualifiedName() { - return element.getSuperQualifiedName(); - } - - public void setSuperQualifiedName(String s) { - element.setSuperQualifiedName(s); - } - - public String getSuperName() { - return element.getSuperName(); - } - - public void setSuperName(String s) { - element.setSuperName(s); - } - - public String getName() { - return element.getName(); - } - - public void setName(String s) { - element.setName(s); - } - - public String getQualifiedName() { - return element.getQualifiedName(); - } - - public void setQualifiedName(String s) { - element.setQualifiedName(s); - } - - public boolean isHasSuper() { - return element.isHasSuper(); - } - - public boolean isDeprecated() { - return element.isDeprecated(); - } - - public void setDeprecated(boolean b) { - element.setDeprecated(b); - } - - public boolean isEnum() { - return element.isEnum(); - } - - public void setEnum(boolean b) { - element.setEnum(b); - } - - public boolean isException() { - return element.isException(); - } - - public void setException(boolean b) { - element.setException(b); - } - - public boolean isAbstract() { - return element.isAbstract(); - } - - public void setAbstract(boolean b) { - element.setAbstract(b); - } - - public void setTypeParams(int i) { - element.setTypeParams(i); - } - - public int getTypeParams() { - return element.getTypeParams(); - } - - @Override - public String toString() { - return "ClassEntry{" + - "raw=" + raw + - ", element=" + element + - ", packageName='" + packageName + '\'' + - ", importList=" + importList + - ", fields=" + fields + - ", allFields=" + allFields + - ", methods=" + methods + - ", allMethods=" + allMethods + - ", members=" + members + - ", allMembers=" + allMembers + - ", typeParamList=" + typeParamList + - '}'; - } - - -} diff --git a/src/me/lotabout/codegenerator/util/EntryFactory.java b/src/me/lotabout/codegenerator/util/EntryFactory.java deleted file mode 100644 index ba1a758..0000000 --- a/src/me/lotabout/codegenerator/util/EntryFactory.java +++ /dev/null @@ -1,31 +0,0 @@ -package me.lotabout.codegenerator.util; - -import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiField; -import com.intellij.psi.PsiMethod; -import org.jetbrains.java.generate.element.ElementFactory; -import org.jetbrains.java.generate.element.FieldElement; - -public class EntryFactory { - public static FieldEntry of(PsiField field, boolean useAccessor) { - if (field == null) return null; - return new FieldEntry(field, ElementFactory.newFieldElement(field, useAccessor)); - } - - public static FieldEntry of(PsiClass clazz, FieldElement element) { - if (clazz == null || element == null) return null; - - PsiField field = clazz.findFieldByName(element.getName(), true); - return new FieldEntry(field, element); - } - - public static MethodEntry of(PsiMethod method) { - if (method == null) return null; - return new MethodEntry(method, ElementFactory.newMethodElement(method)); - } - - public static ClassEntry of(PsiClass clazz) { - if (clazz == null) return null; - return ClassEntry.of(clazz, ElementFactory.newClassElement(clazz)); - } -} diff --git a/src/me/lotabout/codegenerator/util/EntryUtils.java b/src/me/lotabout/codegenerator/util/EntryUtils.java deleted file mode 100644 index 3a1f027..0000000 --- a/src/me/lotabout/codegenerator/util/EntryUtils.java +++ /dev/null @@ -1,73 +0,0 @@ -package me.lotabout.codegenerator.util; - -import com.intellij.psi.PsiField; -import com.intellij.psi.PsiMember; -import com.intellij.psi.PsiMethod; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -public class EntryUtils { - public static List getOnlyAsFieldAndMethodElements(Collection members, - Collection selectedNotNullMembers, - boolean useAccessors) { - List - entryList = new ArrayList<>(); - - for (PsiMember member : members) { - MemberEntry entry = null; - if (member instanceof PsiField) { - FieldEntry fieldEntry= EntryFactory.of((PsiField) member, useAccessors); - if (selectedNotNullMembers.contains(member)) { - fieldEntry.setNotNull(true); - } - entry = fieldEntry; - } else if (member instanceof PsiMethod) { - MethodEntry methodEntry = EntryFactory.of((PsiMethod) member); - if (selectedNotNullMembers.contains(member)) { - methodEntry.setNotNull(true); - } - entry = methodEntry; - } - - if (entry != null) { - entryList.add(entry); - } - } - return entryList; - } - - public static List getOnlyAsFieldEntries(Collection members, - Collection selectedNotNullMembers, - boolean useAccessors) { - List fieldEntryList = new ArrayList<>(); - - for (PsiMember member : members) { - if (member instanceof PsiField) { - PsiField field = (PsiField) member; - FieldEntry fe = EntryFactory.of(field, useAccessors); - if (selectedNotNullMembers.contains(member)) { - fe.setNotNull(true); - } - fieldEntryList.add(fe); - } - } - - return fieldEntryList; - } - - public static List getOnlyAsMethodEntrys(Collection members) { - List methodEntryList = new ArrayList<>(); - - for (PsiMember member : members) { - if (member instanceof PsiMethod) { - PsiMethod method = (PsiMethod) member; - MethodEntry me = EntryFactory.of(method); - methodEntryList.add(me); - } - } - - return methodEntryList; - } -} diff --git a/src/me/lotabout/codegenerator/util/FieldEntry.java b/src/me/lotabout/codegenerator/util/FieldEntry.java deleted file mode 100644 index d3ef787..0000000 --- a/src/me/lotabout/codegenerator/util/FieldEntry.java +++ /dev/null @@ -1,259 +0,0 @@ -package me.lotabout.codegenerator.util; - -import com.intellij.psi.PsiField; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.java.generate.element.FieldElement; - -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - -/** - * Wrapper around FieldElement - */ -public class FieldEntry implements MemberEntry { - private final PsiField raw; - private final FieldElement element; - - public FieldEntry(PsiField field, FieldElement element) { - this.raw = field; - this.element = element; - } - - public PsiField getRaw() { - return raw; - } - - public FieldElement getElement() { - return element; - } - - public String getAccessor() { - return element.getAccessor(); - } - - public boolean isConstant() { - return element.isConstant(); - } - - public boolean isModifierTransient() { - return element.isModifierTransient(); - } - - public boolean isModifierVolatile() { - return element.isModifierVolatile(); - } - - public boolean isEnum() { - return element.isEnum(); - } - - public void setEnum(boolean b) { - element.setEnum(b); - } - - public boolean matchName(String s) throws IllegalArgumentException { - return element.matchName(s); - } - - public void setAccessor(String s) { - element.setAccessor(s); - } - - public String getName() { - return element.getName(); - } - - public boolean isArray() { - return element.isArray(); - } - - public boolean isNestedArray() { - return element.isNestedArray(); - } - - public void setNestedArray(boolean b) { - element.setNestedArray(b); - } - - public boolean isCollection() { - return element.isCollection(); - } - - public boolean isMap() { - return element.isMap(); - } - - public boolean isPrimitive() { - return element.isPrimitive(); - } - - public boolean isString() { - return element.isString(); - } - - public boolean isPrimitiveArray() { - return element.isPrimitiveArray(); - } - - public boolean isObjectArray() { - return element.isObjectArray(); - } - - public boolean isNumeric() { - return element.isNumeric(); - } - - public boolean isObject() { - return element.isObject(); - } - - public boolean isDate() { - return element.isDate(); - } - - public boolean isSet() { - return element.isSet(); - } - - public boolean isList() { - return element.isList(); - } - - public boolean isStringArray() { - return element.isStringArray(); - } - - public boolean isCalendar() { - return element.isCalendar(); - } - - public String getTypeName() { - return element.getTypeName().replace(">", ""); - } - - public String getTypeQualifiedName() { - return element.getTypeQualifiedName(); - } - - public boolean isAnnotatedWith(@NotNull String qualifiedName) { - return AnnotationUtil.isAnnotatedWith(raw, qualifiedName); - } - - public String getType() { - return element.getType(); - } - - public void setType(String s) { - element.setType(s); - } - - public boolean isBoolean() { - return element.isBoolean(); - } - - public boolean isLong() { - return element.isLong(); - } - - public void setLong(boolean b) { - element.setLong(b); - } - - public boolean isFloat() { - return element.isFloat(); - } - - public void setFloat(boolean b) { - element.setFloat(b); - } - - public boolean isDouble() { - return element.isDouble(); - } - - public void setDouble(boolean b) { - element.setDouble(b); - } - - public boolean isVoid() { - return element.isVoid(); - } - - public boolean isNotNull() { - return element.isNotNull(); - } - - public void setNotNull(boolean b) { - element.setNotNull(b); - } - - public void setVoid(boolean b) { - element.setVoid(b); - } - - public boolean isChar() { - return element.isChar(); - } - - public void setChar(boolean b) { - element.setChar(b); - } - - public boolean isByte() { - return element.isByte(); - } - - public void setByte(boolean b) { - element.setByte(b); - } - - public boolean isShort() { - return element.isShort(); - } - - public void setShort(boolean b) { - element.setShort(b); - } - - public void setBoolean(boolean b) { - element.setBoolean(b); - } - - public void setName(String s) { - element.setName(s); - } - - public boolean isModifierStatic() { - return element.isModifierStatic(); - } - - public boolean isModifierPublic() { - return element.isModifierPublic(); - } - - public boolean isModifierProtected() { - return element.isModifierProtected(); - } - - public boolean isModifierPackageLocal() { - return element.isModifierPackageLocal(); - } - - public boolean isModifierPrivate() { - return element.isModifierPrivate(); - } - - public boolean isModifierFinal() { - return element.isModifierFinal(); - } - - @Override - public String toString() { - return "FieldEntry{" + - "raw=" + raw + - ", element=" + element + - '}'; - } -} diff --git a/src/me/lotabout/codegenerator/util/GenerationUtil.java b/src/me/lotabout/codegenerator/util/GenerationUtil.java deleted file mode 100644 index b74822b..0000000 --- a/src/me/lotabout/codegenerator/util/GenerationUtil.java +++ /dev/null @@ -1,291 +0,0 @@ -package me.lotabout.codegenerator.util; - -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.*; -import com.intellij.psi.codeStyle.CodeStyleSettingsManager; -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; -import org.apache.velocity.VelocityContext; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.java.generate.element.ElementComparator; -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 java.io.StringWriter; -import java.util.*; -import java.util.stream.Collectors; - -public class GenerationUtil { - private static final Logger logger = Logger.getInstance("#" + GenerationUtil.class.getName()); - - /** - * 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(PsiField[] filteredFields, PsiMethod[] filteredMethods) { - var 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 List classMemberList) { - if (classMemberList == null || classMemberList.isEmpty()) { - return Collections.emptyList(); - } - List psiMemberList = new ArrayList<>(); - - for (var classMember : classMemberList) { - psiMemberList.add(classMember.getElement()); - } - - return psiMemberList; - } - - public static void insertMembersToContext(List members, List notNullMembers, Map context, String postfix, int sortElements) { - logger.debug("insertMembersToContext - adding fields"); - // field information - final List fieldElements = EntryUtils.getOnlyAsFieldEntries(members, notNullMembers, 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.getOnlyAsMethodEntrys(members)); - context.put("methods", EntryUtils.getOnlyAsMethodEntrys(members)); - - // element information (both fields and methods) - logger.debug("Velocity Context - adding members (fields and methods)"); - var elements = EntryUtils.getOnlyAsFieldAndMethodElements(members, notNullMembers, false); - // sort elements if enabled and not using chooser dialog - if (sortElements != 0) { - elements.sort(new ElementComparator(sortElements)); - } - context.put("members" + postfix, elements); - context.put("members", elements); - } - - public static String velocityEvaluate( - @NotNull Project project, - @NotNull Map contextMap, - Map outputContext, - String template, - List includes) throws GenerateCodeException { - if (template == null) { - return null; - } - - var sw = new StringWriter(); - try { - var vc = new VelocityContext(); - - vc.put("settings", CodeStyleSettingsManager.getSettings(project)); - vc.put("project", project); - vc.put("helper", GenerationHelper.class); - vc.put("StringUtil", StringUtil.class); - vc.put("NameUtil", NameUtil.class); - vc.put("PsiShortNamesCache", PsiShortNamesCache.class); - vc.put("JavaPsiFacade", JavaPsiFacade.class); - vc.put("GlobalSearchScope", GlobalSearchScope.class); - vc.put("EntryFactory", EntryFactory.class); - - for (var paramName : contextMap.keySet()) { - vc.put(paramName, contextMap.get(paramName)); - } - - template = updateTemplateWithIncludes(template, includes); - if (logger.isDebugEnabled()) logger.debug("Velocity Template:\n" + template); - - // velocity - var 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 (var key : vc.getKeys()) { - if (key instanceof String) { - outputContext.put((String) key, vc.get((String) key)); - } - } - } - } catch (ProcessCanceledException e) { - throw e; - } catch (Exception e) { - throw new GenerateCodeException("Error in Velocity code generator", e); - } - - return StringUtil.convertLineSeparators(sw.getBuffer().toString()); - } - - @NotNull - private static String updateTemplateWithIncludes(String template, List includes) { - var includeLookups = getParsedIncludeLookupItems(includes); - var defaultImportParseExpression = includeLookups.stream() - .filter(IncludeLookupItem::isDefaultInclude) - .map(i -> String.format("#parse(%s)", i.getName())) - .collect(Collectors.joining(System.getProperty("line.separator"))); - var templateWithDefaultImports = defaultImportParseExpression + System.getProperty("line.separator") + template; - return replaceParseExpressions(templateWithDefaultImports, includeLookups); - } - - @NotNull - private static List getParsedIncludeLookupItems(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 List includeLookupItems) { - template = template.lines()// - .map(line -> replaceParseExpression(line, includeLookupItems))// - .collect(Collectors.joining(System.getProperty("line.separator"))); - return template; - } - - private static String replaceParseExpression(String line, List includeLookupItems) { - if (line.trim().startsWith("#parse")) { - var includeName = line.trim().replace("#parse(", "") - .replace(")", "") - .replaceAll("\"", ""); - 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(Project project, 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(PsiClass clazz) { - return Arrays.stream(clazz.getFields()) - .map(f -> EntryFactory.of(f, false)) - .collect(Collectors.toList()); - } - - static List getAllFields(PsiClass clazz) { - return Arrays.stream(clazz.getAllFields()) - .map(f -> EntryFactory.of(f, false)) - .collect(Collectors.toList()); - } - - static List getMethods(PsiClass clazz) { - return Arrays.stream(clazz.getMethods()) - .map(EntryFactory::of) - .collect(Collectors.toList()); - } - - static List getAllMethods(PsiClass clazz) { - return Arrays.stream(clazz.getAllMethods()) - .map(EntryFactory::of) - .collect(Collectors.toList()); - } - - static List getImportList(PsiJavaFile javaFile) { - var importList = javaFile.getImportList(); - if (importList == null) { - return new ArrayList<>(); - } - return Arrays.stream(importList.getImportStatements()) - .map(PsiImportStatement::getQualifiedName) - .collect(Collectors.toList()); - } - - static List getClassTypeParameters(PsiClass psiClass) { - return Arrays.stream(psiClass.getTypeParameters()).map(PsiNamedElement::getName).collect(Collectors.toList()); - } - - static final class IncludeLookupItem { - @NotNull - private final String name; - @NotNull - private final String content; - private boolean defaultInclude; - - IncludeLookupItem(@NotNull String name, @NotNull String content, 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/me/lotabout/codegenerator/util/MemberEntry.java b/src/me/lotabout/codegenerator/util/MemberEntry.java deleted file mode 100644 index 6e6cca4..0000000 --- a/src/me/lotabout/codegenerator/util/MemberEntry.java +++ /dev/null @@ -1,8 +0,0 @@ -package me.lotabout.codegenerator.util; - -import com.intellij.psi.PsiMember; -import org.jetbrains.java.generate.element.Element; - -public interface MemberEntry extends Element { - T getRaw(); -} diff --git a/src/me/lotabout/codegenerator/util/MethodEntry.java b/src/me/lotabout/codegenerator/util/MethodEntry.java deleted file mode 100644 index 79f2f73..0000000 --- a/src/me/lotabout/codegenerator/util/MethodEntry.java +++ /dev/null @@ -1,295 +0,0 @@ -package me.lotabout.codegenerator.util; - -import com.intellij.psi.PsiMethod; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.java.generate.element.MethodElement; - -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - -/** - * Wrapper around MethodElement - */ -public class MethodEntry implements MemberEntry { - private final PsiMethod raw; - private final MethodElement element; - - public MethodEntry(PsiMethod field, MethodElement element) { - this.raw = field; - this.element = element; - } - - public MethodElement getElement() { - return element; - } - - public PsiMethod getRaw() { - return raw; - } - - public String getMethodName() { - return element.getMethodName(); - } - - public void setMethodName(String s) { - element.setMethodName(s); - } - - public String getFieldName() { - return element.getFieldName(); - } - - public void setFieldName(String s) { - element.setFieldName(s); - } - - public String getAccessor() { - return element.getAccessor(); - } - - public boolean isModifierAbstract() { - return element.isModifierAbstract(); - } - - public void setModifierAbstract(boolean b) { - element.setModifierAbstract(b); - } - - public boolean isModifierSynchronzied() { - return element.isModifierSynchronzied(); - } - - public boolean isModifierSynchronized() { - return element.isModifierSynchronized(); - } - - public void setModifierSynchronized(boolean b) { - element.setModifierSynchronized(b); - } - - public boolean isReturnTypeVoid() { - return element.isReturnTypeVoid(); - } - - public void setReturnTypeVoid(boolean b) { - element.setReturnTypeVoid(b); - } - - public boolean isGetter() { - return element.isGetter(); - } - - public void setGetter(boolean b) { - element.setGetter(b); - } - - public boolean isDeprecated() { - return element.isDeprecated(); - } - - public void setDeprecated(boolean b) { - element.setDeprecated(b); - } - - public boolean matchName(String s) throws IllegalArgumentException { - return element.matchName(s); - } - - public String getName() { - return element.getName(); - } - - public boolean isArray() { - return element.isArray(); - } - - public boolean isNestedArray() { - return element.isNestedArray(); - } - - public void setNestedArray(boolean b) { - element.setNestedArray(b); - } - - public boolean isCollection() { - return element.isCollection(); - } - - public boolean isMap() { - return element.isMap(); - } - - public boolean isPrimitive() { - return element.isPrimitive(); - } - - public boolean isString() { - return element.isString(); - } - - public boolean isPrimitiveArray() { - return element.isPrimitiveArray(); - } - - public boolean isObjectArray() { - return element.isObjectArray(); - } - - public boolean isNumeric() { - return element.isNumeric(); - } - - public boolean isObject() { - return element.isObject(); - } - - public boolean isDate() { - return element.isDate(); - } - - public boolean isSet() { - return element.isSet(); - } - - public boolean isList() { - return element.isList(); - } - - public boolean isStringArray() { - return element.isStringArray(); - } - - public boolean isCalendar() { - return element.isCalendar(); - } - - public String getTypeName() { - return element.getTypeName(); - } - - public String getTypeQualifiedName() { - return element.getTypeQualifiedName(); - } - - public boolean isAnnotatedWith(@NotNull String qualifiedName) { - return AnnotationUtil.isAnnotatedWith(raw, qualifiedName); - } - - public String getType() { - return element.getType(); - } - - public void setType(String s) { - element.setType(s); - } - - public boolean isBoolean() { - return element.isBoolean(); - } - - public boolean isLong() { - return element.isLong(); - } - - public void setLong(boolean b) { - element.setLong(b); - } - - public boolean isFloat() { - return element.isFloat(); - } - - public void setFloat(boolean b) { - element.setFloat(b); - } - - public boolean isDouble() { - return element.isDouble(); - } - - public void setDouble(boolean b) { - element.setDouble(b); - } - - public boolean isVoid() { - return element.isVoid(); - } - - public boolean isNotNull() { - return element.isNotNull(); - } - - public void setNotNull(boolean b) { - element.setNotNull(b); - } - - public void setVoid(boolean b) { - element.setVoid(b); - } - - public boolean isChar() { - return element.isChar(); - } - - public void setChar(boolean b) { - element.setChar(b); - } - - public boolean isByte() { - return element.isByte(); - } - - public void setByte(boolean b) { - element.setByte(b); - } - - public boolean isShort() { - return element.isShort(); - } - - public void setShort(boolean b) { - element.setShort(b); - } - - public void setBoolean(boolean b) { - element.setBoolean(b); - } - - public void setName(String s) { - element.setName(s); - } - - public boolean isModifierStatic() { - return element.isModifierStatic(); - } - - public boolean isModifierPublic() { - return element.isModifierPublic(); - } - - public boolean isModifierProtected() { - return element.isModifierProtected(); - } - - public boolean isModifierPackageLocal() { - return element.isModifierPackageLocal(); - } - - public boolean isModifierPrivate() { - return element.isModifierPrivate(); - } - - public boolean isModifierFinal() { - return element.isModifierFinal(); - } - - @Override - public String toString() { - return "MethodEntry{" + - "raw=" + raw + - ", element=" + element + - '}'; - } -} diff --git a/src/test/java/me/lotabout/codegenerator/util/ClassEntryTest.java b/src/test/java/me/lotabout/codegenerator/util/ClassEntryTest.java new file mode 100644 index 0000000..2f07fbc --- /dev/null +++ b/src/test/java/me/lotabout/codegenerator/util/ClassEntryTest.java @@ -0,0 +1,138 @@ +package me.lotabout.codegenerator.util; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiJavaFile; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@Disabled +public class ClassEntryTest { + + @Mock + private PsiClass mockClass; + @Mock + private PsiClass mockInterface1; + @Mock + private PsiClass mockInterface2; + @Mock + private PsiClass mockSuperClass; + @Mock + private PsiClass mockSuperInterface; + + private TypeEntry classEntry; + + @BeforeEach + void setUp() { + when(mockClass.getContainingFile()).thenReturn(mock(PsiJavaFile.class)); + classEntry = TypeEntry.of(mockClass); + } + + @Test + void isImplements_DirectInterfaceMatch_ReturnsTrue() { + // Given + when(mockClass.getInterfaces()).thenReturn(new PsiClass[]{mockInterface1}); + when(mockInterface1.getQualifiedName()).thenReturn("com.example.TestInterface"); + when(mockInterface1.getName()).thenReturn("TestInterface"); + + // When + final boolean result = classEntry.isImplements("TestInterface"); + + // Then + assertTrue(result); + verify(mockClass).getInterfaces(); + verify(mockInterface1).getQualifiedName(); + verify(mockInterface1).getName(); + } + + @Test + void isImplements_IndirectInterfaceImplementation_ReturnsTrue() { + // Given + when(mockClass.getInterfaces()).thenReturn(new PsiClass[]{mockInterface1}); + when(mockInterface1.getQualifiedName()).thenReturn("com.example.Interface1"); + when(mockInterface1.getName()).thenReturn("Interface1"); + when(mockInterface1.getInterfaces()).thenReturn(new PsiClass[]{mockInterface2}); + when(mockInterface2.getQualifiedName()).thenReturn("com.example.TestInterface"); + when(mockInterface2.getName()).thenReturn("TestInterface"); + + // When + final boolean result = classEntry.isImplements("TestInterface"); + + // Then + assertTrue(result); + verify(mockClass).getInterfaces(); + verify(mockInterface1).getInterfaces(); + verify(mockInterface2).getQualifiedName(); + verify(mockInterface2).getName(); + } + + @Test + void isImplements_SuperClassImplementation_ReturnsTrue() { + // Given + when(mockClass.getInterfaces()).thenReturn(new PsiClass[0]); + when(mockClass.getSuperClass()).thenReturn(mockSuperClass); + when(mockSuperClass.getInterfaces()).thenReturn(new PsiClass[]{mockInterface1}); + when(mockInterface1.getQualifiedName()).thenReturn("com.example.TestInterface"); + when(mockInterface1.getName()).thenReturn("TestInterface"); + + // When + final boolean result = classEntry.isImplements("TestInterface"); + + // Then + assertTrue(result); + verify(mockClass).getInterfaces(); + verify(mockClass).getSuperClass(); + verify(mockSuperClass).getInterfaces(); + verify(mockInterface1).getQualifiedName(); + verify(mockInterface1).getName(); + } + + @Test + void isImplements_SuperClassIndirectImplementation_ReturnsTrue() { + // Given + when(mockClass.getInterfaces()).thenReturn(new PsiClass[0]); + when(mockClass.getSuperClass()).thenReturn(mockSuperClass); + when(mockSuperClass.getInterfaces()).thenReturn(new PsiClass[]{mockInterface1}); + when(mockInterface1.getInterfaces()).thenReturn(new PsiClass[]{mockSuperInterface}); + when(mockSuperInterface.getQualifiedName()).thenReturn("com.example.TestInterface"); + when(mockSuperInterface.getName()).thenReturn("TestInterface"); + + // When + final boolean result = classEntry.isImplements("TestInterface"); + + // Then + assertTrue(result); + verify(mockClass).getInterfaces(); + verify(mockClass).getSuperClass(); + verify(mockSuperClass).getInterfaces(); + verify(mockInterface1).getInterfaces(); + verify(mockSuperInterface).getQualifiedName(); + verify(mockSuperInterface).getName(); + } + + @Test + void isImplements_NoImplementation_ReturnsFalse() { + // Given + when(mockClass.getInterfaces()).thenReturn(new PsiClass[0]); + when(mockClass.getSuperClass()).thenReturn(null); + + // When + final boolean result = classEntry.isImplements("TestInterface"); + + // Then + assertFalse(result); + verify(mockClass).getInterfaces(); + verify(mockClass).getSuperClass(); + } +} diff --git a/src/test/java/me/lotabout/codegenerator/util/FieldEntryTest.java b/src/test/java/me/lotabout/codegenerator/util/FieldEntryTest.java new file mode 100644 index 0000000..445df4a --- /dev/null +++ b/src/test/java/me/lotabout/codegenerator/util/FieldEntryTest.java @@ -0,0 +1,220 @@ +package me.lotabout.codegenerator.util; + +import org.jetbrains.java.generate.element.FieldElement; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.intellij.psi.PsiArrayType; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiClassType; +import com.intellij.psi.PsiField; +import com.intellij.psi.PsiType; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@Disabled +public class FieldEntryTest { + + @Mock + private PsiField mockField; + @Mock + private FieldElement mockElement; + @Mock + private PsiArrayType mockArrayType; + @Mock + private PsiClassType mockClassType; + @Mock + private PsiClass mockClass; + @Mock + private PsiClassType mockComponentType; + @Mock + private PsiClass mockComponentClass; + + private FieldEntry fieldEntry; + + @BeforeEach + void setUp() { + fieldEntry = FieldEntry.of(mockField, false); + } + + @Test + void getElementType_NonArrayNonCollection_ReturnsNull() { + // Given + when(mockElement.isArray()).thenReturn(false); + when(mockElement.isCollection()).thenReturn(false); + + // When + final TypeEntry result = fieldEntry.getElementType(); + + // Then + assertNull(result); + } + + @Test + void getElementType_Array_ReturnsComponentType() { + // Given + when(mockElement.isArray()).thenReturn(true); + when(mockElement.isCollection()).thenReturn(false); + when(mockField.getType()).thenReturn(mockArrayType); + when(mockArrayType.getComponentType()).thenReturn(mockComponentType); + when(mockComponentType.resolve()).thenReturn(mockComponentClass); + + // When + final TypeEntry result = fieldEntry.getElementType(); + + // Then + assertNotNull(result); + verify(mockField).getType(); + verify(mockArrayType).getComponentType(); + verify(mockComponentType).resolve(); + } + + @Test + void getElementType_Collection_ReturnsGenericType() { + // Given + when(mockElement.isArray()).thenReturn(false); + when(mockElement.isCollection()).thenReturn(true); + when(mockField.getType()).thenReturn(mockClassType); + when(mockClassType.resolveGenerics()).thenReturn(mock(PsiClassType.ClassResolveResult.class)); + when(mockClassType.getParameters()).thenReturn(new PsiType[]{mockComponentType}); + when(mockComponentType.resolve()).thenReturn(mockComponentClass); + + // When + final TypeEntry result = fieldEntry.getElementType(); + + // Then + assertNotNull(result); + verify(mockField).getType(); + verify(mockClassType).resolveGenerics(); + verify(mockClassType).getParameters(); + verify(mockComponentType).resolve(); + } + + @Test + void getElementType_CollectionWithoutGenericType_ReturnsNull() { + // Given + when(mockElement.isArray()).thenReturn(false); + when(mockElement.isCollection()).thenReturn(true); + when(mockField.getType()).thenReturn(mockClassType); + when(mockClassType.resolveGenerics()).thenReturn(mock(PsiClassType.ClassResolveResult.class)); + when(mockClassType.getParameters()).thenReturn(new PsiType[0]); + + // When + final TypeEntry result = fieldEntry.getElementType(); + + // Then + assertNull(result); + verify(mockField).getType(); + verify(mockClassType).resolveGenerics(); + verify(mockClassType).getParameters(); + } + + @Test + void getKeyType_NonMap_ReturnsNull() { + // Given + when(mockElement.isMap()).thenReturn(false); + + // When + final TypeEntry result = fieldEntry.getKeyType(); + + // Then + assertNull(result); + } + + @Test + void getKeyType_Map_ReturnsKeyType() { + // Given + when(mockElement.isMap()).thenReturn(true); + when(mockField.getType()).thenReturn(mockClassType); + when(mockClassType.resolveGenerics()).thenReturn(mock(PsiClassType.ClassResolveResult.class)); + when(mockClassType.getParameters()).thenReturn(new PsiType[]{mockComponentType}); + when(mockComponentType.resolve()).thenReturn(mockComponentClass); + + // When + final TypeEntry result = fieldEntry.getKeyType(); + + // Then + assertNotNull(result); + verify(mockField).getType(); + verify(mockClassType).resolveGenerics(); + verify(mockClassType).getParameters(); + verify(mockComponentType).resolve(); + } + + @Test + void getKeyType_MapWithoutGenericType_ReturnsNull() { + // Given + when(mockElement.isMap()).thenReturn(true); + when(mockField.getType()).thenReturn(mockClassType); + when(mockClassType.resolveGenerics()).thenReturn(mock(PsiClassType.ClassResolveResult.class)); + when(mockClassType.getParameters()).thenReturn(new PsiType[0]); + + // When + final TypeEntry result = fieldEntry.getKeyType(); + + // Then + assertNull(result); + verify(mockField).getType(); + verify(mockClassType).resolveGenerics(); + verify(mockClassType).getParameters(); + } + + @Test + void getValueType_NonMap_ReturnsNull() { + // Given + when(mockElement.isMap()).thenReturn(false); + + // When + final TypeEntry result = fieldEntry.getValueType(); + + // Then + assertNull(result); + } + + @Test + void getValueType_Map_ReturnsValueType() { + // Given + when(mockElement.isMap()).thenReturn(true); + when(mockField.getType()).thenReturn(mockClassType); + when(mockClassType.resolveGenerics()).thenReturn(mock(PsiClassType.ClassResolveResult.class)); + when(mockClassType.getParameters()).thenReturn(new PsiType[]{mock(PsiType.class), mockComponentType}); + when(mockComponentType.resolve()).thenReturn(mockComponentClass); + + // When + final TypeEntry result = fieldEntry.getValueType(); + + // Then + assertNotNull(result); + verify(mockField).getType(); + verify(mockClassType).resolveGenerics(); + verify(mockClassType).getParameters(); + verify(mockComponentType).resolve(); + } + + @Test + void getValueType_MapWithoutGenericType_ReturnsNull() { + // Given + when(mockElement.isMap()).thenReturn(true); + when(mockField.getType()).thenReturn(mockClassType); + when(mockClassType.resolveGenerics()).thenReturn(mock(PsiClassType.ClassResolveResult.class)); + when(mockClassType.getParameters()).thenReturn(new PsiType[]{mock(PsiType.class)}); + + // When + final TypeEntry result = fieldEntry.getValueType(); + + // Then + assertNull(result); + verify(mockField).getType(); + verify(mockClassType).resolveGenerics(); + verify(mockClassType).getParameters(); + } +} diff --git a/src/test/java/me/lotabout/codegenerator/util/GenerationUtilTest.java b/src/test/java/me/lotabout/codegenerator/util/GenerationUtilTest.java new file mode 100644 index 0000000..807496d --- /dev/null +++ b/src/test/java/me/lotabout/codegenerator/util/GenerationUtilTest.java @@ -0,0 +1,47 @@ +package me.lotabout.codegenerator.util; + +import java.io.StringWriter; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.velocity.app.VelocityEngine; +import org.apache.velocity.context.Context; +import org.apache.velocity.tools.ToolManager; +import org.jetbrains.java.generate.velocity.VelocityFactory; +import org.junit.jupiter.api.Test; + +import static java.util.Collections.emptyList; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class GenerationUtilTest { + + @Test + public void testVelocityTools() { + final ToolManager toolManager = new ToolManager(false, true); + toolManager.configure(GenerationUtil.VELOCITY_TOOLS_CONFIG); + final Context context = toolManager.createContext(); + final List names = Arrays.asList("John", "Sarah", "Mark", "Bill"); + context.put("names", names); + final String template = "#foreach($name in $sorter.sort($names))$name #end"; + final VelocityEngine engine = VelocityFactory.getVelocityEngine(); + final StringWriter sw = new StringWriter(); + engine.evaluate(context, sw, this.getClass().getName(), template); + System.out.println(sw); + assertEquals("Bill John Mark Sarah ", sw.toString()); + } + + @Test + public void testVelocityEvaluate() { + final Map contextMap = new HashMap<>(); + final String template = """ + #set($names = ["John", "Sarah", "Mark", "Bill"]) + #foreach($name in $sorter.sort($names))$name #end + """; + final String result = GenerationUtil.velocityEvaluate(contextMap, null, template, emptyList()); + System.out.println(result); + assertEquals("\nBill John Mark Sarah ", result); + } +} diff --git a/src/test/java/me/lotabout/codegenerator/util/NameUtilExTest.java b/src/test/java/me/lotabout/codegenerator/util/NameUtilExTest.java new file mode 100644 index 0000000..71241e8 --- /dev/null +++ b/src/test/java/me/lotabout/codegenerator/util/NameUtilExTest.java @@ -0,0 +1,18 @@ +package me.lotabout.codegenerator.util; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class NameUtilExTest { + + @Test + public void testCapitalizeAndUnderscore() { + assertEquals("HELLO_WORLD", NameUtilEx.capitalizeAndUnderscore("helloWorld")); + } + + @Test + public void testLowercaseAndUnderscore() { + assertEquals("hello_world", NameUtilEx.lowercaseAndUnderscore("helloWorld")); + } +} diff --git a/src/test/java/me/lotabout/codegenerator/util/StringUtilExTest.java b/src/test/java/me/lotabout/codegenerator/util/StringUtilExTest.java new file mode 100644 index 0000000..68ca559 --- /dev/null +++ b/src/test/java/me/lotabout/codegenerator/util/StringUtilExTest.java @@ -0,0 +1,48 @@ +package me.lotabout.codegenerator.util; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import static me.lotabout.codegenerator.util.StringUtilEx.parseArrayInitializerText; + +public class StringUtilExTest { + + @Test + void parseArrayInitializerText_NullInput_ReturnsEmptyArray() { + assertArrayEquals(new String[0], parseArrayInitializerText(null)); + } + + @Test + void parseArrayInitializerText_EmptyBraces_ReturnsEmptyArray() { + assertArrayEquals(new String[0], parseArrayInitializerText("{}")); + } + + @Test + void parseArrayInitializerText_SingleItem_ReturnsArrayWithOneItem() { + assertArrayEquals(new String[]{"item1"}, parseArrayInitializerText("{item1}")); + } + + @Test + void parseArrayInitializerText_MultipleItems_ReturnsArrayWithMultipleItems() { + assertArrayEquals(new String[]{"item1", "item2", "item3"}, + parseArrayInitializerText("{item1, item2, item3}")); + } + + @Test + void parseArrayInitializerText_ItemsWithSpaces_ReturnsTrimmedItems() { + assertArrayEquals(new String[]{"item1", "item2", "item3"}, + parseArrayInitializerText("{ item1 , item2 , item3 }")); + } + + @Test + void parseArrayInitializerText_InvalidFormat_ReturnsEmptyArray() { + assertThrows(AssertionError.class, () -> parseArrayInitializerText("{item1, item2, item3")); + } + + @Test + void parseArrayInitializerText_EmptyString_ReturnsEmptyArray() { + assertThrows(AssertionError.class, () -> parseArrayInitializerText("")); + } +}