From 6e89e5b3fca57e2ffac6bfb8f20da5fbfe084a87 Mon Sep 17 00:00:00 2001 From: tangcent Date: Wed, 3 Apr 2024 08:11:57 +0800 Subject: [PATCH] feat: Replace gson and jsoup with IntelliJ code style for JSON/XML/HTML formatting (#1119) --- .../com/itangcent/http/ApacheHttpClient.kt | 8 +- idea-plugin/build.gradle.kts | 3 - .../export/postman/DefaultPostmanApiHelper.kt | 4 +- .../plugin/api/export/rule/RequestRuleWrap.kt | 16 +-- .../plugin/api/export/suv/SuvApiExporter.kt | 5 +- .../plugin/api/export/yapi/YapiApiHelper.kt | 4 +- .../idea/plugin/dialog/ApiCallDialog.kt | 28 +++-- .../plugin/dialog/ScriptExecutorDialog.kt | 3 +- .../plugin/settings/EnumProcessInitializer.kt | 3 +- .../idea/utils/DefaultFileSaveHelper.kt | 35 +++--- .../itangcent/idea/utils/FormatterHelper.kt | 80 ++++++++++++++ .../com/itangcent/idea/utils/GsonExUtils.kt | 22 ++-- .../com/itangcent/idea/utils/JacksonUtils.kt | 2 +- .../intellij/extend/ActionContextKit.kt | 6 +- .../idea/utils/FormatterHelperTest.kt | 102 ++++++++++++++++++ .../itangcent/idea/utils/GsonExUtilsTest.kt | 55 ++++------ 16 files changed, 276 insertions(+), 100 deletions(-) create mode 100644 idea-plugin/src/main/kotlin/com/itangcent/idea/utils/FormatterHelper.kt create mode 100644 idea-plugin/src/test/kotlin/com/itangcent/idea/utils/FormatterHelperTest.kt diff --git a/common-api/src/main/kotlin/com/itangcent/http/ApacheHttpClient.kt b/common-api/src/main/kotlin/com/itangcent/http/ApacheHttpClient.kt index f425af16e..0788f7708 100644 --- a/common-api/src/main/kotlin/com/itangcent/http/ApacheHttpClient.kt +++ b/common-api/src/main/kotlin/com/itangcent/http/ApacheHttpClient.kt @@ -3,8 +3,7 @@ package com.itangcent.http import com.itangcent.annotation.script.ScriptIgnore import com.itangcent.annotation.script.ScriptTypeName import com.itangcent.common.kit.toJson -import com.itangcent.common.logger.ILogger -import com.itangcent.common.spi.SpiUtils +import com.itangcent.common.logger.Log import com.itangcent.common.utils.notNullOrEmpty import org.apache.http.HttpEntity import org.apache.http.NameValuePair @@ -36,7 +35,7 @@ import javax.net.ssl.SSLContext @ScriptTypeName("httpClient") open class ApacheHttpClient : HttpClient { - companion object { + companion object : Log() { private fun String.createContentType(charset: String = "UTF-8"): ContentType { val contentType = ContentType.parse(this) return if (contentType.charset == null) { @@ -155,8 +154,7 @@ open class ApacheHttpClient : HttpClient { val body = request.body() if (body != null) { if (requestEntity != null) { - SpiUtils.loadService(ILogger::class) - ?.warn("The request with a body should not set content-type:${request.contentType()}") + LOG.warn("The request with a body should not set content-type:${request.contentType()}") } requestEntity = when (body) { is HttpEntity -> { diff --git a/idea-plugin/build.gradle.kts b/idea-plugin/build.gradle.kts index 608bedec5..b99dfe1f9 100644 --- a/idea-plugin/build.gradle.kts +++ b/idea-plugin/build.gradle.kts @@ -72,9 +72,6 @@ dependencies { implementation("org.jetbrains.kotlin:kotlin-reflect:1.8.0") - // https://mvnrepository.com/artifact/org.jsoup/jsoup - implementation("org.jsoup:jsoup:1.12.1") - // https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind implementation("com.fasterxml.jackson.core:jackson-databind:2.12.2") diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/postman/DefaultPostmanApiHelper.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/postman/DefaultPostmanApiHelper.kt index d943521be..205bc1011 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/postman/DefaultPostmanApiHelper.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/postman/DefaultPostmanApiHelper.kt @@ -11,7 +11,7 @@ import com.itangcent.http.HttpRequest import com.itangcent.http.HttpResponse import com.itangcent.http.contentType import com.itangcent.idea.plugin.settings.helper.PostmanSettingsHelper -import com.itangcent.idea.utils.resolveGsonLazily +import com.itangcent.idea.utils.GsonExUtils import com.itangcent.intellij.context.ActionContext import com.itangcent.intellij.extend.acquireGreedy import com.itangcent.intellij.extend.asJsonElement @@ -208,7 +208,7 @@ open class DefaultPostmanApiHelper : PostmanApiHelper { val request = getHttpClient().put("$COLLECTION/$collectionId") .contentType(ContentType.APPLICATION_JSON) .header("x-api-key", postmanSettingsHelper.getPrivateToken()) - .body(GsonUtils.toJson(linkedMapOf("collection" to apiInfo)).resolveGsonLazily()) + .body(GsonUtils.toJson(linkedMapOf("collection" to apiInfo)).apply { GsonExUtils.resolveGsonLazily(this) }) try { beforeRequest(request) diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/rule/RequestRuleWrap.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/rule/RequestRuleWrap.kt index 1aa268b3d..e5ccc71ed 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/rule/RequestRuleWrap.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/rule/RequestRuleWrap.kt @@ -8,12 +8,12 @@ import com.itangcent.common.utils.* import com.itangcent.idea.plugin.api.export.* import com.itangcent.idea.plugin.api.export.core.* import com.itangcent.idea.psi.resource -import com.itangcent.utils.setExts import com.itangcent.intellij.context.ActionContext +import com.itangcent.intellij.extend.logger import com.itangcent.intellij.jvm.DuckTypeHelper import com.itangcent.intellij.jvm.JsonOption import com.itangcent.intellij.jvm.PsiClassHelper -import com.itangcent.intellij.logger.Logger +import com.itangcent.utils.setExts import java.util.* class RequestRuleWrap(private val methodExportContext: MethodExportContext?, private val request: Request) { @@ -102,7 +102,7 @@ class RequestRuleWrap(private val methodExportContext: MethodExportContext?, pri val context = ActionContext.getContext()!! val resource = request.resource() if (resource == null) { - context.instance(Logger::class).warn("no resource be related with:${request}") + context.logger().warn("no resource be related with:${request}") return } val responseType = context.instance(DuckTypeHelper::class).findType(bodyClass, resource) ?: return @@ -126,7 +126,7 @@ class RequestRuleWrap(private val methodExportContext: MethodExportContext?, pri val context = ActionContext.getContext()!! val resource = request.resource() if (resource == null) { - context.instance(Logger::class).warn("no resource be related with:${request}") + context.logger().warn("no resource be related with:${request}") return } val responseType = context.instance(DuckTypeHelper::class).findType(modelClass, resource) @@ -141,7 +141,7 @@ class RequestRuleWrap(private val methodExportContext: MethodExportContext?, pri val context = ActionContext.getContext()!! val resource = request.resource() if (resource == null) { - context.instance(Logger::class).warn("no resource be related with:${request}") + context.logger().warn("no resource be related with:${request}") return } val responseType = context.instance(DuckTypeHelper::class).findType(modelClass, resource) @@ -155,7 +155,7 @@ class RequestRuleWrap(private val methodExportContext: MethodExportContext?, pri ) fun addModelClass(modelClass: String?) { ActionContext.getContext() - ?.instance(Logger::class) + ?.logger() ?.warn("addModelClass is deprecated, please use addModelClassAsFormParam instead of addModelClass") addModelClassAsFormParam(modelClass) } @@ -348,7 +348,7 @@ class RequestRuleWrap(private val methodExportContext: MethodExportContext?, pri val context = ActionContext.getContext()!! val resource = request.resource() if (resource == null) { - context.instance(Logger::class).warn("no resource be related with:${request}") + context.logger().warn("no resource be related with:${request}") return } val responseType = context.instance(DuckTypeHelper::class).findType(bodyClass, resource) @@ -363,7 +363,7 @@ class RequestRuleWrap(private val methodExportContext: MethodExportContext?, pri val context = ActionContext.getContext()!! val resource = request.resource() if (resource == null) { - context.instance(Logger::class).warn("no resource be related with:${request}") + context.logger().warn("no resource be related with:${request}") return } val responseType = context.instance(DuckTypeHelper::class).findType(bodyClass, resource) diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/suv/SuvApiExporter.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/suv/SuvApiExporter.kt index 32488ef80..3058f8891 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/suv/SuvApiExporter.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/suv/SuvApiExporter.kt @@ -43,6 +43,7 @@ import com.itangcent.intellij.context.ActionContext import com.itangcent.intellij.extend.findCurrentMethod import com.itangcent.intellij.extend.guice.singleton import com.itangcent.intellij.extend.guice.with +import com.itangcent.intellij.extend.logger import com.itangcent.intellij.extend.withBoundary import com.itangcent.intellij.file.DefaultLocalFileRepository import com.itangcent.intellij.file.LocalFileRepository @@ -171,7 +172,7 @@ open class SuvApiExporter { fun exportApisFromMethod(actionContext: ActionContext, requests: List) { - this.logger = actionContext.instance(Logger::class) + this.logger = actionContext.logger() val actionContextBuilder = ActionContext.builder() actionContextBuilder.setParentContext(actionContext) @@ -240,7 +241,7 @@ open class SuvApiExporter { builder.inheritFrom(actionContext, TipsHelper::class) -// builder.bindInstance(Logger::class, BeanWrapperProxies.wrap(Logger::class, actionContext.instance(Logger::class))) +// builder.bindInstance(Logger::class, BeanWrapperProxies.wrap(Logger::class, actionContext.logger())) // builder.bind(Logger::class) { it.with(ConfigurableLogger::class).singleton() } // builder.bind(Logger::class, "delegate.logger") { it.with(ConsoleRunnerLogger::class).singleton() } diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/yapi/YapiApiHelper.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/yapi/YapiApiHelper.kt index 8b19ad1f9..696a87314 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/yapi/YapiApiHelper.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/yapi/YapiApiHelper.kt @@ -6,8 +6,8 @@ import com.google.inject.ImplementedBy import com.itangcent.common.logger.traceError import com.itangcent.idea.plugin.api.export.core.Folder import com.itangcent.intellij.context.ActionContext +import com.itangcent.intellij.extend.logger import com.itangcent.intellij.extend.sub -import com.itangcent.intellij.logger.Logger @ImplementedBy(DefaultYapiApiHelper::class) @@ -84,7 +84,7 @@ fun YapiApiHelper.getCartForFolder(folder: Folder, privateToken: String): CartIn try { cartId = findCart(privateToken, name) } catch (e: Exception) { - ActionContext.getContext()?.instance(Logger::class) + ActionContext.getContext()?.logger() ?.traceError("error to find cart [$name]", e) return null } diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/ApiCallDialog.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/ApiCallDialog.kt index eb4225172..77ecd21c3 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/ApiCallDialog.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/ApiCallDialog.kt @@ -21,7 +21,10 @@ import com.itangcent.common.logger.traceError import com.itangcent.common.model.FormParam import com.itangcent.common.model.Request import com.itangcent.common.model.hasBodyOrForm -import com.itangcent.common.utils.* +import com.itangcent.common.utils.appendlnIfNotEmpty +import com.itangcent.common.utils.notNullOrBlank +import com.itangcent.common.utils.notNullOrEmpty +import com.itangcent.common.utils.safe import com.itangcent.http.* import com.itangcent.idea.binder.DbBeanBinderFactory import com.itangcent.idea.icons.EasyIcons @@ -32,6 +35,7 @@ import com.itangcent.idea.psi.resourceMethod import com.itangcent.idea.swing.onSelect import com.itangcent.idea.swing.onTextChange import com.itangcent.idea.utils.* +import com.itangcent.intellij.context.ActionContext import com.itangcent.intellij.extend.rx.ThrottleHelper import com.itangcent.intellij.extend.withBoundary import com.itangcent.intellij.file.LocalFileRepository @@ -40,8 +44,6 @@ import com.itangcent.suv.http.HttpClientProvider import org.apache.commons.lang3.RandomUtils import org.apache.commons.lang3.exception.ExceptionUtils import org.apache.http.entity.ContentType -import org.jsoup.Jsoup -import org.jsoup.nodes.Document import java.awt.BorderLayout import java.awt.Dimension import java.awt.GridBagConstraints @@ -1320,7 +1322,10 @@ class ApiCallDialog : ContextDialog(), ApiCallUI { super.onDispose() } - class ResponseStatus(var response: HttpResponse) { + class ResponseStatus( + var response: HttpResponse, + val actionContext: ActionContext = ActionContext.getContext()!! + ) { //auto format var isFormat: Boolean = true @@ -1344,14 +1349,17 @@ class ApiCallDialog : ContextDialog(), ApiCallUI { return try { val contentType = safe { response.contentType()?.let { ContentType.parse(it) } } if (contentType != null) { - if (contentType.mimeType.startsWith("text/html") || contentType.mimeType.startsWith("text/xml")) { - val doc: Document = Jsoup.parse(getRawResult()) - doc.outputSettings().prettyPrint(true) - return doc.outerHtml() + if (contentType.mimeType.startsWith("text/html")) { + return getRawResult()?.let { actionContext.instance(FormatterHelper::class).formatHtml(it) } + } + if (contentType.mimeType.startsWith("text/xml")) { + return getRawResult()?.let { actionContext.instance(FormatterHelper::class).formatXml(it) } + } + if (contentType.mimeType.startsWith("application/json")) { + return getRawResult()?.let { actionContext.instance(FormatterHelper::class).formatJson(it) } } } - - getRawResult()?.let { GsonUtils.prettyJsonStr(it) } + getRawResult() } catch (e: Exception) { getRawResult() } diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/ScriptExecutorDialog.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/ScriptExecutorDialog.kt index 3504d0833..249c473aa 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/ScriptExecutorDialog.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/ScriptExecutorDialog.kt @@ -25,6 +25,7 @@ import com.itangcent.intellij.config.rule.RuleParser import com.itangcent.intellij.config.rule.StringRule import com.itangcent.intellij.config.rule.parseStringRule import com.itangcent.intellij.context.ActionContext +import com.itangcent.intellij.extend.logger import com.itangcent.intellij.extend.rx.AutoComputer import com.itangcent.intellij.extend.rx.mutual import com.itangcent.intellij.jvm.DuckTypeHelper @@ -618,7 +619,7 @@ class ScriptExecutorDialog : ContextDialog() { try { delay = task() } catch (e: Throwable) { - actionContext.instance(Logger::class) + actionContext.logger() .traceError("error to eval script", e) } finally { schedule(delay ?: 3000) diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/settings/EnumProcessInitializer.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/settings/EnumProcessInitializer.kt index 7202e24d5..9ef62222b 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/settings/EnumProcessInitializer.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/settings/EnumProcessInitializer.kt @@ -8,6 +8,7 @@ import com.itangcent.idea.plugin.settings.helper.RecommendConfigSettingsHelper import com.itangcent.idea.swing.MessagesHelper import com.itangcent.idea.swing.showChooseWithTipDialog import com.itangcent.intellij.context.ActionContext +import com.itangcent.intellij.extend.logger import com.itangcent.intellij.logger.Logger class EnumProcessInitializer : Initializer { @@ -34,7 +35,7 @@ class EnumProcessInitializer : Initializer { propertiesComponent.setValue(ENUM_RECOMMEND_ITEMS_CONFIRMED_KEY, true) } } catch (e: Exception) { - actionContext.instance(Logger::class).traceError("error in enumRecommendItemsConfirmed.", e) + actionContext.logger().traceError("error in enumRecommendItemsConfirmed.", e) } } } diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/utils/DefaultFileSaveHelper.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/utils/DefaultFileSaveHelper.kt index e4c9b2e87..776460126 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/utils/DefaultFileSaveHelper.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/utils/DefaultFileSaveHelper.kt @@ -6,7 +6,7 @@ import com.google.inject.name.Named import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory import com.intellij.openapi.util.io.FileUtil import com.itangcent.intellij.context.ActionContext -import com.itangcent.intellij.logger.Logger +import com.itangcent.intellij.extend.logger import com.itangcent.intellij.util.FileUtils import com.itangcent.intellij.util.ToolUtils import com.itangcent.utils.localPath @@ -27,7 +27,7 @@ class DefaultFileSaveHelper : FileSaveHelper { private val lastImportedLocation: String? = null @Inject - private val actionContext: ActionContext? = null + private lateinit var actionContext: ActionContext override fun saveOrCopy( content: String?, @@ -62,11 +62,13 @@ class DefaultFileSaveHelper : FileSaveHelper { ) { if (content == null) return - IdeaFileChooserHelper.create(actionContext!!, FileChooserDescriptorFactory - .createSingleFileOrFolderDescriptor() - .withTitle("Export Location") - .withDescription("Choose directory to export api to") - .withHideIgnored(false)) + IdeaFileChooserHelper.create( + actionContext, FileChooserDescriptorFactory + .createSingleFileOrFolderDescriptor() + .withTitle("Export Location") + .withDescription("Choose directory to export api to") + .withHideIgnored(false) + ) .lastSelectedLocation(getLastImportedLocation()) .selectFile({ file -> if (file.isDirectory) { @@ -107,11 +109,13 @@ class DefaultFileSaveHelper : FileSaveHelper { onSaveFailed: (String?) -> Unit, onSaveCancel: () -> Unit, ) { - IdeaFileChooserHelper.create(actionContext!!, FileChooserDescriptorFactory - .createSingleFileOrFolderDescriptor() - .withTitle("Select Location") - .withDescription("Choose folder/file to save") - .withHideIgnored(false)) + IdeaFileChooserHelper.create( + actionContext, FileChooserDescriptorFactory + .createSingleFileOrFolderDescriptor() + .withTitle("Select Location") + .withDescription("Choose folder/file to save") + .withHideIgnored(false) + ) .lastSelectedLocation(getLastImportedLocation()) .selectFile({ file -> if (file.isDirectory) { @@ -171,16 +175,15 @@ class DefaultFileSaveHelper : FileSaveHelper { } private fun copyAndLog(info: String, onCopy: () -> Unit) { - val logger: Logger = ActionContext.getContext()!!.instance(Logger::class) try { ToolUtils.copy2Clipboard(info) - } catch (e: HeadlessException) { + } catch (_: HeadlessException) { } onCopy() if (info.length > 10000) { - logger.info("Api data is too lager to show in console!") + actionContext.logger().info("Api data is too lager to show in console!") } else { - logger.log(info) + actionContext.logger().log(info) } } diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/utils/FormatterHelper.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/utils/FormatterHelper.kt new file mode 100644 index 000000000..ebd600099 --- /dev/null +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/utils/FormatterHelper.kt @@ -0,0 +1,80 @@ +package com.itangcent.idea.utils + +import com.google.inject.Inject +import com.google.inject.Singleton +import com.google.inject.name.Named +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.LocalFileSystem +import com.intellij.psi.PsiFile +import com.intellij.psi.PsiManager +import com.intellij.psi.codeStyle.CodeStyleManager +import com.itangcent.common.logger.traceError +import com.itangcent.common.utils.IDUtils +import com.itangcent.intellij.context.ActionContext +import com.itangcent.intellij.extend.logger +import com.itangcent.intellij.file.LocalFileRepository +import java.io.File +import java.nio.charset.StandardCharsets + +/** + * Provides utilities for formatting text in various formats (e.g., JSON, XML, HTML) + * by leveraging IntelliJ Platform's code style management capabilities. + * + * @author tangcent + * @date 2024/03/31 + */ +@Singleton +class FormatterHelper { + + @Inject + private lateinit var actionContext: ActionContext + + @Inject + private lateinit var project: Project + + @Inject + @Named("projectCacheRepository") + private lateinit var localFileRepository: LocalFileRepository + + private val localFileSystem by lazy { LocalFileSystem.getInstance() } + + /** + * Formats the given text according to the specified type (e.g., "json", "xml", "html"), + * utilizing the code style settings of the current project. + * + * @param text The text to format. + * @param type The type of the text (e.g., "json", "xml", "html"). + * @return The formatted text. + */ + fun formatText(text: String, type: String): String { + try { + return actionContext.callInWriteUI { + val file: File = localFileRepository.getOrCreateFile("temp${IDUtils.shortUUID()}.${type}") + try { + file.writeText(text, StandardCharsets.UTF_8) + val virtualFile = localFileSystem.refreshAndFindFileByPath(file.absolutePath)!! + val psiFile: PsiFile = PsiManager.getInstance(project).findFile(virtualFile)!! + CodeStyleManager.getInstance(project).reformat(psiFile) + psiFile.text + } finally { + file.delete() + } + }!! + } catch (e: Exception) { + actionContext.logger().traceError("format text failed", e) + return text + } + } +} + +fun FormatterHelper.formatJson(json: String): String { + return formatText(json, "json") +} + +fun FormatterHelper.formatXml(xml: String): String { + return formatText(xml, "xml") +} + +fun FormatterHelper.formatHtml(html: String): String { + return formatText(html, "html") +} \ No newline at end of file diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/utils/GsonExUtils.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/utils/GsonExUtils.kt index 905e3a0b9..81562cb5a 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/utils/GsonExUtils.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/utils/GsonExUtils.kt @@ -1,18 +1,10 @@ package com.itangcent.idea.utils -import com.itangcent.common.utils.GsonUtils - -@Deprecated(replaceWith = ReplaceWith("GsonUtils"), message = "use GsonUtils") -typealias GsonExUtils = GsonUtils - -fun GsonUtils.prettyJsonStr(json: String): String { - val jsonObject = parseToJsonTree(json) - return prettyJson(jsonObject) -} - -fun String.resolveGsonLazily(): String { - if (this.contains("\"com.google.gson.internal.LazilyParsedNumber\"")) { - return this.replace("\"com.google.gson.internal.LazilyParsedNumber\"", "\"java.lang.Integer\"") +object GsonExUtils { + fun resolveGsonLazily(str: String): String { + if (str.contains("\"com.google.gson.internal.LazilyParsedNumber\"")) { + return str.replace("\"com.google.gson.internal.LazilyParsedNumber\"", "\"java.lang.Integer\"") + } + return str } - return this -} \ No newline at end of file +} diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/utils/JacksonUtils.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/utils/JacksonUtils.kt index 1be27643d..27880b89e 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/utils/JacksonUtils.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/utils/JacksonUtils.kt @@ -39,7 +39,7 @@ object JacksonUtils : Log() { val split = json.indexOf(',') return try { objectMapper.readValue( - json.substring(split + 1).resolveGsonLazily(), + GsonExUtils.resolveGsonLazily(json.substring(split + 1)), Class.forName(json.substring(0, split)) as Class ) } catch (e: Exception) { diff --git a/idea-plugin/src/main/kotlin/com/itangcent/intellij/extend/ActionContextKit.kt b/idea-plugin/src/main/kotlin/com/itangcent/intellij/extend/ActionContextKit.kt index d0774148a..c0132eecf 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/intellij/extend/ActionContextKit.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/intellij/extend/ActionContextKit.kt @@ -94,7 +94,7 @@ fun ActionContext.callWithBoundary(action: () -> T): T? { try { ret = action() } catch (e: Exception) { - ActionContext.instance(Logger::class).traceError(e) + logger().traceError(e) } boundary.waitComplete() return ret @@ -109,13 +109,15 @@ fun ActionContext.withBoundary(timeOut: Long, action: () -> Unit) { try { action() } catch (e: Throwable) { - ActionContext.instance(Logger::class).traceError(e) + logger().traceError(e) } if (!boundary.waitComplete(timeOut)) { boundary.close() } } +fun ActionContext.logger() = instance(Logger::class) + /** * Runs the specified action with the current ActionContext instance. * If no instance is available, the action is run asynchronously. diff --git a/idea-plugin/src/test/kotlin/com/itangcent/idea/utils/FormatterHelperTest.kt b/idea-plugin/src/test/kotlin/com/itangcent/idea/utils/FormatterHelperTest.kt new file mode 100644 index 000000000..335e23909 --- /dev/null +++ b/idea-plugin/src/test/kotlin/com/itangcent/idea/utils/FormatterHelperTest.kt @@ -0,0 +1,102 @@ +package com.itangcent.idea.utils + +import com.google.inject.Inject +import com.itangcent.testFramework.ContextLightCodeInsightFixtureTestCase + +/** + * Test case for [FormatterHelper] + * + * @author tangcent + * @date 2024/03/31 + */ +class FormatterHelperTest : ContextLightCodeInsightFixtureTestCase() { + + @Inject + private lateinit var formatterHelper: FormatterHelper + + fun testFormatJson() { + assertEquals( + """ + { + "name": "tangcent", + "age": 18 + } + """.trimIndent(), + formatterHelper.formatJson( + """ + {"name": "tangcent", "age": 18 } + """.trimIndent() + ) + ) + } + + fun testFormatJsonWithInvalidInput() { + assertEquals( + "invalid json", + formatterHelper.formatJson("invalid json") + ) + } + + fun testFormatHtml() { + assertEquals( + """ + + Example +

Hello, world!

+ + """.trimIndent(), + formatterHelper.formatHtml( + """ + Example

Hello, world!

+ """.trimIndent() + ) + ) + } + + fun testFormatHtmlWithInvalidInput() { + assertEquals( + """ + + Missing closing tags + """.trimIndent(), + formatterHelper.formatHtml("<html><head><title>Missing closing tags") + ) + } + + fun testFormatXml() { + assertEquals( + """ + <?xml version="1.0" encoding="UTF-8"?> + <note> + <to>Tove</to> + <from>Jani</from> + <heading>Reminder</heading> + <body>Don't forget me this weekend!</body> + </note> + """.trimIndent(), + formatterHelper.formatXml( + """ + <?xml version="1.0" encoding="UTF-8"?><note><to>Tove</to><from>Jani</from><heading>Reminder</heading><body>Don't forget me this weekend!</body></note> + """.trimIndent() + ) + ) + } + + fun testFormatXmlWithInvalidInput() { + assertEquals( + """ + <?xml version="1.0" encoding="UTF-8"?> + <note><to>Tove<from>Jani + </note> + """.trimIndent(), + formatterHelper.formatXml("<?xml version=\"1.0\" encoding=\"UTF-8\"?><note><to>Tove<from>Jani</note>") + ) + } + + fun testFormatUnknownType() { + assertEquals( + "unknown type", + formatterHelper.formatText("unknown type", "unknown") + ) + } +} \ No newline at end of file diff --git a/idea-plugin/src/test/kotlin/com/itangcent/idea/utils/GsonExUtilsTest.kt b/idea-plugin/src/test/kotlin/com/itangcent/idea/utils/GsonExUtilsTest.kt index 3025942fe..70eb49764 100644 --- a/idea-plugin/src/test/kotlin/com/itangcent/idea/utils/GsonExUtilsTest.kt +++ b/idea-plugin/src/test/kotlin/com/itangcent/idea/utils/GsonExUtilsTest.kt @@ -7,44 +7,35 @@ import org.junit.jupiter.api.Test /** * Test case for [GsonExUtils] */ -@Suppress("CAST_NEVER_SUCCEEDS") class GsonExUtilsTest { @Test - fun testFromJson() { - //null - assertEquals(null as String?, GsonExUtils.fromJson(GsonExUtils.toJson(null))) - - //int - assertEquals(1 as Int?, GsonExUtils.fromJson(GsonExUtils.toJson(1))!!) - - //long - assertEquals(9999L, GsonExUtils.fromJson(GsonExUtils.toJson(9999L))!!) - - //float - assertEquals(1.1f as Float?, GsonExUtils.fromJson(GsonExUtils.toJson(1.1f))!!) - - //double - assertEquals(1.1 as Double?, GsonExUtils.fromJson(GsonExUtils.toJson(1.1))!!) - - //string - assertEquals("hello world", GsonExUtils.fromJson(GsonExUtils.toJson("hello world"))) + fun `resolveGsonLazily replaces LazilyParsedNumber with Integer when present`() { + val originalString = "This is a test with \"com.google.gson.internal.LazilyParsedNumber\" included" + val expected = "This is a test with \"java.lang.Integer\" included" + val result = GsonExUtils.resolveGsonLazily(originalString) + assertEquals(expected, result) + } - //custom data - val point = GsonExUtilsTestPoint(1, 2) - assertEquals(point, GsonExUtils.fromJson<GsonExUtilsTestPoint>(GsonExUtils.toJson(point))) + @Test + fun `resolveGsonLazily returns the original string when LazilyParsedNumber is not present`() { + val originalString = "This is a test without the specific substring" + val result = GsonExUtils.resolveGsonLazily(originalString) + assertEquals(originalString, result) } @Test - fun prettyJsonStr() { - assertEquals("{\n \"a\": 1\n}", GsonExUtils.prettyJsonStr("{a:1}")) - assertEquals( - "{\n" + - " \"a\": 1.0,\n" + - " \"b\": \"1.1f\"\n" + - "}", GsonExUtils.prettyJsonStr("{a:1.0,b:1.1f}") - ) + fun `resolveGsonLazily returns an empty string when given an empty string`() { + val originalString = "" + val result = GsonExUtils.resolveGsonLazily(originalString) + assertEquals(originalString, result) } -} -data class GsonExUtilsTestPoint(var x: Int, var y: Int) \ No newline at end of file + @Test + fun `resolveGsonLazily replaces the entire string when it only contains LazilyParsedNumber`() { + val originalString = "\"com.google.gson.internal.LazilyParsedNumber\"" + val expected = "\"java.lang.Integer\"" + val result = GsonExUtils.resolveGsonLazily(originalString) + assertEquals(expected, result) + } +} \ No newline at end of file