mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-03-22 15:19:59 +07:00
WEB-76927 PolySymbols: make it easier to provide generic properties in implementations
(cherry picked from commit 05381d85eb702bf76e1f25bed8e87e794f43ea7b) IJ-CR-193424 GitOrigin-RevId: 5e7d1cecf23f7e2f6a1d895f4394452255d6a823
This commit is contained in:
committed by
intellij-monorepo-bot
parent
40abaa7658
commit
a128acc83e
@@ -43,6 +43,9 @@
|
||||
- a:getValue():D
|
||||
*f:com.intellij.polySymbols.PolySymbol$Priority$Companion
|
||||
- f:custom(D):com.intellij.polySymbols.PolySymbol$Priority
|
||||
*@:com.intellij.polySymbols.PolySymbol$Property
|
||||
- java.lang.annotation.Annotation
|
||||
- a:property():java.lang.Class
|
||||
*:com.intellij.polySymbols.PolySymbolApiStatus
|
||||
- *sf:Companion:com.intellij.polySymbols.PolySymbolApiStatus$Companion
|
||||
- sf:Deprecated:com.intellij.polySymbols.PolySymbolApiStatus$Deprecated
|
||||
@@ -200,11 +203,15 @@
|
||||
- s:getEntries():kotlin.enums.EnumEntries
|
||||
- s:valueOf(java.lang.String):com.intellij.polySymbols.PolySymbolNameSegment$MatchProblem
|
||||
- s:values():com.intellij.polySymbols.PolySymbolNameSegment$MatchProblem[]
|
||||
*:com.intellij.polySymbols.PolySymbolProperty
|
||||
*a:com.intellij.polySymbols.PolySymbolProperty
|
||||
- *sf:Companion:com.intellij.polySymbols.PolySymbolProperty$Companion
|
||||
- s:get(java.lang.String,java.lang.Class):com.intellij.polySymbols.PolySymbolProperty
|
||||
- a:getName():java.lang.String
|
||||
- a:tryCast(java.lang.Object):java.lang.Object
|
||||
- <init>(java.lang.String,java.lang.Class):V
|
||||
- f:equals(java.lang.Object):Z
|
||||
- sf:get(java.lang.String,java.lang.Class):com.intellij.polySymbols.PolySymbolProperty
|
||||
- f:getName():java.lang.String
|
||||
- f:getType():java.lang.Class
|
||||
- f:hashCode():I
|
||||
- f:tryCast(java.lang.Object):java.lang.Object
|
||||
*f:com.intellij.polySymbols.PolySymbolProperty$Companion
|
||||
- f:get(java.lang.String,java.lang.Class):com.intellij.polySymbols.PolySymbolProperty
|
||||
*:com.intellij.polySymbols.PolySymbolQualifiedName
|
||||
|
||||
@@ -14,6 +14,7 @@ jvm_library(
|
||||
srcs = glob(["src/**/*.kt", "src/**/*.java", "src/**/*.form"], allow_empty = True),
|
||||
resources = [":backend_resources"],
|
||||
deps = [
|
||||
"//libraries/kotlin/reflect",
|
||||
"@lib//:kotlin-stdlib",
|
||||
"//platform/core-api:core",
|
||||
"//platform/projectModel-impl",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="module" module-name="intellij.libraries.kotlin.reflect" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="kotlin-stdlib" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.platform.core" />
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
<idea-plugin visibility="public">
|
||||
<dependencies>
|
||||
<module name="intellij.platform.backend"/>
|
||||
<!-- region Generated dependencies - run `Generate Product Layouts` to regenerate -->
|
||||
<module name="intellij.libraries.kotlin.reflect"/>
|
||||
<module name="intellij.platform.polySymbols"/>
|
||||
<!-- endregion -->
|
||||
</dependencies>
|
||||
|
||||
<extensionPoints>
|
||||
|
||||
@@ -16,6 +16,7 @@ import com.intellij.platform.backend.presentation.TargetPresentation
|
||||
import com.intellij.polySymbols.PolySymbol.Companion.PROP_DOC_HIDE_ICON
|
||||
import com.intellij.polySymbols.context.PolyContext
|
||||
import com.intellij.polySymbols.documentation.PolySymbolDocumentationCustomizer
|
||||
import com.intellij.polySymbols.impl.PolySymbolPropertyGetter
|
||||
import com.intellij.polySymbols.query.PolySymbolMatch
|
||||
import com.intellij.polySymbols.query.PolySymbolMatchCustomizer
|
||||
import com.intellij.polySymbols.query.PolySymbolQueryExecutor
|
||||
@@ -38,6 +39,7 @@ import com.intellij.util.concurrency.annotations.RequiresReadLock
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import java.util.Locale
|
||||
import javax.swing.Icon
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* The core element of the Poly Symbols framework. It is identified through `name` and `kind` properties.
|
||||
@@ -165,7 +167,7 @@ interface PolySymbol : Symbol, NavigatableSymbol, PolySymbolPrioritizedScope {
|
||||
* [PolySymbolProperty.tryCast] method for returned values.
|
||||
*/
|
||||
operator fun <T : Any> get(property: PolySymbolProperty<T>): T? =
|
||||
null
|
||||
PolySymbolPropertyGetter.get(this, property)
|
||||
|
||||
/**
|
||||
* Returns [TargetPresentation] used by [SearchTarget] and [RenameTarget].
|
||||
@@ -273,7 +275,6 @@ interface PolySymbol : Symbol, NavigatableSymbol, PolySymbolPrioritizedScope {
|
||||
): String =
|
||||
queryExecutor.namesProvider.adjustRename(oldName, newName, occurence)
|
||||
|
||||
|
||||
sealed interface Priority : Comparable<Priority> {
|
||||
|
||||
val value: Double
|
||||
@@ -312,6 +313,11 @@ interface PolySymbol : Symbol, NavigatableSymbol, PolySymbolPrioritizedScope {
|
||||
}
|
||||
}
|
||||
|
||||
@Target(AnnotationTarget.PROPERTY, AnnotationTarget.FIELD, AnnotationTarget.PROPERTY_GETTER,
|
||||
AnnotationTarget.ANNOTATION_CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class Property(val property: KClass<*>)
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,14 +1,30 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.polySymbols
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
abstract class PolySymbolProperty<T : Any>(
|
||||
val name: String,
|
||||
type: Class<T>,
|
||||
) {
|
||||
|
||||
@ApiStatus.NonExtendable
|
||||
interface PolySymbolProperty<T : Any> {
|
||||
val type: Class<T> = if (type.isPrimitive) type.kotlin.javaObjectType else type
|
||||
|
||||
val name: String
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun tryCast(value: Any?): T? =
|
||||
if (value != null && this.type.isInstance(value)) value as T? else null
|
||||
|
||||
fun tryCast(value: Any?): T?
|
||||
final override fun equals(other: Any?): Boolean =
|
||||
other === this ||
|
||||
other is PolySymbolPropertyData<*>
|
||||
&& other.name == name
|
||||
&& other.type == type
|
||||
|
||||
final override fun hashCode(): Int {
|
||||
var result = name.hashCode()
|
||||
result = 31 * result + type.hashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
override fun toString(): String = name + ": " + type.simpleName + " (" + javaClass.simpleName + ")"
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@@ -21,24 +37,8 @@ interface PolySymbolProperty<T : Any> {
|
||||
}
|
||||
}
|
||||
|
||||
private class PolySymbolPropertyData<T : Any>(override val name: String, private val type: Class<T>) : PolySymbolProperty<T> {
|
||||
private class PolySymbolPropertyData<T : Any>(name: String, type: Class<T>) : PolySymbolProperty<T>(name, type) {
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun tryCast(value: Any?): T? =
|
||||
if (value != null && this.type.isInstance(value)) value as T? else null
|
||||
|
||||
override fun equals(other: Any?): Boolean =
|
||||
other === this ||
|
||||
other is PolySymbolPropertyData<*>
|
||||
&& other.name == name
|
||||
&& other.type == type
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = name.hashCode()
|
||||
result = 31 * result + type.hashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
override fun toString(): String = name
|
||||
override fun toString(): String = name + ": " + type.simpleName
|
||||
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.polySymbols.impl
|
||||
|
||||
import com.intellij.openapi.diagnostic.thisLogger
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.polySymbols.PolySymbol
|
||||
import com.intellij.polySymbols.PolySymbolProperty
|
||||
import com.intellij.util.asSafely
|
||||
import com.intellij.util.containers.ContainerUtil
|
||||
import java.lang.reflect.Field
|
||||
import java.lang.reflect.Method
|
||||
|
||||
object PolySymbolPropertyGetter {
|
||||
|
||||
private val accessorsMap: MutableMap<Class<out PolySymbol>, Map<PolySymbolProperty<*>, (PolySymbol) -> Any?>> =
|
||||
ContainerUtil.createConcurrentWeakMap()
|
||||
|
||||
fun <T : Any> get(symbol: PolySymbol, property: PolySymbolProperty<T>): T? =
|
||||
accessorsMap.computeIfAbsent(symbol::class.java) {
|
||||
buildPropertiesMap(it)
|
||||
}[property]?.let { property.tryCast(it(symbol)) }
|
||||
|
||||
private fun buildPropertiesMap(symbolClass: Class<out PolySymbol>): Map<PolySymbolProperty<*>, (PolySymbol) -> Any?> {
|
||||
val toVisit = ArrayDeque<Class<*>>()
|
||||
toVisit.add(symbolClass)
|
||||
val visited = hashSetOf<Class<*>>()
|
||||
|
||||
val result = mutableMapOf<PolySymbolProperty<*>, (PolySymbol) -> Any?>()
|
||||
|
||||
while (toVisit.isNotEmpty()) {
|
||||
val clazz = toVisit.removeFirst()
|
||||
if (!visited.add(clazz)) continue
|
||||
|
||||
for (field in clazz.declaredFields) {
|
||||
val property = getProperty(field.getDeclaredAnnotation(PolySymbol.Property::class.java))
|
||||
?: continue
|
||||
addField(property, field, result, clazz)
|
||||
}
|
||||
for (method in clazz.declaredMethods) {
|
||||
val property = getProperty(method.getDeclaredAnnotation(PolySymbol.Property::class.java))
|
||||
?: continue
|
||||
addMethod(property, method, result, clazz)
|
||||
}
|
||||
clazz.superclass?.let { toVisit.add(it) }
|
||||
toVisit.addAll(clazz.interfaces)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun addField(
|
||||
property: PolySymbolProperty<*>?,
|
||||
field: Field,
|
||||
result: MutableMap<PolySymbolProperty<*>, (PolySymbol) -> Any?>,
|
||||
clazz: Class<*>,
|
||||
) {
|
||||
if (property == null || result.containsKey(property)) return
|
||||
if (property.type.objectType.isAssignableFrom(field.type.objectType)) {
|
||||
field.isAccessible = true
|
||||
result[property] = { field.get(it) }
|
||||
}
|
||||
else {
|
||||
thisLogger().error("PolySymbol property ${property.name} of type ${property.type} is not assignable from field type ${field.type} of ${clazz.name}.${field.name}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun addMethod(
|
||||
property: PolySymbolProperty<*>?,
|
||||
method: Method,
|
||||
result: MutableMap<PolySymbolProperty<*>, (PolySymbol) -> Any?>,
|
||||
clazz: Class<*>,
|
||||
) {
|
||||
if (property == null || result.containsKey(property)) return
|
||||
val actualMethod = if (method.name.endsWith($$"$annotations"))
|
||||
try {
|
||||
clazz.getDeclaredMethod(method.name.removeSuffix($$"$annotations"))
|
||||
}
|
||||
catch (_: NoSuchMethodException) {
|
||||
if (method.name.startsWith("get")) {
|
||||
try {
|
||||
val field = clazz.getDeclaredField(
|
||||
method.name.removeSuffix($$"$annotations").removePrefix("get").let { StringUtil.decapitalize(it) }
|
||||
)
|
||||
addField(property, field, result, clazz)
|
||||
return
|
||||
}
|
||||
catch (_: NoSuchFieldException) {
|
||||
}
|
||||
}
|
||||
method
|
||||
}
|
||||
else
|
||||
method
|
||||
if (actualMethod.parameterCount != 0) {
|
||||
thisLogger().error("Method ${clazz.name}.${actualMethod.name} annotated with @PolySymbol.Property should not have parameters.")
|
||||
}
|
||||
else if (property.type.objectType.isAssignableFrom(actualMethod.returnType.objectType)) {
|
||||
actualMethod.isAccessible = true
|
||||
result[property] = { actualMethod.invoke(it) }
|
||||
}
|
||||
else {
|
||||
thisLogger().error("PolySymbol property ${property.name} of type ${property.type} is not assignable from return type ${actualMethod.returnType} of ${clazz.name}.${actualMethod.name}()")
|
||||
}
|
||||
}
|
||||
|
||||
private val Class<*>.objectType: Class<*>
|
||||
get() = if (isPrimitive) kotlin.javaObjectType else this
|
||||
|
||||
private fun getProperty(annotation: PolySymbol.Property?): PolySymbolProperty<*>? {
|
||||
val propertyClass = annotation?.property ?: return null
|
||||
@Suppress("NO_REFLECTION_IN_CLASS_PATH")
|
||||
return propertyClass.objectInstance.asSafely<PolySymbolProperty<*>>()
|
||||
?: try {
|
||||
propertyClass.java.getDeclaredConstructor()
|
||||
}
|
||||
catch (_: NoSuchMethodException) {
|
||||
null
|
||||
}
|
||||
?.newInstance()
|
||||
?.asSafely<PolySymbolProperty<*>>()
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,7 @@ interface PolySymbolDelegate<T : PolySymbol> : PolySymbol, PolySymbolScope {
|
||||
get() = delegate.priority
|
||||
|
||||
override fun <T : Any> get(property: PolySymbolProperty<T>): T? =
|
||||
delegate[property]
|
||||
super.get(property) ?: delegate[property]
|
||||
|
||||
override fun getDocumentationTarget(location: PsiElement?): DocumentationTarget? =
|
||||
delegate.getDocumentationTarget(location)
|
||||
|
||||
@@ -111,7 +111,7 @@ open class PolySymbolsDebugOutputPrinter : DebugOutputPrinter() {
|
||||
level, "properties",
|
||||
propertiesToPrint
|
||||
.sortedBy { it.name }
|
||||
.mapNotNull { prop -> source[prop]?.let { Pair(prop, it) } }
|
||||
.mapNotNull { prop -> source[prop]?.let { Pair(prop.name, it) } }
|
||||
.toMap()
|
||||
.takeIf { it.isNotEmpty() }
|
||||
)
|
||||
|
||||
@@ -59,7 +59,7 @@ internal class CommitMessageReferenceProvider : PsiPolySymbolReferenceProvider<P
|
||||
override fun <T : Any> get(property: PolySymbolProperty<T>): T? =
|
||||
when (property) {
|
||||
PolySymbol.PROP_IJ_TEXT_ATTRIBUTES_KEY -> property.tryCast(EditorColors.REFERENCE_HYPERLINK_COLOR.externalName)
|
||||
else -> null
|
||||
else -> super.get(property)
|
||||
}
|
||||
|
||||
override val presentation: TargetPresentation
|
||||
|
||||
@@ -159,7 +159,7 @@ sealed class AbstractTaskSymbol : PolySymbol, DocumentationSymbol {
|
||||
when (property) {
|
||||
PolySymbol.PROP_IJ_TEXT_ATTRIBUTES_KEY -> property.tryCast(EditorColors.REFERENCE_HYPERLINK_COLOR.externalName)
|
||||
TASK_PROPERTY -> property.tryCast(task)
|
||||
else -> null
|
||||
else -> super.get(property)
|
||||
}
|
||||
|
||||
override val presentation: TargetPresentation
|
||||
|
||||
Reference in New Issue
Block a user