diff --git a/.idea/modules.xml b/.idea/modules.xml
index 424dccfeb890..d87d1422e2ea 100644
--- a/.idea/modules.xml
+++ b/.idea/modules.xml
@@ -356,6 +356,8 @@
+
+
diff --git a/plugins/devkit/intellij.devkit.workspaceModel/intellij.devkit.workspaceModel.iml b/plugins/devkit/intellij.devkit.workspaceModel/intellij.devkit.workspaceModel.iml
index 84fedb22b92d..cef7f54d19a8 100644
--- a/plugins/devkit/intellij.devkit.workspaceModel/intellij.devkit.workspaceModel.iml
+++ b/plugins/devkit/intellij.devkit.workspaceModel/intellij.devkit.workspaceModel.iml
@@ -4,7 +4,7 @@
-
+
@@ -24,38 +24,38 @@
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
\ No newline at end of file
diff --git a/plugins/devkit/intellij.devkit.workspaceModel/k1/intellij.devkit.workspaceModel.k1.iml b/plugins/devkit/intellij.devkit.workspaceModel/k1/intellij.devkit.workspaceModel.k1.iml
new file mode 100644
index 000000000000..40fa32898583
--- /dev/null
+++ b/plugins/devkit/intellij.devkit.workspaceModel/k1/intellij.devkit.workspaceModel.k1.iml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/plugins/devkit/intellij.devkit.workspaceModel/k1/resources/intellij.devkit.workspaceModel.k1.xml b/plugins/devkit/intellij.devkit.workspaceModel/k1/resources/intellij.devkit.workspaceModel.k1.xml
new file mode 100644
index 000000000000..3a7d850aef62
--- /dev/null
+++ b/plugins/devkit/intellij.devkit.workspaceModel/k1/resources/intellij.devkit.workspaceModel.k1.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/plugins/devkit/intellij.devkit.workspaceModel/src/metaModel/impl/extensions/DescriptorExtensions.kt b/plugins/devkit/intellij.devkit.workspaceModel/k1/src/metaModel/DescriptorExtensions.kt
similarity index 65%
rename from plugins/devkit/intellij.devkit.workspaceModel/src/metaModel/impl/extensions/DescriptorExtensions.kt
rename to plugins/devkit/intellij.devkit.workspaceModel/k1/src/metaModel/DescriptorExtensions.kt
index 9c8e83fa3777..0df0a2c55ce0 100644
--- a/plugins/devkit/intellij.devkit.workspaceModel/src/metaModel/impl/extensions/DescriptorExtensions.kt
+++ b/plugins/devkit/intellij.devkit.workspaceModel/k1/src/metaModel/DescriptorExtensions.kt
@@ -1,11 +1,13 @@
-// 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.devkit.workspaceModel.metaModel.impl.extensions
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.devkit.workspaceModel.k1.metaModel
-import com.intellij.devkit.workspaceModel.metaModel.impl.*
+import com.intellij.devkit.workspaceModel.metaModel.WorkspaceModelDefaults
+import com.intellij.devkit.workspaceModel.metaModel.impl.CompiledObjModuleImpl
+import com.intellij.devkit.workspaceModel.metaModel.impl.ObjAnnotationImpl
+import com.intellij.devkit.workspaceModel.metaModel.impl.ObjClassImpl
+import com.intellij.psi.CommonClassNames
import com.intellij.psi.JavaPsiFacade
-import com.intellij.psi.PsiClass
import com.intellij.psi.search.GlobalSearchScope
-import com.intellij.psi.search.SearchScope
import com.intellij.psi.search.searches.ClassInheritorsSearch
import com.intellij.workspaceModel.codegen.deft.meta.Obj
import com.intellij.workspaceModel.codegen.deft.meta.ObjClass
@@ -22,39 +24,31 @@ import org.jetbrains.kotlin.resolve.source.getPsi
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.typeUtil.supertypes
-
private val moduleAbstractTypesClassIds: List = listOf(
- ClassId.topLevel(StandardNames.ENTITY_SOURCE),
- ClassId.topLevel(StandardNames.SYMBOLIC_ENTITY_ID)
+ WorkspaceModelDefaults.ENTITY_SOURCE.classId,
+ WorkspaceModelDefaults.SYMBOLIC_ENTITY_ID.classId
)
-private val entitiesSuperclassFqn = StandardNames.WORKSPACE_ENTITY
+private val entitiesSuperclassFqn: FqName = WorkspaceModelDefaults.WORKSPACE_ENTITY.fqName
-internal val blobClasses: List = arrayListOf(
- StandardNames.VIRTUAL_FILE_URL,
- StandardNames.ENTITY_POINTER
+private val blobClasses: List = arrayListOf(
+ WorkspaceModelDefaults.VIRTUAL_FILE_URL.fqName,
+ WorkspaceModelDefaults.ENTITY_POINTER.fqName
)
-
internal val ModuleDescriptor.moduleAbstractTypes: List
get() = moduleAbstractTypesClassIds.mapNotNull { findClassAcrossModuleDependencies(it) }
internal fun ClassDescriptor.inheritors(
- javaPsiFacade: JavaPsiFacade, scope: GlobalSearchScope
+ javaPsiFacade: JavaPsiFacade, scope: GlobalSearchScope,
): List {
val psiClass = javaPsiFacade.findClass(fqNameSafe.asString(), scope) ?: return emptyList()
- return psiClass.inheritors(scope).map { it.getJavaClassDescriptor()!! }
+ val inheritors = ClassInheritorsSearch.search(psiClass, scope, true, true, false)
+ .filterNot { it.qualifiedName == null }
+ .sortedBy { it.qualifiedName } // Sorting is needed for consistency in case of regeneration
+ return inheritors.map { it.getJavaClassDescriptor()!! }
}
-private fun PsiClass.inheritors(scope: SearchScope): List {
- val inheritors = ClassInheritorsSearch.search(this, scope, true, true, false).asIterable().filterNot { it.isAnonymous }
- return inheritors.sortedBy { it.qualifiedName } //Sorting is needed for consistency in case of regeneration
-}
-
-private val PsiClass.isAnonymous: Boolean
- get() = qualifiedName == null
-
-
internal val ClassifierDescriptor.javaClassFqn: String
get() {
val nameWithoutPackage = fqNameSafe.asString()
@@ -73,7 +67,7 @@ internal val ClassDescriptor.superTypesJavaFqns: List
get() = defaultType.supertypes()
.mapNotNull { it.constructor.declarationDescriptor?.javaClassFqn }
.withoutStandardTypes()
-
+ .sorted()
internal val ClassDescriptor.isObject: Boolean
get() = kind == ClassKind.OBJECT
@@ -96,11 +90,11 @@ internal val ClassDescriptor.isAbstractClassOrInterface: Boolean
get() = !isFinalClass
internal val ClassDescriptor.isEntityInterface: Boolean
- get() = DescriptorUtils.isInterface(this) && defaultType.isSubclassOf(entitiesSuperclassFqn) // If this is `WorkspaceEntity` interface we need to check it
+ // If this is `WorkspaceEntity` interface, we need to check it
+ get() = DescriptorUtils.isInterface(this) && defaultType.isSubclassOf(entitiesSuperclassFqn)
internal val ClassDescriptor.isEntityBuilderInterface: Boolean
- get() = isEntityInterface && name.identifier == "Builder" //todo improve
-
+ get() = isEntityInterface && name.identifier == "Builder" // TODO: improve
private fun KotlinType.isSubclassOf(superClassName: FqName): Boolean {
return constructor.declarationDescriptor?.fqNameSafe == superClassName || constructor.supertypes.any {
@@ -108,32 +102,32 @@ private fun KotlinType.isSubclassOf(superClassName: FqName): Boolean {
}
}
-
internal fun createObjTypeStub(interfaceDescriptor: ClassDescriptor, module: CompiledObjModuleImpl): ObjClassImpl {
val openness = when {
- interfaceDescriptor.isAnnotatedBy(StandardNames.ABSTRACT_ANNOTATION) -> ObjClass.Openness.abstract
- interfaceDescriptor.isAnnotatedBy(StandardNames.OPEN_ANNOTATION) -> ObjClass.Openness.open
+ interfaceDescriptor.isAnnotatedBy(WorkspaceModelDefaults.ABSTRACT_ANNOTATION.fqName) -> ObjClass.Openness.abstract
+ interfaceDescriptor.isAnnotatedBy(WorkspaceModelDefaults.OPEN_ANNOTATION.fqName) -> ObjClass.Openness.open
else -> ObjClass.Openness.final
}
val propertyAnnotations = interfaceDescriptor.annotations
- .mapNotNull { it.fqName }
- .map { ObjAnnotationImpl(it.asString(), it.pathSegments().map { segment -> segment.asString() }) }
- return ObjClassImpl(module, interfaceDescriptor.name.identifier, openness, interfaceDescriptor.source, propertyAnnotations)
+ .mapNotNull { it.fqName }
+ .map { ObjAnnotationImpl(it.asString(), it.pathSegments().map { segment -> segment.asString() }) }
+ return ObjClassImpl(module, interfaceDescriptor.name.identifier, openness, interfaceDescriptor.source.getPsi(), propertyAnnotations)
}
-internal fun computeKind(property: PropertyDescriptor): ObjProperty.ValueKind {
- val getter = property.getter ?: return ObjProperty.ValueKind.Plain
+internal fun PropertyDescriptor.computeKind(): ObjProperty.ValueKind {
+ val getter = getter ?: return ObjProperty.ValueKind.Plain
if (!getter.hasBody()) return ObjProperty.ValueKind.Plain
val declaration = getter.source.getPsi() as? KtDeclarationWithBody ?: return ObjProperty.ValueKind.Plain
val getterText = (declaration.bodyExpression ?: declaration.bodyBlockExpression)?.text
return when {
getterText == null -> ObjProperty.ValueKind.Plain
- getter.isAnnotatedBy(StandardNames.DEFAULT_ANNOTATION) -> ObjProperty.ValueKind.WithDefault(getterText)
+ getter.isAnnotatedBy(WorkspaceModelDefaults.DEFAULT_ANNOTATION.fqName) -> ObjProperty.ValueKind.WithDefault(getterText)
else -> ObjProperty.ValueKind.Computable(getterText)
}
}
+private val standardTypes = setOf(Any::class.qualifiedName, CommonClassNames.JAVA_LANG_OBJECT, CommonClassNames.JAVA_LANG_ENUM)
private fun Iterable.withoutStandardTypes(): List {
return filterNot { standardTypes.contains(it) }
-}
\ No newline at end of file
+}
diff --git a/plugins/devkit/intellij.devkit.workspaceModel/k1/src/metaModel/ObjTypeConverter.kt b/plugins/devkit/intellij.devkit.workspaceModel/k1/src/metaModel/ObjTypeConverter.kt
new file mode 100644
index 000000000000..2bf98190cbea
--- /dev/null
+++ b/plugins/devkit/intellij.devkit.workspaceModel/k1/src/metaModel/ObjTypeConverter.kt
@@ -0,0 +1,39 @@
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.devkit.workspaceModel.k1.metaModel
+
+import com.intellij.psi.CommonClassNames
+import com.intellij.workspaceModel.codegen.deft.meta.ValueType
+import org.jetbrains.kotlin.name.FqName
+
+internal object ObjTypeConverter {
+ private val javaPrimitiveTypes = mapOf(
+ CommonClassNames.JAVA_LANG_BOOLEAN to ValueType.Boolean,
+ CommonClassNames.JAVA_LANG_BYTE to ValueType.Byte,
+ CommonClassNames.JAVA_LANG_SHORT to ValueType.Short,
+ CommonClassNames.JAVA_LANG_INTEGER to ValueType.Int,
+ CommonClassNames.JAVA_LANG_LONG to ValueType.Long,
+ CommonClassNames.JAVA_LANG_FLOAT to ValueType.Float,
+ CommonClassNames.JAVA_LANG_DOUBLE to ValueType.Double,
+ CommonClassNames.JAVA_LANG_CHARACTER to ValueType.Char,
+ CommonClassNames.JAVA_LANG_STRING to ValueType.String,
+ )
+
+ private val kotlinPrimitiveTypes = mapOf(
+ "kotlin.Boolean" to ValueType.Boolean,
+ "kotlin.Byte" to ValueType.Byte,
+ "kotlin.Short" to ValueType.Short,
+ "kotlin.Int" to ValueType.Int,
+ "kotlin.Long" to ValueType.Long,
+ "kotlin.Float" to ValueType.Float,
+ "kotlin.Double" to ValueType.Double,
+ "kotlin.UByte" to ValueType.UByte,
+ "kotlin.UShort" to ValueType.UShort,
+ "kotlin.UInt" to ValueType.UInt,
+ "kotlin.ULong" to ValueType.ULong,
+ "kotlin.Char" to ValueType.Char,
+ "kotlin.String" to ValueType.String,
+ )
+
+ operator fun get(fqName: FqName): ValueType.Primitive<*>? =
+ kotlinPrimitiveTypes[fqName.asString()] ?: javaPrimitiveTypes[fqName.asString()]
+}
diff --git a/plugins/devkit/intellij.devkit.workspaceModel/k1/src/metaModel/Utils.kt b/plugins/devkit/intellij.devkit.workspaceModel/k1/src/metaModel/Utils.kt
new file mode 100644
index 000000000000..fd9369918821
--- /dev/null
+++ b/plugins/devkit/intellij.devkit.workspaceModel/k1/src/metaModel/Utils.kt
@@ -0,0 +1,29 @@
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.devkit.workspaceModel.k1.metaModel
+
+import com.intellij.devkit.workspaceModel.metaModel.WorkspaceModelDefaults
+import com.intellij.workspaceModel.codegen.deft.meta.CompiledObjModule
+import org.jetbrains.kotlin.descriptors.ModuleDescriptor
+import org.jetbrains.kotlin.descriptors.annotations.Annotated
+import org.jetbrains.kotlin.name.FqName
+
+internal fun Annotated.isAnnotatedBy(fqName: FqName) = annotations.hasAnnotation(fqName)
+
+internal val FqName.isCollection: Boolean
+ get() = isList || isSet || isMap
+
+internal val FqName.isList: Boolean
+ get() = this == WorkspaceModelDefaults.LIST_INTERFACE.fqName
+
+internal val FqName.isSet: Boolean
+ get() = this == WorkspaceModelDefaults.SET_INTERFACE.fqName
+
+internal val FqName.isMap: Boolean
+ get() = this == WorkspaceModelDefaults.MAP_INTERFACE.fqName
+
+internal data class CompiledObjModuleAndK1Module(
+ val compiledObjModule: CompiledObjModule,
+ val kotlinModule: ModuleDescriptor,
+)
+
+internal infix fun CompiledObjModule.and(moduleDescriptor: ModuleDescriptor) = CompiledObjModuleAndK1Module(this, moduleDescriptor)
diff --git a/plugins/devkit/intellij.devkit.workspaceModel/src/metaModel/impl/WorkspaceMetaModelProviderImpl.kt b/plugins/devkit/intellij.devkit.workspaceModel/k1/src/metaModel/WorkspaceMetaModelBuilder.kt
similarity index 59%
rename from plugins/devkit/intellij.devkit.workspaceModel/src/metaModel/impl/WorkspaceMetaModelProviderImpl.kt
rename to plugins/devkit/intellij.devkit.workspaceModel/k1/src/metaModel/WorkspaceMetaModelBuilder.kt
index 0b19b0872380..d3383c572829 100644
--- a/plugins/devkit/intellij.devkit.workspaceModel/src/metaModel/impl/WorkspaceMetaModelProviderImpl.kt
+++ b/plugins/devkit/intellij.devkit.workspaceModel/k1/src/metaModel/WorkspaceMetaModelBuilder.kt
@@ -1,24 +1,17 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
-package com.intellij.devkit.workspaceModel.metaModel.impl
+package com.intellij.devkit.workspaceModel.k1.metaModel
import com.intellij.devkit.workspaceModel.metaModel.IncorrectObjInterfaceException
-import com.intellij.devkit.workspaceModel.metaModel.WorkspaceMetaModelProvider
-import com.intellij.devkit.workspaceModel.metaModel.impl.extensions.*
+import com.intellij.devkit.workspaceModel.metaModel.WorkspaceModelDefaults
+import com.intellij.devkit.workspaceModel.metaModel.impl.*
+import com.intellij.devkit.workspaceModel.metaModel.unsupportedType
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.Project
-import com.intellij.platform.workspace.storage.*
-import com.intellij.platform.workspace.storage.annotations.Abstract
-import com.intellij.platform.workspace.storage.annotations.Child
-import com.intellij.platform.workspace.storage.annotations.Default
-import com.intellij.platform.workspace.storage.annotations.Open
-import com.intellij.platform.workspace.storage.url.VirtualFileUrl
-import com.intellij.psi.CommonClassNames
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.workspaceModel.codegen.deft.meta.*
import org.jetbrains.kotlin.caches.resolve.KotlinCacheService
import org.jetbrains.kotlin.descriptors.*
-import org.jetbrains.kotlin.descriptors.annotations.Annotated
import org.jetbrains.kotlin.idea.base.projectStructure.productionSourceInfo
import org.jetbrains.kotlin.idea.base.projectStructure.testSourceInfo
import org.jetbrains.kotlin.name.FqName
@@ -26,42 +19,46 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtensionProperty
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
+import org.jetbrains.kotlin.resolve.source.getPsi
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.typeUtil.makeNotNullable
import java.util.concurrent.ConcurrentHashMap
-internal class WorkspaceMetaModelProviderImpl(
+internal class WorkspaceMetaModelBuilder(
private val processAbstractTypes: Boolean,
- project: Project
-): WorkspaceMetaModelProvider {
- private val objModuleByName = ConcurrentHashMap>()
+ project: Project,
+) {
+ private val objModuleByName = ConcurrentHashMap()
private val javaPsiFacade = JavaPsiFacade.getInstance(project)
private val allScope = GlobalSearchScope.allScope(project)
- override fun getObjModule(packageName: String, module: Module, isTestSourceFolder: Boolean): CompiledObjModule {
- val sourceInfo = if (!isTestSourceFolder) {
+ fun getObjModule(packageName: String, module: Module, isTestSourceFolder: Boolean): CompiledObjModule {
+ val sourceInfo = if (!isTestSourceFolder) {
module.productionSourceInfo
- } else {
+ }
+ else {
module.testSourceInfo
} ?: error("No production sources in ${module.name}")
- val resolutionFacade = KotlinCacheService.getInstance(module.project).getResolutionFacadeByModuleInfo(sourceInfo, sourceInfo.platform)!!
+ val resolutionFacade = KotlinCacheService.getInstance(module.project).getResolutionFacadeByModuleInfo(sourceInfo, sourceInfo.platform)
val moduleDescriptor = resolutionFacade.moduleDescriptor
return getObjModule(packageName, moduleDescriptor)
}
private fun getObjModule(packageName: String, moduleDescriptor: ModuleDescriptor): CompiledObjModule {
val cached = objModuleByName[packageName]
- if (cached != null && cached.second == moduleDescriptor) return cached.first
+ if (cached != null && cached.kotlinModule == moduleDescriptor) return cached.compiledObjModule
val packageViewDescriptor = moduleDescriptor.getPackage(FqName(packageName))
val objModuleStub = createObjModuleStub(moduleDescriptor, packageViewDescriptor, packageName)
- val result = registerObjModuleContent(packageViewDescriptor, objModuleStub, moduleDescriptor)
- objModuleByName[packageName] = result to moduleDescriptor
- return result
+ val compiledObjModule = registerObjModuleContent(packageViewDescriptor, objModuleStub, moduleDescriptor)
+ objModuleByName[packageName] = compiledObjModule and moduleDescriptor
+ return compiledObjModule
}
- private fun registerObjModuleContent(packageViewDescriptor: PackageViewDescriptor,
- objModuleStub: ObjModuleStub,
- moduleDescriptor: ModuleDescriptor): CompiledObjModule {
+ private fun registerObjModuleContent(
+ packageViewDescriptor: PackageViewDescriptor,
+ objModuleStub: ObjModuleStub,
+ moduleDescriptor: ModuleDescriptor,
+ ): CompiledObjModule {
val extensionProperties = packageViewDescriptor.fragments.flatMap { fragment ->
fragment.getMemberScope().getContributedDescriptors(DescriptorKindFilter.VARIABLES)
.filterIsInstance()
@@ -69,14 +66,16 @@ internal class WorkspaceMetaModelProviderImpl(
.filter { it.module == moduleDescriptor }
}
val externalProperties = extensionProperties.mapNotNull { property ->
- val receiver = property.extensionReceiverParameter?.value?.type?.constructor?.declarationDescriptor as? ClassDescriptor
- receiver?.let { property to receiver }
- }.filter { it.second.isEntityInterface && !it.second.isEntityBuilderInterface }
+ val receiver = property.extensionReceiverParameter?.value?.type?.constructor?.declarationDescriptor as? ClassDescriptor
+ receiver?.let { property to receiver }
+ }.filter { it.second.isEntityInterface && !it.second.isEntityBuilderInterface }
return objModuleStub.registerContent(externalProperties)
}
- private fun createObjModuleStub(moduleDescriptor: ModuleDescriptor,
- packageViewDescriptor: PackageViewDescriptor, packageName: String): ObjModuleStub {
+ private fun createObjModuleStub(
+ moduleDescriptor: ModuleDescriptor,
+ packageViewDescriptor: PackageViewDescriptor, packageName: String,
+ ): ObjModuleStub {
val entityInterfaces = packageViewDescriptor.fragments.flatMap { fragment ->
fragment.getMemberScope().getContributedDescriptors(DescriptorKindFilter.CLASSIFIERS)
.filterIsInstance()
@@ -99,10 +98,12 @@ internal class WorkspaceMetaModelProviderImpl(
return objModule.types.find { it.name == entityInterfaceName } ?: error("Cannot find $entityInterfaceName in $objModule")
}
- private inner class ObjModuleStub(val module: CompiledObjModuleImpl,
- val types: List>>,
- val moduleAbstractTypes: List,
- val moduleDescriptor: ModuleDescriptor) {
+ private inner class ObjModuleStub(
+ val module: CompiledObjModuleImpl,
+ val types: List>>,
+ val moduleAbstractTypes: List,
+ val moduleDescriptor: ModuleDescriptor,
+ ) {
fun registerContent(extProperties: List>): CompiledObjModule {
var extPropertyId = 0
for ((classDescriptor, objType) in types) {
@@ -111,10 +112,11 @@ internal class WorkspaceMetaModelProviderImpl(
.filter { it.kind.isReal }
.filter { it.module == moduleDescriptor }
for ((propertyId, property) in properties.withIndex()) {
- val kind = computeKind(property)
+ val kind = property.computeKind()
if (kind !is ObjProperty.ValueKind.Computable ||
// We can't simply skip all `Computable` because some of them are SymbolicIds
- property.overriddenDescriptors.isNotEmpty()) {
+ property.overriddenDescriptors.isNotEmpty()
+ ) {
objType.addField(createOwnProperty(property, propertyId, objType))
}
}
@@ -151,14 +153,16 @@ internal class WorkspaceMetaModelProviderImpl(
}
- private fun createOwnProperty(property: PropertyDescriptor, propertyId: Int,
- receiver: ObjClassImpl): OwnProperty {
- val valueType = convertType(property.type, hashMapOf(), property.isAnnotatedBy (StandardNames.CHILD_ANNOTATION))
+ private fun createOwnProperty(
+ property: PropertyDescriptor, propertyId: Int,
+ receiver: ObjClassImpl,
+ ): OwnProperty {
+ val valueType = convertType(property.type, hashMapOf(), property.isAnnotatedBy(WorkspaceModelDefaults.CHILD_ANNOTATION.fqName))
return OwnPropertyImpl(
- receiver, property.name.identifier, valueType, computeKind(property),
- property.isAnnotatedBy(StandardNames.OPEN_ANNOTATION), property.isVar,
+ receiver, property.name.identifier, valueType, property.computeKind(),
+ property.isAnnotatedBy(WorkspaceModelDefaults.OPEN_ANNOTATION.fqName), property.isVar,
false, false, propertyId,
- property.isAnnotatedBy(StandardNames.EQUALS_BY_ANNOTATION), property.source
+ property.isAnnotatedBy(WorkspaceModelDefaults.EQUALS_BY_ANNOTATION.fqName), property.source.getPsi()
)
}
@@ -170,13 +174,17 @@ internal class WorkspaceMetaModelProviderImpl(
?: emptyList()
return ExtPropertyImpl(
findObjClass(receiverClass), extProperty.name.identifier, valueType,
- computeKind(extProperty), extProperty.isAnnotatedBy(StandardNames.OPEN_ANNOTATION),
- extProperty.isVar, false, module, extPropertyId, propertyAnnotations, extProperty.source
+ extProperty.computeKind(), extProperty.isAnnotatedBy(WorkspaceModelDefaults.OPEN_ANNOTATION.fqName),
+ extProperty.isVar, false, module, extPropertyId, propertyAnnotations, extProperty.source.getPsi()
)
}
- private fun convertType(type: KotlinType, knownTypes: MutableMap>, hasChildAnnotation: Boolean): ValueType<*> {
+ private fun convertType(
+ type: KotlinType,
+ knownTypes: MutableMap>,
+ hasChildAnnotation: Boolean,
+ ): ValueType<*> {
if (type.isMarkedNullable) {
return ValueType.Optional(convertType(type.makeNotNullable(), knownTypes, hasChildAnnotation))
}
@@ -185,7 +193,7 @@ internal class WorkspaceMetaModelProviderImpl(
if (descriptor is ClassDescriptor) {
val fqName = descriptor.fqNameSafe
- val primitive = ObjTypeConverter.findPrimitive(fqName)
+ val primitive = ObjTypeConverter[fqName]
if (primitive != null) return primitive
if (fqName.isCollection) {
@@ -199,7 +207,8 @@ internal class WorkspaceMetaModelProviderImpl(
}
if (descriptor.isEntityInterface) {
- return ValueType.ObjRef(type.isAnnotatedBy(StandardNames.CHILD_ANNOTATION) || hasChildAnnotation, //todo leave only one target for @Child annotation
+ return ValueType.ObjRef(type.isAnnotatedBy(
+ WorkspaceModelDefaults.CHILD_ANNOTATION.fqName) || hasChildAnnotation, //todo leave only one target for @Child annotation
findObjClass(descriptor))
}
return descriptor.toValueType(knownTypes, processAbstractTypes)
@@ -208,7 +217,10 @@ internal class WorkspaceMetaModelProviderImpl(
return unsupportedType(type.toString())
}
- private fun ClassDescriptor.toValueType(knownTypes: MutableMap>, processAbstractTypes: Boolean): ValueType.JvmClass<*> {
+ private fun ClassDescriptor.toValueType(
+ knownTypes: MutableMap>,
+ processAbstractTypes: Boolean,
+ ): ValueType.JvmClass<*> {
val javaClassFqn = javaClassFqn
val superTypes = superTypesJavaFqns
@@ -243,16 +255,21 @@ internal class WorkspaceMetaModelProviderImpl(
}
}
- private fun createProperties(descriptor: ClassDescriptor, knownTypes: MutableMap>): List> {
+ private fun createProperties(
+ descriptor: ClassDescriptor,
+ knownTypes: MutableMap>,
+ ): List> {
return descriptor.unsubstitutedMemberScope.getContributedDescriptors(DescriptorKindFilter.VARIABLES)
.filterIsInstance()
+ .sortedBy { it.name.identifier }
.map { ValueType.ClassProperty(it.name.identifier, convertType(it.type, knownTypes, false)) }
}
private fun findObjClass(descriptor: ClassDescriptor): ObjClass<*> {
if (descriptor.packageOrDie.asString() == module.name) {
- return types.find { it.first.typeConstructor == descriptor.typeConstructor }?.second ?: error("Cannot find ${descriptor.fqNameSafe} in $module")
+ return types.find { it.first.typeConstructor == descriptor.typeConstructor }?.second ?: error(
+ "Cannot find ${descriptor.fqNameSafe} in $module")
}
return getObjClass(descriptor)
}
@@ -262,77 +279,3 @@ internal class WorkspaceMetaModelProviderImpl(
}
}
}
-
-
-internal fun Annotated.isAnnotatedBy(fqName: FqName) = annotations.hasAnnotation(fqName)
-
-
-private val FqName.isCollection: Boolean
- get() = isList || isSet || isMap
-
-private val FqName.isList: Boolean
- get() = this == StandardNames.LIST_INTERFACE
-
-private val FqName.isSet: Boolean
- get() = this == StandardNames.SET_INTERFACE
-
-private val FqName.isMap: Boolean
- get() = this == StandardNames.MAP_INTERFACE
-
-
-private object ObjTypeConverter {
- private val javaPrimitiveTypes = mapOf(
- CommonClassNames.JAVA_LANG_BOOLEAN to ValueType.Boolean,
- CommonClassNames.JAVA_LANG_BYTE to ValueType.Byte,
- CommonClassNames.JAVA_LANG_SHORT to ValueType.Short,
- CommonClassNames.JAVA_LANG_INTEGER to ValueType.Int,
- CommonClassNames.JAVA_LANG_LONG to ValueType.Long,
- CommonClassNames.JAVA_LANG_FLOAT to ValueType.Float,
- CommonClassNames.JAVA_LANG_DOUBLE to ValueType.Double,
- CommonClassNames.JAVA_LANG_CHARACTER to ValueType.Char,
- CommonClassNames.JAVA_LANG_STRING to ValueType.String,
- )
-
- private val kotlinPrimitiveTypes = mapOf(
- "kotlin.Boolean" to ValueType.Boolean,
- "kotlin.Byte" to ValueType.Byte,
- "kotlin.Short" to ValueType.Short,
- "kotlin.Int" to ValueType.Int,
- "kotlin.Long" to ValueType.Long,
- "kotlin.Float" to ValueType.Float,
- "kotlin.Double" to ValueType.Double,
- "kotlin.UByte" to ValueType.UByte,
- "kotlin.UShort" to ValueType.UShort,
- "kotlin.UInt" to ValueType.UInt,
- "kotlin.ULong" to ValueType.ULong,
- "kotlin.Char" to ValueType.Char,
- "kotlin.String" to ValueType.String,
- )
-
- fun findPrimitive(fqName: FqName): ValueType.Primitive<*>? =
- kotlinPrimitiveTypes[fqName.asString()] ?: javaPrimitiveTypes[fqName.asString()]
-}
-
-internal object StandardNames {
- val DEFAULT_ANNOTATION = FqName(Default::class.qualifiedName!!)
- val OPEN_ANNOTATION = FqName(Open::class.qualifiedName!!)
- val ABSTRACT_ANNOTATION = FqName(Abstract::class.qualifiedName!!)
- val CHILD_ANNOTATION = FqName(Child::class.qualifiedName!!)
- val EQUALS_BY_ANNOTATION = FqName(EqualsBy::class.qualifiedName!!)
- val LIST_INTERFACE = FqName(List::class.qualifiedName!!)
- val SET_INTERFACE = FqName(Set::class.qualifiedName!!)
- val MAP_INTERFACE = FqName(Map::class.qualifiedName!!)
-
- val WORKSPACE_ENTITY = FqName(WorkspaceEntity::class.qualifiedName!!)
- val ENTITY_SOURCE = FqName(EntitySource::class.qualifiedName!!)
- val VIRTUAL_FILE_URL = FqName(VirtualFileUrl::class.qualifiedName!!)
- val SYMBOLIC_ENTITY_ID = FqName(SymbolicEntityId::class.qualifiedName!!)
- val ENTITY_POINTER = FqName(EntityPointer::class.qualifiedName!!)
-}
-
-internal val standardTypes = setOf(Any::class.qualifiedName, CommonClassNames.JAVA_LANG_OBJECT, CommonClassNames.JAVA_LANG_ENUM)
-
-
-private fun unsupportedType(type: String?): ValueType<*> {
- throw IncorrectObjInterfaceException("Unsupported type '$type'")
-}
\ No newline at end of file
diff --git a/plugins/devkit/intellij.devkit.workspaceModel/k1/src/metaModel/WorkspaceMetaModelProviderImpl.kt b/plugins/devkit/intellij.devkit.workspaceModel/k1/src/metaModel/WorkspaceMetaModelProviderImpl.kt
new file mode 100644
index 000000000000..4653233062ba
--- /dev/null
+++ b/plugins/devkit/intellij.devkit.workspaceModel/k1/src/metaModel/WorkspaceMetaModelProviderImpl.kt
@@ -0,0 +1,25 @@
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.devkit.workspaceModel.k1.metaModel
+
+import com.intellij.devkit.workspaceModel.metaModel.WorkspaceMetaModelProvider
+import com.intellij.openapi.module.Module
+import com.intellij.workspaceModel.codegen.deft.meta.CompiledObjModule
+import org.jetbrains.kotlin.psi.KtClass
+
+internal class WorkspaceMetaModelProviderImpl : WorkspaceMetaModelProvider {
+
+ override fun loadObjModules(
+ ktClasses: HashMap,
+ module: Module,
+ processAbstractTypes: Boolean,
+ isTestSourceFolder: Boolean,
+ ): List {
+ val packages = ktClasses.values.mapTo(LinkedHashSet()) { it.containingKtFile.packageFqName.asString() }
+
+ val metaModelProvider = WorkspaceMetaModelBuilder(
+ processAbstractTypes = processAbstractTypes,
+ module.project
+ )
+ return packages.filter { it != "" }.map { metaModelProvider.getObjModule(it, module, isTestSourceFolder) }
+ }
+}
diff --git a/plugins/devkit/intellij.devkit.workspaceModel/k1/test/AllIntellijEntitiesGenerationTest.kt b/plugins/devkit/intellij.devkit.workspaceModel/k1/test/AllIntellijEntitiesGenerationTest.kt
new file mode 100644
index 000000000000..5ad671f82a30
--- /dev/null
+++ b/plugins/devkit/intellij.devkit.workspaceModel/k1/test/AllIntellijEntitiesGenerationTest.kt
@@ -0,0 +1,10 @@
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.devkit.workspaceModel.k1
+
+import com.intellij.devkit.workspaceModel.AbstractAllIntellijEntitiesGenerationTest
+import org.jetbrains.kotlin.idea.base.plugin.KotlinPluginMode
+
+class AllIntellijEntitiesGenerationTest : AbstractAllIntellijEntitiesGenerationTest() {
+ override val pluginMode: KotlinPluginMode
+ get() = KotlinPluginMode.K1
+}
diff --git a/plugins/devkit/intellij.devkit.workspaceModel/k1/test/EntityCodeGenTest.kt b/plugins/devkit/intellij.devkit.workspaceModel/k1/test/EntityCodeGenTest.kt
new file mode 100644
index 000000000000..d99212ecf75c
--- /dev/null
+++ b/plugins/devkit/intellij.devkit.workspaceModel/k1/test/EntityCodeGenTest.kt
@@ -0,0 +1,10 @@
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.devkit.workspaceModel.k1
+
+import com.intellij.devkit.workspaceModel.AbstractEntityCodeGenTest
+import org.jetbrains.kotlin.idea.base.plugin.KotlinPluginMode
+
+class EntityCodeGenTest : AbstractEntityCodeGenTest() {
+ override val pluginMode: KotlinPluginMode
+ get() = KotlinPluginMode.K1
+}
diff --git a/plugins/devkit/intellij.devkit.workspaceModel/k2/intellij.devkit.workspaceModel.k2.iml b/plugins/devkit/intellij.devkit.workspaceModel/k2/intellij.devkit.workspaceModel.k2.iml
new file mode 100644
index 000000000000..bbe5544218e0
--- /dev/null
+++ b/plugins/devkit/intellij.devkit.workspaceModel/k2/intellij.devkit.workspaceModel.k2.iml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/plugins/devkit/intellij.devkit.workspaceModel/k2/resources/intellij.devkit.workspaceModel.k2.xml b/plugins/devkit/intellij.devkit.workspaceModel/k2/resources/intellij.devkit.workspaceModel.k2.xml
new file mode 100644
index 000000000000..655a8881a737
--- /dev/null
+++ b/plugins/devkit/intellij.devkit.workspaceModel/k2/resources/intellij.devkit.workspaceModel.k2.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/plugins/devkit/intellij.devkit.workspaceModel/k2/src/metaModel/KaExtensions.kt b/plugins/devkit/intellij.devkit.workspaceModel/k2/src/metaModel/KaExtensions.kt
new file mode 100644
index 000000000000..c520760313cf
--- /dev/null
+++ b/plugins/devkit/intellij.devkit.workspaceModel/k2/src/metaModel/KaExtensions.kt
@@ -0,0 +1,143 @@
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.devkit.workspaceModel.k2.metaModel
+
+import com.intellij.devkit.workspaceModel.metaModel.WorkspaceModelDefaults
+import com.intellij.devkit.workspaceModel.metaModel.impl.CompiledObjModuleImpl
+import com.intellij.devkit.workspaceModel.metaModel.impl.ObjAnnotationImpl
+import com.intellij.devkit.workspaceModel.metaModel.impl.ObjClassImpl
+import com.intellij.psi.CommonClassNames
+import com.intellij.psi.JavaPsiFacade
+import com.intellij.psi.PsiClass
+import com.intellij.psi.search.GlobalSearchScope
+import com.intellij.psi.search.searches.ClassInheritorsSearch
+import com.intellij.workspaceModel.codegen.deft.meta.Obj
+import com.intellij.workspaceModel.codegen.deft.meta.ObjClass
+import com.intellij.workspaceModel.codegen.deft.meta.ObjProperty
+import org.jetbrains.kotlin.analysis.api.KaSession
+import org.jetbrains.kotlin.analysis.api.annotations.KaAnnotated
+import org.jetbrains.kotlin.analysis.api.symbols.*
+import org.jetbrains.kotlin.analysis.api.types.symbol
+import org.jetbrains.kotlin.idea.stubindex.KotlinFullClassNameIndex
+import org.jetbrains.kotlin.name.ClassId
+import org.jetbrains.kotlin.name.FqName
+import org.jetbrains.kotlin.psi.KtClass
+import org.jetbrains.kotlin.psi.KtClassOrObject
+import org.jetbrains.kotlin.psi.KtDeclarationWithBody
+
+internal fun KaAnnotated.isAnnotatedBy(classId: ClassId) = annotations.contains(classId)
+
+private val moduleAbstractTypesClassIds: List = listOf(
+ WorkspaceModelDefaults.ENTITY_SOURCE.classId,
+ WorkspaceModelDefaults.SYMBOLIC_ENTITY_ID.classId,
+)
+
+internal fun KaSession.inheritors(
+ classSymbol: KaClassSymbol, javaPsiFacade: JavaPsiFacade, scope: GlobalSearchScope,
+): List {
+ // can be rewritten using org.jetbrains.kotlin.idea.searching.inheritors.KotlinSearchUtilKt.findAllInheritors
+ val psiClass = javaPsiFacade.findClass(classSymbol.javaClassFqn, scope) ?: return emptyList()
+ return ClassInheritorsSearch.search(psiClass, scope, true, true, false)
+ .filterNot { it.isAnonymous }
+ .sortedBy { it.qualifiedName } // Sorting is needed for consistency in case of regeneration
+ .mapNotNull {
+ KotlinFullClassNameIndex[it.qualifiedName!!, it.project, scope].firstOrNull()
+ }
+}
+
+private val standardTypes = setOf(Any::class.qualifiedName, CommonClassNames.JAVA_LANG_OBJECT, CommonClassNames.JAVA_LANG_ENUM)
+
+internal fun KaSession.superTypesJavaFqns(
+ classSymbol: KaClassSymbol, javaPsiFacade: JavaPsiFacade, scope: GlobalSearchScope,
+): List {
+ // FIXME: There's definitely smth wrong with AA
+ val kaSuperTypes1 = classSymbol.defaultType.allSupertypes
+ .mapNotNull { it.expandedSymbol?.javaClassFqn }
+ .toList()
+ val kaSuperTypes2 = classSymbol.superTypes.flatMap { listOf(it) + it.allSupertypes }
+ .mapNotNull { it.expandedSymbol?.javaClassFqn }
+ .toList()
+ val psiSuperTypes = javaPsiFacade.findClass(classSymbol.javaClassFqn, scope)?.supers?.mapNotNull { it.qualifiedName }
+ ?: emptyList()
+ val mergedSuperTypes = (psiSuperTypes + kaSuperTypes1 + kaSuperTypes2).distinct()
+ return mergedSuperTypes.withoutStandardTypes().sorted()
+}
+
+// TODO: merge with above?
+internal val KaClassSymbol.superTypesJavaFqns: List
+ get() = superTypes.mapNotNull { it.symbol?.javaClassFqn }.withoutStandardTypes().sorted()
+
+private val PsiClass.isAnonymous: Boolean
+ get() = qualifiedName == null
+
+internal val KaClassLikeSymbol.javaClassFqn: String
+ get() {
+ val nameWithoutPackage = classId?.asSingleFqName()?.asString()
+ ?.substringAfter("$packageName.")
+ ?.replace(".", "$")
+ return "$packageName.$nameWithoutPackage"
+ }
+
+internal val KaClassLikeSymbol.packageName: String
+ get() = packageOrDie.asString()
+
+internal val KaClassLikeSymbol.packageOrDie: FqName
+ get() = classId?.packageFqName ?: error("$name has no package")
+
+internal fun KaSession.getPackageSymbol(classSymbol: KaClassSymbol): KaPackageSymbol? =
+ classSymbol.classId?.packageFqName?.let { findPackage(it) }
+
+private val blobClasses: List = arrayListOf(
+ WorkspaceModelDefaults.VIRTUAL_FILE_URL.classId,
+ WorkspaceModelDefaults.ENTITY_POINTER.classId,
+)
+
+internal fun KaSession.isBlob(classSymbol: KaClassSymbol): Boolean {
+ val classId = classSymbol.classId ?: return false
+ return blobClasses.any { blobClass -> classSymbol.defaultType.isSubtypeOf(blobClass) } ||
+ classId in moduleAbstractTypesClassIds ||
+ classId.packageFqName.asString().startsWith("java.") ||
+ classId.packageFqName.asString().startsWith("kotlin.")
+}
+
+private fun Iterable.withoutStandardTypes(): List {
+ return filterNot { standardTypes.contains(it) }
+}
+
+internal fun KaSession.moduleAbstractTypes(): List {
+ return moduleAbstractTypesClassIds.mapNotNull { classId -> findClass(classId) }
+}
+
+internal fun KaSession.isEntityInterface(classSymbol: KaClassSymbol): Boolean =
+ classSymbol.classKind == KaClassKind.INTERFACE && classSymbol.defaultType.isSubtypeOf(WorkspaceModelDefaults.WORKSPACE_ENTITY.classId)
+
+internal fun KaSession.isEntityBuilderInterface(classSymbol: KaClassSymbol): Boolean =
+ isEntityInterface(classSymbol) && classSymbol.name?.identifier == "Builder" // TODO: improve
+
+internal fun KaSession.computeKind(property: KaPropertySymbol): ObjProperty.ValueKind {
+ val getter = property.getter ?: return ObjProperty.ValueKind.Plain
+ if (!getter.hasBody) return ObjProperty.ValueKind.Plain
+ val declaration = getter.psi as? KtDeclarationWithBody ?: return ObjProperty.ValueKind.Plain
+ val getterText = (declaration.bodyExpression ?: declaration.bodyBlockExpression)?.text
+ return when {
+ getterText == null -> ObjProperty.ValueKind.Plain
+ getter.isAnnotatedBy(WorkspaceModelDefaults.DEFAULT_ANNOTATION.classId) -> ObjProperty.ValueKind.WithDefault(getterText)
+ else -> ObjProperty.ValueKind.Computable(getterText)
+ }
+}
+
+internal fun KaSession.createObjTypeStub(symbol: KaClassSymbol, module: CompiledObjModuleImpl): ObjClassImpl {
+ val openness = when {
+ symbol.isAnnotatedBy(WorkspaceModelDefaults.ABSTRACT_ANNOTATION.classId) -> ObjClass.Openness.abstract
+ symbol.isAnnotatedBy(WorkspaceModelDefaults.OPEN_ANNOTATION.classId) -> ObjClass.Openness.open
+ else -> ObjClass.Openness.final
+ }
+
+ val propertyAnnotations = symbol.annotations
+ .mapNotNull { it.classId?.asSingleFqName() }
+ .map { ObjAnnotationImpl(it.asString(), it.pathSegments().map { segment -> segment.asString() }) }
+
+ return ObjClassImpl(module, symbol.name?.identifier!!, openness, symbol.sourcePsi(), propertyAnnotations)
+}
+
+internal fun KaSession.isChild(kaType: KaAnnotated) =
+ kaType.isAnnotatedBy(WorkspaceModelDefaults.CHILD_ANNOTATION.classId)
diff --git a/plugins/devkit/intellij.devkit.workspaceModel/k2/src/metaModel/ObjTypeConverter.kt b/plugins/devkit/intellij.devkit.workspaceModel/k2/src/metaModel/ObjTypeConverter.kt
new file mode 100644
index 000000000000..1d4e79ab2eb0
--- /dev/null
+++ b/plugins/devkit/intellij.devkit.workspaceModel/k2/src/metaModel/ObjTypeConverter.kt
@@ -0,0 +1,27 @@
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.devkit.workspaceModel.k2.metaModel
+
+import com.intellij.workspaceModel.codegen.deft.meta.ValueType
+import org.jetbrains.kotlin.analysis.api.components.DefaultTypeClassIds
+import org.jetbrains.kotlin.builtins.StandardNames
+import org.jetbrains.kotlin.name.ClassId
+
+internal object ObjTypeConverter {
+ private val kotlinDefaultTypeToValueType = mapOf(
+ DefaultTypeClassIds.BOOLEAN to ValueType.Boolean,
+ DefaultTypeClassIds.BYTE to ValueType.Byte,
+ DefaultTypeClassIds.SHORT to ValueType.Short,
+ DefaultTypeClassIds.INT to ValueType.Int,
+ DefaultTypeClassIds.LONG to ValueType.Long,
+ DefaultTypeClassIds.FLOAT to ValueType.Float,
+ DefaultTypeClassIds.DOUBLE to ValueType.Double,
+ DefaultTypeClassIds.CHAR to ValueType.Char,
+ DefaultTypeClassIds.STRING to ValueType.String,
+ StandardNames.FqNames.uByte to ValueType.UByte,
+ StandardNames.FqNames.uShort to ValueType.UShort,
+ StandardNames.FqNames.uInt to ValueType.UInt,
+ StandardNames.FqNames.uLong to ValueType.ULong,
+ )
+
+ operator fun get(classId: ClassId?): ValueType.Primitive<*>? = classId?.let { kotlinDefaultTypeToValueType[it] }
+}
diff --git a/plugins/devkit/intellij.devkit.workspaceModel/k2/src/metaModel/Utils.kt b/plugins/devkit/intellij.devkit.workspaceModel/k2/src/metaModel/Utils.kt
new file mode 100644
index 000000000000..bcef8639d0a3
--- /dev/null
+++ b/plugins/devkit/intellij.devkit.workspaceModel/k2/src/metaModel/Utils.kt
@@ -0,0 +1,9 @@
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.devkit.workspaceModel.k2.metaModel
+
+import com.intellij.workspaceModel.codegen.deft.meta.CompiledObjModule
+import org.jetbrains.kotlin.analysis.api.projectStructure.KaModule
+
+internal data class CompiledObjModuleAndK2Module(val compiledObjModule: CompiledObjModule, val kotlinModule: KaModule)
+
+internal infix fun CompiledObjModule.and(kaModule: KaModule) = CompiledObjModuleAndK2Module(this, kaModule)
diff --git a/plugins/devkit/intellij.devkit.workspaceModel/k2/src/metaModel/WorkspaceMetaModelBuilder.kt b/plugins/devkit/intellij.devkit.workspaceModel/k2/src/metaModel/WorkspaceMetaModelBuilder.kt
new file mode 100644
index 000000000000..6746ef098b93
--- /dev/null
+++ b/plugins/devkit/intellij.devkit.workspaceModel/k2/src/metaModel/WorkspaceMetaModelBuilder.kt
@@ -0,0 +1,311 @@
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.devkit.workspaceModel.k2.metaModel
+
+import com.intellij.devkit.workspaceModel.metaModel.IncorrectObjInterfaceException
+import com.intellij.devkit.workspaceModel.metaModel.WorkspaceModelDefaults
+import com.intellij.devkit.workspaceModel.metaModel.impl.*
+import com.intellij.devkit.workspaceModel.metaModel.unsupportedType
+import com.intellij.openapi.project.Project
+import com.intellij.psi.JavaPsiFacade
+import com.intellij.psi.search.GlobalSearchScope
+import com.intellij.util.concurrency.annotations.RequiresReadLock
+import com.intellij.workspaceModel.codegen.deft.meta.*
+import org.jetbrains.kotlin.analysis.api.KaSession
+import org.jetbrains.kotlin.analysis.api.analyze
+import org.jetbrains.kotlin.analysis.api.projectStructure.KaModule
+import org.jetbrains.kotlin.analysis.api.symbols.*
+import org.jetbrains.kotlin.analysis.api.types.KaClassType
+import org.jetbrains.kotlin.analysis.api.types.KaType
+import org.jetbrains.kotlin.analysis.api.types.KaTypeNullability
+import org.jetbrains.kotlin.idea.stubindex.KotlinExactPackagesIndex
+import org.jetbrains.kotlin.name.StandardClassIds
+import java.util.concurrent.ConcurrentHashMap
+
+internal class WorkspaceMetaModelBuilder(
+ private val processAbstractTypes: Boolean,
+ private val project: Project,
+) {
+ private val objModuleByName = ConcurrentHashMap()
+ private val javaPsiFacade = JavaPsiFacade.getInstance(project)
+ private val allProjectScope = GlobalSearchScope.allScope(project)
+
+ @RequiresReadLock
+ fun getObjModule(
+ packageName: String,
+ kaModule: KaModule,
+ ): CompiledObjModule = analyze(kaModule) {
+ val ktFile = KotlinExactPackagesIndex.get(packageName, project, kaModule.contentScope).firstOrNull()
+ ?: error("Cannot find any files with package $packageName in module ${kaModule}")
+ val packageSymbol = findPackage(ktFile.packageFqName) ?: error("Could not find package $packageName in module ${kaModule}")
+ getObjModule(packageName, packageSymbol, kaModule)
+ }
+
+ private fun KaSession.getObjModule(
+ packageName: String,
+ packageSymbol: KaPackageSymbol,
+ kaModule: KaModule,
+ ): CompiledObjModule {
+ val cached = objModuleByName[packageName]
+ if (cached != null && cached.kotlinModule == kaModule) return cached.compiledObjModule
+ val objModuleStub = createObjModuleStub(packageName, packageSymbol, kaModule)
+ val compiledObjModule = registerObjModuleContent(packageSymbol, objModuleStub, kaModule)
+ objModuleByName[packageName] = compiledObjModule and kaModule
+ return compiledObjModule
+ }
+
+ private fun KaSession.registerObjModuleContent(
+ packageSymbol: KaPackageSymbol,
+ objModuleStub: ObjModuleStub,
+ kaModule: KaModule,
+ ): CompiledObjModule {
+ val extensionProperties = packageSymbol.packageScope.callables
+ .filterIsInstance()
+ .filter { it.isExtension }
+ .filter { it.containingModule == kaModule }
+ .mapNotNull { propertySymbol ->
+ propertySymbol.receiverType?.expandedSymbol
+ ?.takeIf { receiverClassSymbol -> isEntityInterface(receiverClassSymbol) && !isEntityBuilderInterface(receiverClassSymbol) }
+ ?.let { propertySymbol to it }
+ }
+ return objModuleStub.registerContent(this@registerObjModuleContent, extensionProperties.toList())
+ }
+
+ private fun KaSession.createObjModuleStub(
+ packageName: String,
+ packageSymbol: KaPackageSymbol,
+ kaModule: KaModule,
+ ): ObjModuleStub {
+ val module = CompiledObjModuleImpl(packageName)
+
+ val types = packageSymbol.packageScope.declarations
+ .filterIsInstance()
+ .filter(::isEntityInterface)
+ .filter { it.containingModule == kaModule }
+ .sortedBy { it.name }
+ .map { it to createObjTypeStub(it, module) }
+ .toList()
+
+ val moduleAbstractTypes = moduleAbstractTypes()
+ return ObjModuleStub(module, types, moduleAbstractTypes, kaModule)
+ }
+
+ private fun KaSession.getObjClass(entityInterface: KaClassSymbol): ObjClass<*> {
+ val containingPackage = getPackageSymbol(entityInterface) ?: error("Cannot find package for ${entityInterface.name ?: entityInterface}")
+ val objModule = getObjModule(entityInterface.packageName, containingPackage, entityInterface.containingModule)
+ val entityInterfaceName = entityInterface.name?.identifier ?: error("Too many errors")
+ return objModule.types.find { it.name == entityInterfaceName } ?: error("Cannot find $entityInterfaceName in $objModule")
+ }
+
+ private inner class ObjModuleStub(
+ val module: CompiledObjModuleImpl,
+ // TODO: storing KaClassSymbol is unsafe, ObjModuleStub should inherit from KaLifetimeOwner
+ val types: List>>,
+ val moduleAbstractTypes: List,
+ val kaModule: KaModule,
+ ) /* : KaLifetimeOwner */ {
+ @RequiresReadLock
+ fun registerContent(kaSession: KaSession, extProperties: List>): CompiledObjModule =
+ with(kaSession) {
+ var extPropertyId = 0
+ for ((classSymbol, objType) in types) {
+ val properties = classSymbol.declaredMemberScope.callables
+ .filterIsInstance().toList()
+ .filter { it.containingModule == kaModule }
+
+ for ((propertyId, propertySymbol) in properties.withIndex()) {
+ val kind = computeKind(propertySymbol)
+ if (kind !is ObjProperty.ValueKind.Computable ||
+ // We can't simply skip all `Computable` because some of them are SymbolicIds
+ // propertySymbol.overriddenDescriptors.isNotEmpty()
+ propertySymbol.allOverriddenSymbols.any()
+ ) {
+ objType.addField(createOwnProperty(propertySymbol, propertyId, objType))
+ }
+ }
+
+ classSymbol.superTypes.forEach { superType ->
+ val superSymbol = superType.expandedSymbol
+ if (superSymbol is KaClassSymbol && isEntityInterface(superSymbol)) {
+ val superClass = findObjClass(superSymbol)
+ objType.addSuperType(superClass)
+ }
+ }
+
+ module.addType(objType)
+ }
+
+ registerModuleAbstractTypes()
+
+ for ((extProperty, receiverClass) in extProperties) {
+ module.addExtension(createExtProperty(extProperty, receiverClass, extPropertyId++))
+ }
+
+ module
+ }
+
+ private fun KaSession.registerModuleAbstractTypes() {
+ for (abstractTypeClassSymbol in moduleAbstractTypes) {
+ val javaClassFqn = abstractTypeClassSymbol.javaClassFqn
+ val superTypes = abstractTypeClassSymbol.superTypesJavaFqns
+ val blobType = ValueType.Blob(javaClassFqn, superTypes)
+
+ val inheritors = mutableListOf>()
+ val inheritorsKtClasses = inheritors(abstractTypeClassSymbol, javaPsiFacade, allProjectScope)
+ for (inheritor in inheritorsKtClasses) {
+ analyze(inheritor) {
+ val inheritorSymbol = inheritor.namedClassSymbol
+ if (inheritorSymbol != null && inheritorSymbol.packageName == module.name && inheritorSymbol.containingModule == kaModule) {
+ val inheritorValueType = classSymbolToValueType(inheritorSymbol, hashMapOf(javaClassFqn to blobType), true)
+ inheritors.add(inheritorValueType)
+ }
+ }
+ }
+
+ if (inheritors.isNotEmpty()) {
+ module.addAbstractType(ValueType.AbstractClass(javaClassFqn, superTypes, inheritors))
+ }
+ }
+ }
+
+ private fun KaSession.createOwnProperty(
+ property: KaPropertySymbol,
+ propertyId: Int,
+ receiver: ObjClassImpl,
+ ): OwnProperty {
+ val valueType = convertType(property.returnType, hashMapOf(), isChild(property))
+ return OwnPropertyImpl(receiver, property.name.identifier, valueType, computeKind(property),
+ property.isAnnotatedBy(WorkspaceModelDefaults.OPEN_ANNOTATION.classId), !property.isVal, false, false,
+ propertyId, property.isAnnotatedBy(WorkspaceModelDefaults.EQUALS_BY_ANNOTATION.classId),
+ property.sourcePsiSafe())
+ }
+
+ private fun KaSession.createExtProperty(
+ extProperty: KaPropertySymbol,
+ receiverClass: KaClassSymbol,
+ extPropertyId: Int,
+ ): ExtProperty<*, *> {
+ val valueType = convertType(extProperty.returnType, hashMapOf(), false)
+ val propertyAnnotations = extProperty.getter?.annotations?.mapNotNull { annotation ->
+ annotation.classId?.asSingleFqName()?.let {
+ ObjAnnotationImpl(it.asString(), it.pathSegments().map { segment -> segment.asString() })
+ }
+ } ?: emptyList()
+
+ return ExtPropertyImpl(findObjClass(receiverClass), extProperty.name.identifier, valueType, computeKind(extProperty),
+ extProperty.isAnnotatedBy(WorkspaceModelDefaults.OPEN_ANNOTATION.classId), !extProperty.isVal, false, module,
+ extPropertyId, propertyAnnotations, extProperty.sourcePsiSafe())
+ }
+
+ private fun KaSession.convertType(
+ type: KaType,
+ knownTypes: MutableMap>,
+ hasChildAnnotation: Boolean,
+ ): ValueType<*> {
+ if (type !is KaClassType) error("$type is not a class")
+ if (type.nullability == KaTypeNullability.NULLABLE) {
+ val nonNullableType = type.withNullability(KaTypeNullability.NON_NULLABLE)
+ return ValueType.Optional(convertType(nonNullableType, knownTypes, hasChildAnnotation))
+ }
+
+ val symbol = type.expandedSymbol
+ if (symbol is KaClassSymbol) {
+ val kotlinDefaultValueType = ObjTypeConverter[type.symbol.classId]
+ if (kotlinDefaultValueType != null) return kotlinDefaultValueType
+
+ if (type.isSubtypeOf(StandardClassIds.Collection)) {
+ val typeArguments = type.typeArguments.mapNotNull { it.type }
+ val genericType = convertType(typeArguments.first(), knownTypes, hasChildAnnotation)
+ when {
+ type.isSubtypeOf(StandardClassIds.List) -> return ValueType.List(genericType)
+ type.isSubtypeOf(StandardClassIds.Set) -> return ValueType.Set(genericType)
+ else -> return unsupportedType(type.toString())
+ }
+ }
+
+ if (type.isSubtypeOf(StandardClassIds.Map)) {
+ val (keyType, valueType) = type.typeArguments.mapNotNull { it.type }
+ val convertedKeyType = convertType(keyType, knownTypes, hasChildAnnotation)
+ val convertedValueType = convertType(valueType, knownTypes, hasChildAnnotation)
+ return ValueType.Map(convertedKeyType, convertedValueType)
+ }
+
+ if (symbol.classKind == KaClassKind.INTERFACE && type.isSubtypeOf(WorkspaceModelDefaults.WORKSPACE_ENTITY.classId)) {
+ // TODO IJPL-600 Replace @Child annotation to @Parent annotation and not for the type, but for the field
+ return ValueType.ObjRef(isChild(type) || hasChildAnnotation, findObjClass(symbol))
+ }
+
+ return classSymbolToValueType(symbol, knownTypes, processAbstractTypes)
+ }
+
+ return unsupportedType(type.toString())
+ }
+
+ private fun KaSession.findObjClass(classSymbol: KaClassSymbol): ObjClass<*> {
+ if (classSymbol.packageOrDie.asString() == module.name) {
+ return types.find { it.first.classId == classSymbol.classId }?.second
+ ?: error("Cannot find ${classSymbol.name} in $module")
+ }
+ return getObjClass(classSymbol)
+ }
+
+ private fun KaSession.classSymbolToValueType(
+ classSymbol: KaClassSymbol,
+ knownTypes: MutableMap>,
+ processAbstractTypes: Boolean,
+ ): ValueType.JvmClass<*> {
+ val javaClassFqn = classSymbol.javaClassFqn
+ val superTypesJavaFqns = superTypesJavaFqns(classSymbol, javaPsiFacade, allProjectScope)
+
+ val blobType = ValueType.Blob(javaClassFqn, superTypesJavaFqns)
+ if (knownTypes.containsKey(javaClassFqn) || isBlob(classSymbol)) {
+ return blobType
+ }
+
+ knownTypes[javaClassFqn] = blobType
+
+ return when {
+ classSymbol.classKind == KaClassKind.OBJECT -> ValueType.Object(javaClassFqn, superTypesJavaFqns,
+ createProperties(classSymbol, knownTypes).toList())
+ classSymbol.classKind == KaClassKind.ENUM_CLASS -> {
+ val enumEntries = classSymbol.staticMemberScope.callables.filterIsInstance().map { it.name.asString() }
+ ValueType.Enum(javaClassFqn, superTypesJavaFqns, enumEntries.toList(),
+ createProperties(classSymbol, knownTypes).asSequence().withoutEnumFields().toList())
+ }
+ classSymbol.modality == KaSymbolModality.SEALED && classSymbol is KaNamedClassSymbol -> {
+ val subclasses = classSymbol.sealedClassInheritors
+ .map { convertType(it.defaultType, knownTypes, false) as ValueType.JvmClass<*> }
+ ValueType.AbstractClass(javaClassFqn, superTypesJavaFqns, subclasses)
+ }
+ classSymbol.classKind == KaClassKind.INTERFACE || classSymbol.modality == KaSymbolModality.ABSTRACT -> {
+ if (!processAbstractTypes) {
+ throw IncorrectObjInterfaceException("$javaClassFqn is abstract type. Abstract types are not supported in generator")
+ }
+ val inheritors = inheritors(classSymbol, javaPsiFacade, allProjectScope)
+ .mapNotNull { ktClassOrObject ->
+ analyze(ktClassOrObject) {
+ val inheritorSymbol = ktClassOrObject.namedClassSymbol
+ if (inheritorSymbol != null) {
+ classSymbolToValueType(inheritorSymbol, knownTypes, processAbstractTypes)
+ }
+ else null
+ }
+ }
+ ValueType.AbstractClass(javaClassFqn, superTypesJavaFqns, inheritors)
+ }
+ else -> ValueType.FinalClass(javaClassFqn, superTypesJavaFqns, createProperties(classSymbol, knownTypes).toList())
+ }
+ }
+
+ private fun KaSession.createProperties(classSymbol: KaClassSymbol, knownTypes: MutableMap>) =
+ classSymbol.memberScope.callables
+ .filterIsInstance()
+ .sortedBy { symbol -> symbol.name }
+ .map { propertySymbol ->
+ propertySymbol.location
+ ValueType.ClassProperty(propertySymbol.name.identifier, convertType(propertySymbol.returnType, knownTypes, false))
+ }
+ }
+
+ // TODO: Kirill does not understand why it exists
+ private fun Sequence>.withoutEnumFields() = filterNot { it.name == "name" || it.name == "ordinal" }
+}
diff --git a/plugins/devkit/intellij.devkit.workspaceModel/k2/src/metaModel/WorkspaceMetaModelProviderImpl.kt b/plugins/devkit/intellij.devkit.workspaceModel/k2/src/metaModel/WorkspaceMetaModelProviderImpl.kt
new file mode 100644
index 000000000000..40114f299af3
--- /dev/null
+++ b/plugins/devkit/intellij.devkit.workspaceModel/k2/src/metaModel/WorkspaceMetaModelProviderImpl.kt
@@ -0,0 +1,49 @@
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.devkit.workspaceModel.k2.metaModel
+
+import com.intellij.devkit.workspaceModel.metaModel.WorkspaceMetaModelProvider
+import com.intellij.openapi.module.Module
+import com.intellij.workspaceModel.codegen.deft.meta.CompiledObjModule
+import org.jetbrains.kotlin.analysis.api.permissions.KaAllowAnalysisFromWriteAction
+import org.jetbrains.kotlin.analysis.api.permissions.KaAllowAnalysisOnEdt
+import org.jetbrains.kotlin.analysis.api.permissions.allowAnalysisFromWriteAction
+import org.jetbrains.kotlin.analysis.api.permissions.allowAnalysisOnEdt
+import org.jetbrains.kotlin.idea.base.projectStructure.toKaSourceModuleForProduction
+import org.jetbrains.kotlin.idea.base.projectStructure.toKaSourceModuleForTest
+import org.jetbrains.kotlin.psi.KtClass
+
+internal class WorkspaceMetaModelProviderImpl : WorkspaceMetaModelProvider {
+
+ @OptIn(KaAllowAnalysisFromWriteAction::class, KaAllowAnalysisOnEdt::class)
+ override fun loadObjModules(
+ ktClasses: HashMap,
+ module: Module,
+ processAbstractTypes: Boolean,
+ isTestSourceFolder: Boolean,
+ ): List {
+ val packages = ktClasses.values.mapTo(LinkedHashSet()) { it.containingKtFile.packageFqName.asString() }
+
+ val metaModelProvider = WorkspaceMetaModelBuilder(
+ processAbstractTypes,
+ module.project
+ )
+ val compiledObjModule = allowAnalysisOnEdt {
+ allowAnalysisFromWriteAction {
+ packages
+ .filter { it != "" }
+ .mapNotNull { packageName ->
+ if (!isTestSourceFolder) {
+ module.toKaSourceModuleForProduction()
+ }
+ else {
+ module.toKaSourceModuleForTest()
+ }
+ ?.let { kaModule ->
+ metaModelProvider.getObjModule(packageName, kaModule)
+ }
+ }
+ }
+ }
+ return compiledObjModule
+ }
+}
diff --git a/plugins/devkit/intellij.devkit.workspaceModel/k2/test/AllIntellijEntitiesGenerationTest.kt b/plugins/devkit/intellij.devkit.workspaceModel/k2/test/AllIntellijEntitiesGenerationTest.kt
new file mode 100644
index 000000000000..ed4a72dd91b6
--- /dev/null
+++ b/plugins/devkit/intellij.devkit.workspaceModel/k2/test/AllIntellijEntitiesGenerationTest.kt
@@ -0,0 +1,10 @@
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.devkit.workspaceModel.k2
+
+import com.intellij.devkit.workspaceModel.AbstractAllIntellijEntitiesGenerationTest
+import org.jetbrains.kotlin.idea.base.plugin.KotlinPluginMode
+
+class AllIntellijEntitiesGenerationTest : AbstractAllIntellijEntitiesGenerationTest() {
+ override val pluginMode: KotlinPluginMode
+ get() = KotlinPluginMode.K2
+}
diff --git a/plugins/devkit/intellij.devkit.workspaceModel/k2/test/EntityCodeGenTest.kt b/plugins/devkit/intellij.devkit.workspaceModel/k2/test/EntityCodeGenTest.kt
new file mode 100644
index 000000000000..05842bdca3f0
--- /dev/null
+++ b/plugins/devkit/intellij.devkit.workspaceModel/k2/test/EntityCodeGenTest.kt
@@ -0,0 +1,10 @@
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.devkit.workspaceModel.k2
+
+import com.intellij.devkit.workspaceModel.AbstractEntityCodeGenTest
+import org.jetbrains.kotlin.idea.base.plugin.KotlinPluginMode
+
+class EntityCodeGenTest : AbstractEntityCodeGenTest() {
+ override val pluginMode: KotlinPluginMode
+ get() = KotlinPluginMode.K2
+}
diff --git a/plugins/devkit/intellij.devkit.workspaceModel/resources/intellij.devkit.workspaceModel.xml b/plugins/devkit/intellij.devkit.workspaceModel/resources/intellij.devkit.workspaceModel.xml
index abd1f39880f7..64974c18f0f3 100644
--- a/plugins/devkit/intellij.devkit.workspaceModel/resources/intellij.devkit.workspaceModel.xml
+++ b/plugins/devkit/intellij.devkit.workspaceModel/resources/intellij.devkit.workspaceModel.xml
@@ -1,10 +1,13 @@
+
+
messages.DevKitWorkspaceModelBundle
+
()
object CodeWriter {
@@ -51,7 +53,7 @@ object CodeWriter {
processAbstractTypes: Boolean, explicitApiEnabled: Boolean,
isTestSourceFolder: Boolean, isTestModule: Boolean,
targetFolderGenerator: () -> VirtualFile?,
- existingTargetFolder: () -> VirtualFile?
+ existingTargetFolder: () -> VirtualFile?,
) {
val sourceFilePerObjModule = HashMap()
val ktClasses = HashMap()
@@ -59,15 +61,23 @@ object CodeWriter {
if (it.extension == "kt") {
val ktFile = PsiManager.getInstance(project).findFile(it) as? KtFile?
- ktFile?.declarations?.filterIsInstance()?.filter { clazz -> clazz.name != null }?.forEach { clazz ->
- val fqName = clazz.fqName!!.asString()
- val objModuleName = fqName.replace(clazz.name!!, "").substringBeforeLast(".")
+ ktFile?.declarations
+ ?.filterIsInstance()
+ ?.filter { it.name != null }
+ ?.forEach { ktClass ->
+ val fqName = ktClass.fqName!!.asString()
+ val objModuleName = fqName
+ .replace(ktClass.name!!, "")
+ .substringBeforeLast(".")
- /* We find one virtual file for each module. This is necessary to find the relative path for the generated GeneratedObjModuleFile.
- See [addGeneratedObjModuleFile] method */
- sourceFilePerObjModule[objModuleName] = it
- ktClasses[fqName] = clazz
- }
+ /**
+ * We find one virtual file for each module.
+ * This is necessary to find the relative path for the generated GeneratedObjModuleFile.
+ * See [addGeneratedObjModuleFile] method.
+ */
+ sourceFilePerObjModule[objModuleName] = it
+ ktClasses[fqName] = ktClass
+ }
}
return@processFilesRecursively true
}
@@ -83,11 +93,12 @@ object CodeWriter {
return
}
- CommandProcessor.getInstance().executeCommand(project, Runnable {
+ executeCommand(project, DevKitWorkspaceModelBundle.message("command.name.generate.code.for.workspace.entities.in", sourceFolder.name)) {
val title = DevKitWorkspaceModelBundle.message("progress.title.generating.code")
ApplicationManagerEx.getApplicationEx().runWriteActionWithCancellableProgressInDispatchThread(title, project, null) { indicator ->
indicator.text = DevKitWorkspaceModelBundle.message("progress.text.collecting.classes.metadata")
- val objModules = loadObjModules(ktClasses, module, processAbstractTypes, isTestSourceFolder)
+ val metaLoader: WorkspaceMetaModelProvider = service()
+ val objModules = metaLoader.loadObjModules(ktClasses, module, processAbstractTypes, isTestSourceFolder)
val results = generate(codeGenerator, objModules, explicitApiEnabled, isTestModule)
val generatedCode = results.flatMap { it.generatedCode }
@@ -120,12 +131,6 @@ object CodeWriter {
indicator.text = DevKitWorkspaceModelBundle.message("progress.text.writing.code")
indicator.isIndeterminate = false
- val settings = CodeStyle.getSettings(project)
- val kotlinSettings = settings.getCustomSettings(KotlinCodeStyleSettings::class.java)
- val oldValue = kotlinSettings.NAME_COUNT_TO_USE_STAR_IMPORT
- kotlinSettings.NAME_COUNT_TO_USE_STAR_IMPORT = Int.MAX_VALUE
- CodeStyleSettingsManager.getInstance(project).notifyCodeStyleSettingsChanged()
-
generatedCode.forEachIndexed { i, code ->
val psiFactory = KtPsiFactory(project)
indicator.fraction = 0.15 + 0.1 * i / generatedCode.size
@@ -146,40 +151,47 @@ object CodeWriter {
}
}
- indicator.text = DevKitWorkspaceModelBundle.message("progress.text.formatting.generated.code")
importsByFile.forEach { (file, imports) ->
- addImports(file, imports.set)
+ addImports(file, imports)
}
- generatedFiles.withoutBigFiles().forEachIndexed { i, file ->
- indicator.fraction = 0.25 + 0.7 * i / generatedFiles.size
- CodeStyleManager.getInstance(project).reformat(file)
- }
- topLevelDeclarations.entrySet().forEach { (file, placeAndDeclarations) ->
- val addedElements = ArrayList()
- for ((place, declarations) in placeAndDeclarations) {
- var nextPlace: PsiElement = place
- val newElements = ArrayList()
- for (declaration in declarations) {
- val added = file.addAfter(declaration, nextPlace) as KtDeclaration
- newElements.add(added)
- nextPlace = added
+
+ CodeStyle.runWithLocalSettings(project, CodeStyle.getSettings(project)) { localSettings ->
+ localSettings.setUpCodeStyle()
+ generatedFiles.withoutBigFiles().forEachIndexed { i, file ->
+ indicator.fraction = 0.25 + 0.7 * i / generatedFiles.size
+ CodeStyleManager.getInstance(project).reformat(file)
+ file.apiFileNameForImplFile?.let { ktClasses[it] }?.containingKtFile?.let { apiFile -> copyHeaderComment(apiFile, file) }
+ }
+ topLevelDeclarations.entrySet().forEach { (file, placeAndDeclarations) ->
+ val addedElements = ArrayList()
+ for ((place, declarations) in placeAndDeclarations) {
+ var nextPlace: PsiElement = place
+ val newElements = ArrayList()
+ for (declaration in declarations) {
+ val added = file.addAfter(declaration, nextPlace) as KtDeclaration
+ newElements.add(added)
+ nextPlace = added
+ }
+ addGeneratedRegionStartComment(file, newElements.first())
+ addGeneratedRegionEndComment(file, newElements.last())
+ addedElements.addAll(newElements)
}
- addGeneratedRegionStartComment(file, newElements.first())
- addGeneratedRegionEndComment(file, newElements.last())
- addedElements.addAll(newElements)
}
- }
- val filesWithGeneratedRegions = ktClasses.values.groupBy { it.containingFile }.toList()
- filesWithGeneratedRegions.forEachIndexed { i, (file, classes) ->
- indicator.fraction = 0.95 + 0.05 * i / filesWithGeneratedRegions.size
- reformatCodeInGeneratedRegions(file, classes.mapNotNull { it.body?.node } + listOf(file.node))
- }
+ val filesWithGeneratedRegions = ktClasses.values.groupBy { it.containingFile }.toList()
+ filesWithGeneratedRegions.forEachIndexed { i, (file, classes) ->
+ indicator.fraction = 0.95 + 0.05 * i / filesWithGeneratedRegions.size
+ reformatCodeInGeneratedRegions(file, classes.mapNotNull { it.body?.node } + listOf(file.node))
+ }
- kotlinSettings.NAME_COUNT_TO_USE_STAR_IMPORT = oldValue
- CodeStyleSettingsManager.getInstance(project).notifyCodeStyleSettingsChanged()
+ }
}
- }, DevKitWorkspaceModelBundle.message("command.name.generate.code.for.workspace.entities.in", sourceFolder.name), null)
+ }
+ }
+
+ private fun CodeStyleSettings.setUpCodeStyle() {
+ val kotlinCustomSettings = this.kotlinCustomSettings
+ kotlinCustomSettings.NAME_COUNT_TO_USE_STAR_IMPORT = Int.MAX_VALUE
}
// Documentation for IndexNotReadyException says that it's enough to run completeJustSubmittedTasks only once
@@ -205,7 +217,8 @@ object CodeWriter {
val message = if (apiVersionFromDownloadedJar == CodegenApiVersion.UNKNOWN_VERSION || apiVersionInDevkit > apiVersionFromDownloadedJar) {
DevKitWorkspaceModelBundle.message("notification.workspace.incompatible.codegen.api.versions.content.newer", apiVersionInDevkit, apiVersionFromDownloadedJar)
- } else {
+ }
+ else {
DevKitWorkspaceModelBundle.message("notification.workspace.incompatible.codegen.api.versions.content.older", apiVersionInDevkit, apiVersionFromDownloadedJar)
}
@@ -248,7 +261,8 @@ object CodeWriter {
val apiVersion: String?
try {
apiVersion = readApiVersionFromFile(fileAbsolutePath)
- } catch (e: IOException) {
+ }
+ catch (e: IOException) {
LOG.info("Failed to read codegen-api version from file \"$fileAbsolutePath\": " + e.message)
return CodegenApiVersion.UNKNOWN_VERSION
}
@@ -256,18 +270,10 @@ object CodeWriter {
return apiVersion ?: CodegenApiVersion.UNKNOWN_VERSION
}
- private fun loadObjModules(ktClasses: HashMap, module: Module, processAbstractTypes: Boolean, isTestSourceFolder: Boolean): List {
- val packages = ktClasses.values.mapTo(LinkedHashSet()) { it.containingKtFile.packageFqName.asString() }
-
- val metaModelProvider: WorkspaceMetaModelProvider = WorkspaceMetaModelProviderImpl(
- processAbstractTypes = processAbstractTypes,
- module.project
- )
- return packages.filter { it != "" }.map { metaModelProvider.getObjModule(it, module, isTestSourceFolder) }
- }
-
- private fun generate(codeGenerator: CodeGenerator, objModules: List,
- explicitApiEnabled: Boolean, isTestModule: Boolean): List {
+ private fun generate(
+ codeGenerator: CodeGenerator, objModules: List,
+ explicitApiEnabled: Boolean, isTestModule: Boolean,
+ ): List {
val generatorSettings = GeneratorSettings(explicitApiEnabled = explicitApiEnabled, testModeEnabled = isTestModule)
val entitiesImplementations = objModules.map { codeGenerator.generateEntitiesImplementation(it, generatorSettings) }
val metadataStorageImplementation = codeGenerator.generateMetadataStoragesImplementation(objModules, generatorSettings)
@@ -303,10 +309,12 @@ object CodeWriter {
filesToRemove.forEach { it.delete(CodeWriter) }
}
- private fun addGeneratedObjModuleFile(code: ObjModuleFileGeneratedCode, generatedFiles: MutableList,
- project: Project, sourceFolder: VirtualFile, genFolder: VirtualFile,
- sourceFilePerObjModule: Map,
- importsByFile: MutableMap, psiFactory: KtPsiFactory) {
+ private fun addGeneratedObjModuleFile(
+ code: ObjModuleFileGeneratedCode, generatedFiles: MutableList,
+ project: Project, sourceFolder: VirtualFile, genFolder: VirtualFile,
+ sourceFilePerObjModule: Map,
+ importsByFile: MutableMap, psiFactory: KtPsiFactory,
+ ) {
val packageFqnName = code.objModuleName
val sourceFile = sourceFilePerObjModule[packageFqnName]!!
@@ -323,10 +331,12 @@ object CodeWriter {
importsByFile[addedFile] = implImports
}
- private fun addGeneratedObjClassFile(code: ObjClassGeneratedCode, generatedFiles: MutableList,
- project: Project, sourceFolder: VirtualFile, genFolder: VirtualFile,
- ktClasses: Map, importsByFile: MutableMap,
- topLevelDeclarations: MultiMap>>, psiFactory: KtPsiFactory) {
+ private fun addGeneratedObjClassFile(
+ code: ObjClassGeneratedCode, generatedFiles: MutableList,
+ project: Project, sourceFolder: VirtualFile, genFolder: VirtualFile,
+ ktClasses: Map, importsByFile: MutableMap,
+ topLevelDeclarations: MultiMap>>, psiFactory: KtPsiFactory,
+ ) {
if (code.target.name in SKIPPED_TYPES) return
@@ -349,7 +359,6 @@ object CodeWriter {
val implPackageFqnName = "${apiFile.packageFqName.asString()}.impl"
val implImports = Imports(implPackageFqnName)
val implFile = psiFactory.createFile("${code.target.name}Impl.kt", implImports.findAndRemoveFqns(implementationClassText))
- copyHeaderComment(apiFile, implFile)
apiClass.containingKtFile.importDirectives.mapNotNull { it.importPath }.forEach { import ->
implImports.add(import.pathStr)
}
@@ -381,21 +390,20 @@ object CodeWriter {
}
}
- private fun addImports(file: KtFile, imports: Collection) {
+ private fun addImports(file: KtFile, imports: Imports) {
val psiFactory = KtPsiFactory(file.project)
val importList = file.importList ?: error("no imports in ${file.name}")
- val existingImports = importList.imports.mapNotNullTo(HashSet()) { it.importedFqName?.asString() }
- imports.sorted().filterNot { it in existingImports }.forEach { imported ->
- val place = importList.imports.find { imported < (it.importedFqName?.asString() ?: "") }
- val importDirective = psiFactory.createImportDirective(ImportPath.fromString(imported))
- if (place != null) {
- val added = importList.addBefore(importDirective, place)
- importList.addAfter(psiFactory.createNewLine(), added)
- }
- else {
- val added = importList.add(importDirective)
- importList.addBefore(psiFactory.createNewLine(), added)
- }
+ importList.imports.asSequence().mapNotNull { it.importPath?.pathStr }.forEach { imports.add(it) }
+ if (importList.children.isNotEmpty()) {
+ importList.deleteChildRange(importList.firstChild, importList.lastChild)
+ }
+ imports.imports.sorted().forEach {
+ val importDirective = psiFactory.createImportDirective(ImportPath.fromString(it))
+ importList.add(importDirective)
+ importList.add(psiFactory.createNewLine())
+ }
+ if (imports.isNotEmpty()) {
+ importList.lastChild.delete()
}
}
@@ -454,13 +462,19 @@ object CodeWriter {
if (child.isGeneratedRegionStart) {
regionStart = if (child.treePrev?.elementType == KtTokens.WHITE_SPACE) child.treePrev else child
}
- else if (regionStart != null && child.isGeneratedRegionEnd) {
- generatedRegions.add(regionStart!! to child)
+ else if (child.isGeneratedRegionEnd) {
+ regionStart?.let { generatedRegions.add(it to child) }
}
}
return generatedRegions
}
+ private val KtFile.apiFileNameForImplFile: String?
+ get() {
+ val packageName = packageFqName.asString()
+ if (!packageName.endsWith(".impl") || !name.endsWith("Impl.kt")) return null
+ return "${packageFqName.asString().dropLast(4)}${name.dropLast(7)}"
+ }
private const val GENERATED_REGION_START = "//region generated code"
@@ -492,9 +506,8 @@ object CodeWriter {
const val UNKNOWN_VERSION = "unknown version"
}
- //Function was added because CodeStyleManager throws an exception for big MetadataStorageImpl files
+ // This function was added because CodeStyleManager throws an exception for big MetadataStorageImpl files
private fun Iterable.withoutBigFiles(): Iterable {
return filterNot { it.name == GENERATED_METADATA_STORAGE_FILE }
}
-
}
diff --git a/plugins/devkit/intellij.devkit.workspaceModel/src/codegen/writer/Imports.kt b/plugins/devkit/intellij.devkit.workspaceModel/src/codegen/writer/Imports.kt
index 46a269f08d8d..51c4bca30e8a 100644
--- a/plugins/devkit/intellij.devkit.workspaceModel/src/codegen/writer/Imports.kt
+++ b/plugins/devkit/intellij.devkit.workspaceModel/src/codegen/writer/Imports.kt
@@ -19,7 +19,13 @@ private val packagesImportedByDefault = setOf(
)
class Imports(private val scopeFqn: String?) {
- val set = mutableSetOf()
+ fun isNotEmpty(): Boolean = set.isNotEmpty() || wildcardImports.isNotEmpty()
+
+ val imports: Set
+ get() = set + wildcardImports.map { "$it.*" }
+
+ private val set = mutableSetOf()
+ private val wildcardImports = mutableSetOf()
fun findAndRemoveFqns(str: String): String {
val res = StringBuilder()
@@ -43,11 +49,28 @@ class Imports(private val scopeFqn: String?) {
}
fun add(import: String) {
- set.add(import)
+ val importSegments = import.split('.')
+ if (importSegments.size < 2) set.add(import)
+ else {
+ val (packageName, name) = import
+ .split('.')
+ .let {
+ it.dropLast(1).joinToString(".") to it.last()
+ }
+ add(packageName, name)
+ }
}
+ private fun String.packageRegex() = Regex("^${replace(".", "\\.")}\\.[^.]*$")
+
private fun add(packageName: String, name: String) {
- if (packageName != scopeFqn && packageName !in packagesImportedByDefault) {
+ if (packageName == scopeFqn || packageName in packagesImportedByDefault || packageName in wildcardImports) {
+ return
+ } else if (name == "*") {
+ val packageRegex = packageName.packageRegex()
+ set.removeIf { it.matches(packageRegex) }
+ wildcardImports.add(packageName)
+ } else {
set.add("$packageName.$name")
}
}
diff --git a/plugins/devkit/intellij.devkit.workspaceModel/src/codegen/writer/WorkspaceCodegenProblemsProvider.kt b/plugins/devkit/intellij.devkit.workspaceModel/src/codegen/writer/WorkspaceCodegenProblemsProvider.kt
index 06b13a7cc3ed..0e15287d9c1a 100644
--- a/plugins/devkit/intellij.devkit.workspaceModel/src/codegen/writer/WorkspaceCodegenProblemsProvider.kt
+++ b/plugins/devkit/intellij.devkit.workspaceModel/src/codegen/writer/WorkspaceCodegenProblemsProvider.kt
@@ -7,7 +7,7 @@ import com.intellij.analysis.problemsView.ProblemsCollector
import com.intellij.analysis.problemsView.ProblemsProvider
import com.intellij.analysis.problemsView.toolWindow.ProblemsViewToolWindowUtils.selectProjectErrorsTab
import com.intellij.codeHighlighting.HighlightDisplayLevel
-import com.intellij.devkit.workspaceModel.metaModel.ObjMetaElementWithSource
+import com.intellij.devkit.workspaceModel.metaModel.ObjMetaElementWithPsi
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
@@ -18,7 +18,6 @@ import com.intellij.psi.PsiNameIdentifierOwner
import com.intellij.psi.util.startOffset
import com.intellij.workspaceModel.codegen.engine.GenerationProblem
import com.intellij.workspaceModel.codegen.engine.ProblemLocation
-import org.jetbrains.kotlin.resolve.source.getPsi
import javax.swing.Icon
@Service(Service.Level.PROJECT)
@@ -49,11 +48,10 @@ class WorkspaceCodegenProblemsProvider(override val project: Project): ProblemsP
}
private fun GenerationProblem.toProblem(): WorkspaceModelCodeGenerationProblem {
- val sourceElement = when (val problemLocation = location) {
- is ProblemLocation.Class -> (problemLocation.objClass as ObjMetaElementWithSource).sourceElement
- is ProblemLocation.Property -> (problemLocation.property as ObjMetaElementWithSource).sourceElement
- }
- val psiElement = sourceElement.getPsi() ?: return WorkspaceModelCodeGenerationProblem(this)
+ val psiElement = when (val problemLocation = location) {
+ is ProblemLocation.Class -> (problemLocation.objClass as ObjMetaElementWithPsi).sourcePsi
+ is ProblemLocation.Property -> (problemLocation.property as ObjMetaElementWithPsi).sourcePsi
+ } ?: return WorkspaceModelCodeGenerationProblem(this)
val psiToHighlight = (psiElement as? PsiNameIdentifierOwner)?.nameIdentifier ?: psiElement
val file = psiToHighlight.containingFile.virtualFile
val document = FileDocumentManager.getInstance().getDocument(file) ?: return WorkspaceModelCodeGenerationProblem(this)
@@ -84,4 +82,3 @@ class WorkspaceCodegenProblemsProvider(override val project: Project): ProblemsP
override val column: Int
) : WorkspaceModelCodeGenerationProblem(originalProblem), FileProblem
}
-
diff --git a/plugins/devkit/intellij.devkit.workspaceModel/src/metaModel/Utils.kt b/plugins/devkit/intellij.devkit.workspaceModel/src/metaModel/Utils.kt
new file mode 100644
index 000000000000..05ab7f212453
--- /dev/null
+++ b/plugins/devkit/intellij.devkit.workspaceModel/src/metaModel/Utils.kt
@@ -0,0 +1,15 @@
+// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.devkit.workspaceModel.metaModel
+
+import com.intellij.psi.PsiElement
+import com.intellij.workspaceModel.codegen.deft.meta.ValueType
+
+class IncorrectObjInterfaceException(errorMessage: String) : RuntimeException(errorMessage)
+
+interface ObjMetaElementWithPsi {
+ val sourcePsi: PsiElement?
+}
+
+fun unsupportedType(type: String?): ValueType<*> {
+ throw IncorrectObjInterfaceException("Unsupported type '$type'")
+}
diff --git a/plugins/devkit/intellij.devkit.workspaceModel/src/metaModel/WorkspaceMetaModelProvider.kt b/plugins/devkit/intellij.devkit.workspaceModel/src/metaModel/WorkspaceMetaModelProvider.kt
index 939f4393bcdb..f7ee33d291a5 100644
--- a/plugins/devkit/intellij.devkit.workspaceModel/src/metaModel/WorkspaceMetaModelProvider.kt
+++ b/plugins/devkit/intellij.devkit.workspaceModel/src/metaModel/WorkspaceMetaModelProvider.kt
@@ -1,16 +1,15 @@
-// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.devkit.workspaceModel.metaModel
import com.intellij.openapi.module.Module
import com.intellij.workspaceModel.codegen.deft.meta.CompiledObjModule
-import org.jetbrains.kotlin.descriptors.SourceElement
+import org.jetbrains.kotlin.psi.KtClass
interface WorkspaceMetaModelProvider {
- fun getObjModule(packageName: String, module: Module, isTestSourceFolder: Boolean): CompiledObjModule
+ fun loadObjModules(
+ ktClasses: HashMap,
+ module: Module,
+ processAbstractTypes: Boolean,
+ isTestSourceFolder: Boolean,
+ ): List
}
-
-class IncorrectObjInterfaceException(errorMessage: String): RuntimeException(errorMessage)
-
-interface ObjMetaElementWithSource {
- val sourceElement: SourceElement
-}
\ No newline at end of file
diff --git a/plugins/devkit/intellij.devkit.workspaceModel/src/metaModel/WorkspaceModelDefaults.kt b/plugins/devkit/intellij.devkit.workspaceModel/src/metaModel/WorkspaceModelDefaults.kt
new file mode 100644
index 000000000000..0d20824ddb2a
--- /dev/null
+++ b/plugins/devkit/intellij.devkit.workspaceModel/src/metaModel/WorkspaceModelDefaults.kt
@@ -0,0 +1,32 @@
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.devkit.workspaceModel.metaModel
+
+import com.intellij.platform.workspace.storage.*
+import com.intellij.platform.workspace.storage.annotations.Abstract
+import com.intellij.platform.workspace.storage.annotations.Child
+import com.intellij.platform.workspace.storage.annotations.Default
+import com.intellij.platform.workspace.storage.annotations.Open
+import com.intellij.platform.workspace.storage.url.VirtualFileUrl
+import org.jetbrains.kotlin.builtins.StandardNames
+import org.jetbrains.kotlin.name.ClassId
+import org.jetbrains.kotlin.name.FqName
+import org.jetbrains.kotlin.name.StandardClassIds
+
+enum class WorkspaceModelDefaults(val fqName: FqName, val classId: ClassId) {
+ DEFAULT_ANNOTATION(FqName(Default::class.qualifiedName!!), ClassId.topLevel(FqName(Default::class.qualifiedName!!))),
+ OPEN_ANNOTATION(FqName(Open::class.qualifiedName!!), ClassId.topLevel(FqName(Open::class.qualifiedName!!))),
+ ABSTRACT_ANNOTATION(FqName(Abstract::class.qualifiedName!!), ClassId.topLevel(FqName(Abstract::class.qualifiedName!!))),
+ CHILD_ANNOTATION(FqName(Child::class.qualifiedName!!), ClassId.topLevel(FqName(Child::class.qualifiedName!!))),
+ EQUALS_BY_ANNOTATION(FqName(EqualsBy::class.qualifiedName!!), ClassId.topLevel(FqName(EqualsBy::class.qualifiedName!!))),
+
+ LIST_INTERFACE(StandardNames.FqNames.list, StandardClassIds.List),
+ SET_INTERFACE(StandardNames.FqNames.set, StandardClassIds.Set),
+ MAP_INTERFACE(StandardNames.FqNames.map, StandardClassIds.Map),
+
+ WORKSPACE_ENTITY(FqName(WorkspaceEntity::class.qualifiedName!!), ClassId.topLevel(FqName(WorkspaceEntity::class.qualifiedName!!))),
+ ENTITY_SOURCE(FqName(EntitySource::class.qualifiedName!!), ClassId.topLevel(FqName(EntitySource::class.qualifiedName!!))),
+ VIRTUAL_FILE_URL(FqName(VirtualFileUrl::class.qualifiedName!!), ClassId.topLevel(FqName(VirtualFileUrl::class.qualifiedName!!))),
+ SYMBOLIC_ENTITY_ID(FqName(SymbolicEntityId::class.qualifiedName!!), ClassId.topLevel(FqName(SymbolicEntityId::class.qualifiedName!!))),
+ ENTITY_POINTER(FqName(EntityPointer::class.qualifiedName!!), ClassId.topLevel(FqName(EntityPointer::class.qualifiedName!!)))
+ ;
+}
diff --git a/plugins/devkit/intellij.devkit.workspaceModel/src/metaModel/impl/fields.kt b/plugins/devkit/intellij.devkit.workspaceModel/src/metaModel/impl/fields.kt
index cb0483513b9a..ad27c99d5b4d 100644
--- a/plugins/devkit/intellij.devkit.workspaceModel/src/metaModel/impl/fields.kt
+++ b/plugins/devkit/intellij.devkit.workspaceModel/src/metaModel/impl/fields.kt
@@ -1,9 +1,9 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.devkit.workspaceModel.metaModel.impl
-import com.intellij.devkit.workspaceModel.metaModel.ObjMetaElementWithSource
+import com.intellij.devkit.workspaceModel.metaModel.ObjMetaElementWithPsi
+import com.intellij.psi.PsiElement
import com.intellij.workspaceModel.codegen.deft.meta.*
-import org.jetbrains.kotlin.descriptors.SourceElement
private val fieldObjType = ObjTypeImpl>()
@@ -15,8 +15,8 @@ sealed class ObjPropertyBase(
override val open: Boolean,
override val mutable: Boolean,
override val content: Boolean,
- override val sourceElement: SourceElement
-) : ObjProperty, ObjMetaElementWithSource {
+ override val sourcePsi: PsiElement?
+) : ObjProperty, ObjMetaElementWithPsi {
private val mutableOverrides: MutableList> = ArrayList()
private val mutableOverriddenBy: MutableList> = ArrayList()
@@ -39,8 +39,8 @@ class OwnPropertyImpl(
override val constructorParameter: Boolean,
override val classLocalId: Int,
override val isKey: Boolean,
- sourceElement: SourceElement
-) : ObjPropertyBase(receiver, name, valueType, valueKind, open, mutable, content, sourceElement), OwnProperty {
+ sourcePsi: PsiElement?
+) : ObjPropertyBase(receiver, name, valueType, valueKind, open, mutable, content, sourcePsi), OwnProperty {
override fun toString(): String = "$name (${receiver.name})"
}
@@ -56,7 +56,7 @@ class ExtPropertyImpl(
override val module: ObjModule,
override val moduleLocalId: Int,
override val annotations: List,
- sourceElement: SourceElement,
-) : ObjPropertyBase(receiver, name, valueType, valueKind, open, mutable, content, sourceElement), ExtProperty {
+ sourcePsi: PsiElement?
+) : ObjPropertyBase(receiver, name, valueType, valueKind, open, mutable, content, sourcePsi), ExtProperty {
override fun toString(): String = "$name (${receiver.name})"
-}
\ No newline at end of file
+}
diff --git a/plugins/devkit/intellij.devkit.workspaceModel/src/metaModel/impl/types.kt b/plugins/devkit/intellij.devkit.workspaceModel/src/metaModel/impl/types.kt
index a5fbd5f39dd1..8a46f8cc6c9a 100644
--- a/plugins/devkit/intellij.devkit.workspaceModel/src/metaModel/impl/types.kt
+++ b/plugins/devkit/intellij.devkit.workspaceModel/src/metaModel/impl/types.kt
@@ -1,9 +1,9 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.devkit.workspaceModel.metaModel.impl
-import com.intellij.devkit.workspaceModel.metaModel.ObjMetaElementWithSource
+import com.intellij.devkit.workspaceModel.metaModel.ObjMetaElementWithPsi
+import com.intellij.psi.PsiElement
import com.intellij.workspaceModel.codegen.deft.meta.*
-import org.jetbrains.kotlin.descriptors.SourceElement
object ObjTypeType : ObjType> {
override val classifier: ObjType>
@@ -27,9 +27,9 @@ class ObjClassImpl(
override val module: ObjModule,
override val name: String,
override val openness: ObjClass.Openness,
- override val sourceElement: SourceElement,
+ override val sourcePsi: PsiElement?,
override val annotations: List
-) : ObjClass, ObjTypeImpl(), ObjMetaElementWithSource {
+) : ObjClass, ObjTypeImpl(), ObjMetaElementWithPsi {
override var parentField: OwnProperty? = null
override var nameField: OwnProperty? = null
private val mutableFields: MutableList> = ArrayList()
@@ -69,4 +69,4 @@ class ObjClassImpl(
class ObjAnnotationImpl(override val fqName: String, override val pathSegments: List) : ObjAnnotation {
override fun toString(): String = fqName
-}
\ No newline at end of file
+}
diff --git a/plugins/devkit/intellij.devkit.workspaceModel/tests/intellij.devkit.workspaceModel.tests.iml b/plugins/devkit/intellij.devkit.workspaceModel/tests/intellij.devkit.workspaceModel.tests.iml
index ef11a61a71ec..446fa9c51684 100644
--- a/plugins/devkit/intellij.devkit.workspaceModel/tests/intellij.devkit.workspaceModel.tests.iml
+++ b/plugins/devkit/intellij.devkit.workspaceModel/tests/intellij.devkit.workspaceModel.tests.iml
@@ -3,7 +3,7 @@
-
+
@@ -14,8 +14,7 @@
-
-
+
@@ -29,5 +28,6 @@
+
\ No newline at end of file
diff --git a/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/codeGen/defaultProperty/after/entity.kt b/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/codeGen/defaultProperty/after/entity.kt
index cf32c3ecb1f0..913ed0888f21 100644
--- a/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/codeGen/defaultProperty/after/entity.kt
+++ b/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/codeGen/defaultProperty/after/entity.kt
@@ -4,8 +4,8 @@ import com.intellij.platform.workspace.storage.EntitySource
import com.intellij.platform.workspace.storage.EntityType
import com.intellij.platform.workspace.storage.GeneratedCodeApiVersion
import com.intellij.platform.workspace.storage.MutableEntityStorage
-import com.intellij.platform.workspace.storage.annotations.Default
import com.intellij.platform.workspace.storage.WorkspaceEntity
+import com.intellij.platform.workspace.storage.annotations.Default
interface DefaultFieldEntity : WorkspaceEntity {
val version: Int
diff --git a/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/codeGen/defaultProperty/after/gen/impl/MetadataStorageImpl.kt b/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/codeGen/defaultProperty/after/gen/impl/MetadataStorageImpl.kt
index 17df9c2545c8..469886a001ec 100644
--- a/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/codeGen/defaultProperty/after/gen/impl/MetadataStorageImpl.kt
+++ b/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/codeGen/defaultProperty/after/gen/impl/MetadataStorageImpl.kt
@@ -18,8 +18,8 @@ internal object MetadataStorageImpl: MetadataStorageBase() {
typeMetadata = EntityMetadata(fqName = "com.intellij.workspaceModel.test.api.DefaultFieldEntity", entityDataFqName = "com.intellij.workspaceModel.test.api.impl.DefaultFieldEntityData", supertypes = listOf("com.intellij.platform.workspace.storage.WorkspaceEntity"), properties = listOf(OwnPropertyMetadata(isComputable = false, isKey = false, isOpen = false, name = "entitySource", valueType = ValueTypeMetadata.SimpleType.CustomType(isNullable = false, typeMetadata = FinalClassMetadata.KnownClass(fqName = "com.intellij.platform.workspace.storage.EntitySource")), withDefault = false),
OwnPropertyMetadata(isComputable = false, isKey = false, isOpen = false, name = "version", valueType = primitiveTypeIntNotNullable, withDefault = false),
-OwnPropertyMetadata(isComputable = false, isKey = false, isOpen = false, name = "data", valueType = ValueTypeMetadata.SimpleType.CustomType(isNullable = false, typeMetadata = FinalClassMetadata.ClassMetadata(fqName = "com.intellij.workspaceModel.test.api.TestData", properties = listOf(OwnPropertyMetadata(isComputable = false, isKey = false, isOpen = false, name = "name", valueType = primitiveTypeStringNotNullable, withDefault = false),
-OwnPropertyMetadata(isComputable = false, isKey = false, isOpen = false, name = "description", valueType = primitiveTypeStringNotNullable, withDefault = false)), supertypes = listOf())), withDefault = false),
+OwnPropertyMetadata(isComputable = false, isKey = false, isOpen = false, name = "data", valueType = ValueTypeMetadata.SimpleType.CustomType(isNullable = false, typeMetadata = FinalClassMetadata.ClassMetadata(fqName = "com.intellij.workspaceModel.test.api.TestData", properties = listOf(OwnPropertyMetadata(isComputable = false, isKey = false, isOpen = false, name = "description", valueType = primitiveTypeStringNotNullable, withDefault = false),
+OwnPropertyMetadata(isComputable = false, isKey = false, isOpen = false, name = "name", valueType = primitiveTypeStringNotNullable, withDefault = false)), supertypes = listOf())), withDefault = false),
OwnPropertyMetadata(isComputable = false, isKey = false, isOpen = false, name = "anotherVersion", valueType = primitiveTypeIntNotNullable, withDefault = true),
OwnPropertyMetadata(isComputable = false, isKey = false, isOpen = false, name = "description", valueType = primitiveTypeStringNotNullable, withDefault = true)), extProperties = listOf(), isAbstract = false)
@@ -27,8 +27,8 @@ OwnPropertyMetadata(isComputable = false, isKey = false, isOpen = false, name =
}
override fun initializeMetadataHash() {
- addMetadataHash(typeFqn = "com.intellij.workspaceModel.test.api.DefaultFieldEntity", metadataHash = 124467804)
- addMetadataHash(typeFqn = "com.intellij.workspaceModel.test.api.TestData", metadataHash = 589453437)
+ addMetadataHash(typeFqn = "com.intellij.workspaceModel.test.api.DefaultFieldEntity", metadataHash = -1646169674)
+ addMetadataHash(typeFqn = "com.intellij.workspaceModel.test.api.TestData", metadataHash = -898117917)
}
}
diff --git a/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/codeGen/entityWithChildrenCollection/after/entity.kt b/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/codeGen/entityWithChildrenCollection/after/entity.kt
new file mode 100644
index 000000000000..077fdf9d3d46
--- /dev/null
+++ b/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/codeGen/entityWithChildrenCollection/after/entity.kt
@@ -0,0 +1,96 @@
+package com.intellij.workspaceModel.test.api
+
+import com.intellij.platform.workspace.storage.EntitySource
+import com.intellij.platform.workspace.storage.EntityType
+import com.intellij.platform.workspace.storage.GeneratedCodeApiVersion
+import com.intellij.platform.workspace.storage.MutableEntityStorage
+import com.intellij.platform.workspace.storage.WorkspaceEntity
+import com.intellij.platform.workspace.storage.annotations.Child
+
+interface ChildrenCollectionFieldEntity : WorkspaceEntity {
+ val name: String
+ val childrenEntitiesCollection: List<@Child SimpleEntity>
+
+ //region generated code
+ @GeneratedCodeApiVersion(3)
+ interface Builder : WorkspaceEntity.Builder {
+ override var entitySource: EntitySource
+ var name: String
+ var childrenEntitiesCollection: List
+ }
+
+ companion object : EntityType() {
+ @JvmOverloads
+ @JvmStatic
+ @JvmName("create")
+ operator fun invoke(
+ name: String,
+ entitySource: EntitySource,
+ init: (Builder.() -> Unit)? = null,
+ ): Builder {
+ val builder = builder()
+ builder.name = name
+ builder.entitySource = entitySource
+ init?.invoke(builder)
+ return builder
+ }
+ }
+ //endregion
+}
+
+//region generated code
+fun MutableEntityStorage.modifyChildrenCollectionFieldEntity(
+ entity: ChildrenCollectionFieldEntity,
+ modification: ChildrenCollectionFieldEntity.Builder.() -> Unit,
+): ChildrenCollectionFieldEntity {
+ return modifyEntity(ChildrenCollectionFieldEntity.Builder::class.java, entity, modification)
+}
+//endregion
+
+interface SimpleEntity : WorkspaceEntity {
+ val version: Int
+ val name: String
+ val isSimple: Boolean
+ val parent: ChildrenCollectionFieldEntity
+
+ //region generated code
+ @GeneratedCodeApiVersion(3)
+ interface Builder : WorkspaceEntity.Builder {
+ override var entitySource: EntitySource
+ var version: Int
+ var name: String
+ var isSimple: Boolean
+ var parent: ChildrenCollectionFieldEntity.Builder
+ }
+
+ companion object : EntityType() {
+ @JvmOverloads
+ @JvmStatic
+ @JvmName("create")
+ operator fun invoke(
+ version: Int,
+ name: String,
+ isSimple: Boolean,
+ entitySource: EntitySource,
+ init: (Builder.() -> Unit)? = null,
+ ): Builder {
+ val builder = builder()
+ builder.version = version
+ builder.name = name
+ builder.isSimple = isSimple
+ builder.entitySource = entitySource
+ init?.invoke(builder)
+ return builder
+ }
+ }
+ //endregion
+}
+
+//region generated code
+fun MutableEntityStorage.modifySimpleEntity(
+ entity: SimpleEntity,
+ modification: SimpleEntity.Builder.() -> Unit,
+): SimpleEntity {
+ return modifyEntity(SimpleEntity.Builder::class.java, entity, modification)
+}
+//endregion
diff --git a/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/codeGen/entityWithChildrenCollection/after/gen/impl/ChildrenCollectionFieldEntityImpl.kt b/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/codeGen/entityWithChildrenCollection/after/gen/impl/ChildrenCollectionFieldEntityImpl.kt
new file mode 100644
index 000000000000..1b1d31f78130
--- /dev/null
+++ b/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/codeGen/entityWithChildrenCollection/after/gen/impl/ChildrenCollectionFieldEntityImpl.kt
@@ -0,0 +1,267 @@
+package com.intellij.workspaceModel.test.api.impl
+
+import com.intellij.platform.workspace.storage.ConnectionId
+import com.intellij.platform.workspace.storage.EntitySource
+import com.intellij.platform.workspace.storage.GeneratedCodeApiVersion
+import com.intellij.platform.workspace.storage.GeneratedCodeImplVersion
+import com.intellij.platform.workspace.storage.MutableEntityStorage
+import com.intellij.platform.workspace.storage.WorkspaceEntity
+import com.intellij.platform.workspace.storage.WorkspaceEntityInternalApi
+import com.intellij.platform.workspace.storage.annotations.Child
+import com.intellij.platform.workspace.storage.impl.EntityLink
+import com.intellij.platform.workspace.storage.impl.ModifiableWorkspaceEntityBase
+import com.intellij.platform.workspace.storage.impl.WorkspaceEntityBase
+import com.intellij.platform.workspace.storage.impl.WorkspaceEntityData
+import com.intellij.platform.workspace.storage.impl.extractOneToManyChildren
+import com.intellij.platform.workspace.storage.impl.updateOneToManyChildrenOfParent
+import com.intellij.platform.workspace.storage.instrumentation.EntityStorageInstrumentation
+import com.intellij.platform.workspace.storage.instrumentation.EntityStorageInstrumentationApi
+import com.intellij.platform.workspace.storage.instrumentation.MutableEntityStorageInstrumentation
+import com.intellij.platform.workspace.storage.metadata.model.EntityMetadata
+import com.intellij.workspaceModel.test.api.ChildrenCollectionFieldEntity
+import com.intellij.workspaceModel.test.api.SimpleEntity
+
+@GeneratedCodeApiVersion(3)
+@GeneratedCodeImplVersion(6)
+@OptIn(WorkspaceEntityInternalApi::class)
+internal class ChildrenCollectionFieldEntityImpl(private val dataSource: ChildrenCollectionFieldEntityData) : ChildrenCollectionFieldEntity,
+ WorkspaceEntityBase(
+ dataSource
+ ) {
+
+ private companion object {
+ internal val CHILDRENENTITIESCOLLECTION_CONNECTION_ID: ConnectionId = ConnectionId.create(
+ ChildrenCollectionFieldEntity::class.java, SimpleEntity::class.java, ConnectionId.ConnectionType.ONE_TO_MANY, false
+ )
+
+ private val connections = listOf(
+ CHILDRENENTITIESCOLLECTION_CONNECTION_ID,
+ )
+
+ }
+
+ override val name: String
+ get() {
+ readField("name")
+ return dataSource.name
+ }
+
+ override val childrenEntitiesCollection: List
+ get() = snapshot.extractOneToManyChildren(CHILDRENENTITIESCOLLECTION_CONNECTION_ID, this)!!.toList()
+
+ override val entitySource: EntitySource
+ get() {
+ readField("entitySource")
+ return dataSource.entitySource
+ }
+
+ override fun connectionIdList(): List {
+ return connections
+ }
+
+
+ internal class Builder(result: ChildrenCollectionFieldEntityData?) :
+ ModifiableWorkspaceEntityBase(result),
+ ChildrenCollectionFieldEntity.Builder {
+ internal constructor() : this(ChildrenCollectionFieldEntityData())
+
+ override fun applyToBuilder(builder: MutableEntityStorage) {
+ if (this.diff != null) {
+ if (existsInBuilder(builder)) {
+ this.diff = builder
+ return
+ }
+ else {
+ error("Entity ChildrenCollectionFieldEntity is already created in a different builder")
+ }
+ }
+
+ this.diff = builder
+ addToBuilder()
+ this.id = getEntityData().createEntityId()
+ // After adding entity data to the builder, we need to unbind it and move the control over entity data to builder
+ // Builder may switch to snapshot at any moment and lock entity data to modification
+ this.currentEntityData = null
+
+ // Process linked entities that are connected without a builder
+ processLinkedEntities(builder)
+ checkInitialization() // TODO uncomment and check failed tests
+ }
+
+ private fun checkInitialization() {
+ val _diff = diff
+ if (!getEntityData().isEntitySourceInitialized()) {
+ error("Field WorkspaceEntity#entitySource should be initialized")
+ }
+ if (!getEntityData().isNameInitialized()) {
+ error("Field ChildrenCollectionFieldEntity#name should be initialized")
+ }
+ // Check initialization for list with ref type
+ if (_diff != null) {
+ if (_diff.extractOneToManyChildren(CHILDRENENTITIESCOLLECTION_CONNECTION_ID, this) == null) {
+ error("Field ChildrenCollectionFieldEntity#childrenEntitiesCollection should be initialized")
+ }
+ }
+ else {
+ if (this.entityLinks[EntityLink(true, CHILDRENENTITIESCOLLECTION_CONNECTION_ID)] == null) {
+ error("Field ChildrenCollectionFieldEntity#childrenEntitiesCollection should be initialized")
+ }
+ }
+ }
+
+ override fun connectionIdList(): List {
+ return connections
+ }
+
+ // Relabeling code, move information from dataSource to this builder
+ override fun relabel(dataSource: WorkspaceEntity, parents: Set?) {
+ dataSource as ChildrenCollectionFieldEntity
+ if (this.entitySource != dataSource.entitySource) this.entitySource = dataSource.entitySource
+ if (this.name != dataSource.name) this.name = dataSource.name
+ updateChildToParentReferences(parents)
+ }
+
+
+ override var entitySource: EntitySource
+ get() = getEntityData().entitySource
+ set(value) {
+ checkModificationAllowed()
+ getEntityData(true).entitySource = value
+ changedProperty.add("entitySource")
+
+ }
+
+ override var name: String
+ get() = getEntityData().name
+ set(value) {
+ checkModificationAllowed()
+ getEntityData(true).name = value
+ changedProperty.add("name")
+ }
+
+ // List of non-abstract referenced types
+ var _childrenEntitiesCollection: List? = emptyList()
+ override var childrenEntitiesCollection: List
+ get() {
+ // Getter of the list of non-abstract referenced types
+ val _diff = diff
+ return if (_diff != null) {
+ @OptIn(EntityStorageInstrumentationApi::class)
+ ((_diff as MutableEntityStorageInstrumentation).getManyChildrenBuilders(CHILDRENENTITIESCOLLECTION_CONNECTION_ID, this)!!
+ .toList() as List) +
+ (this.entityLinks[EntityLink(true, CHILDRENENTITIESCOLLECTION_CONNECTION_ID)] as? List ?: emptyList())
+ }
+ else {
+ this.entityLinks[EntityLink(true, CHILDRENENTITIESCOLLECTION_CONNECTION_ID)] as? List ?: emptyList()
+ }
+ }
+ set(value) {
+ // Setter of the list of non-abstract referenced types
+ checkModificationAllowed()
+ val _diff = diff
+ if (_diff != null) {
+ for (item_value in value) {
+ if (item_value is ModifiableWorkspaceEntityBase<*, *> && (item_value as? ModifiableWorkspaceEntityBase<*, *>)?.diff == null) {
+ // Backref setup before adding to store
+ if (item_value is ModifiableWorkspaceEntityBase<*, *>) {
+ item_value.entityLinks[EntityLink(false, CHILDRENENTITIESCOLLECTION_CONNECTION_ID)] = this
+ }
+ // else you're attaching a new entity to an existing entity that is not modifiable
+
+ _diff.addEntity(item_value as ModifiableWorkspaceEntityBase)
+ }
+ }
+ _diff.updateOneToManyChildrenOfParent(CHILDRENENTITIESCOLLECTION_CONNECTION_ID, this, value)
+ }
+ else {
+ for (item_value in value) {
+ if (item_value is ModifiableWorkspaceEntityBase<*, *>) {
+ item_value.entityLinks[EntityLink(false, CHILDRENENTITIESCOLLECTION_CONNECTION_ID)] = this
+ }
+ // else you're attaching a new entity to an existing entity that is not modifiable
+ }
+
+ this.entityLinks[EntityLink(true, CHILDRENENTITIESCOLLECTION_CONNECTION_ID)] = value
+ }
+ changedProperty.add("childrenEntitiesCollection")
+ }
+
+ override fun getEntityClass(): Class = ChildrenCollectionFieldEntity::class.java
+ }
+}
+
+@OptIn(WorkspaceEntityInternalApi::class)
+internal class ChildrenCollectionFieldEntityData : WorkspaceEntityData() {
+ lateinit var name: String
+
+ internal fun isNameInitialized(): Boolean = ::name.isInitialized
+
+ override fun wrapAsModifiable(diff: MutableEntityStorage): WorkspaceEntity.Builder {
+ val modifiable = ChildrenCollectionFieldEntityImpl.Builder(null)
+ modifiable.diff = diff
+ modifiable.id = createEntityId()
+ return modifiable
+ }
+
+ @OptIn(EntityStorageInstrumentationApi::class)
+ override fun createEntity(snapshot: EntityStorageInstrumentation): ChildrenCollectionFieldEntity {
+ val entityId = createEntityId()
+ return snapshot.initializeEntity(entityId) {
+ val entity = ChildrenCollectionFieldEntityImpl(this)
+ entity.snapshot = snapshot
+ entity.id = entityId
+ entity
+ }
+ }
+
+ override fun getMetadata(): EntityMetadata {
+ return MetadataStorageImpl.getMetadataByTypeFqn("com.intellij.workspaceModel.test.api.ChildrenCollectionFieldEntity") as EntityMetadata
+ }
+
+ override fun getEntityInterface(): Class {
+ return ChildrenCollectionFieldEntity::class.java
+ }
+
+ override fun createDetachedEntity(parents: List>): WorkspaceEntity.Builder<*> {
+ return ChildrenCollectionFieldEntity(name, entitySource) {
+ }
+ }
+
+ override fun getRequiredParents(): List> {
+ val res = mutableListOf>()
+ return res
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (other == null) return false
+ if (this.javaClass != other.javaClass) return false
+
+ other as ChildrenCollectionFieldEntityData
+
+ if (this.entitySource != other.entitySource) return false
+ if (this.name != other.name) return false
+ return true
+ }
+
+ override fun equalsIgnoringEntitySource(other: Any?): Boolean {
+ if (other == null) return false
+ if (this.javaClass != other.javaClass) return false
+
+ other as ChildrenCollectionFieldEntityData
+
+ if (this.name != other.name) return false
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = entitySource.hashCode()
+ result = 31 * result + name.hashCode()
+ return result
+ }
+
+ override fun hashCodeIgnoringEntitySource(): Int {
+ var result = javaClass.hashCode()
+ result = 31 * result + name.hashCode()
+ return result
+ }
+}
diff --git a/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/codeGen/entityWithChildrenCollection/after/gen/impl/MetadataStorageImpl.kt b/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/codeGen/entityWithChildrenCollection/after/gen/impl/MetadataStorageImpl.kt
new file mode 100644
index 000000000000..9360b9969d40
--- /dev/null
+++ b/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/codeGen/entityWithChildrenCollection/after/gen/impl/MetadataStorageImpl.kt
@@ -0,0 +1,41 @@
+package com.intellij.workspaceModel.test.api.impl
+
+import com.intellij.platform.workspace.storage.ConnectionId
+import com.intellij.platform.workspace.storage.WorkspaceEntityInternalApi
+import com.intellij.platform.workspace.storage.metadata.impl.MetadataStorageBase
+import com.intellij.platform.workspace.storage.metadata.model.EntityMetadata
+import com.intellij.platform.workspace.storage.metadata.model.FinalClassMetadata
+import com.intellij.platform.workspace.storage.metadata.model.OwnPropertyMetadata
+import com.intellij.platform.workspace.storage.metadata.model.StorageTypeMetadata
+import com.intellij.platform.workspace.storage.metadata.model.ValueTypeMetadata
+
+@OptIn(WorkspaceEntityInternalApi::class)
+internal object MetadataStorageImpl: MetadataStorageBase() {
+ override fun initializeMetadata() {
+ val primitiveTypeStringNotNullable = ValueTypeMetadata.SimpleType.PrimitiveType(isNullable = false, type = "String")
+ val primitiveTypeIntNotNullable = ValueTypeMetadata.SimpleType.PrimitiveType(isNullable = false, type = "Int")
+ val primitiveTypeBooleanNotNullable = ValueTypeMetadata.SimpleType.PrimitiveType(isNullable = false, type = "Boolean")
+
+ var typeMetadata: StorageTypeMetadata
+
+ typeMetadata = EntityMetadata(fqName = "com.intellij.workspaceModel.test.api.ChildrenCollectionFieldEntity", entityDataFqName = "com.intellij.workspaceModel.test.api.impl.ChildrenCollectionFieldEntityData", supertypes = listOf("com.intellij.platform.workspace.storage.WorkspaceEntity"), properties = listOf(OwnPropertyMetadata(isComputable = false, isKey = false, isOpen = false, name = "entitySource", valueType = ValueTypeMetadata.SimpleType.CustomType(isNullable = false, typeMetadata = FinalClassMetadata.KnownClass(fqName = "com.intellij.platform.workspace.storage.EntitySource")), withDefault = false),
+OwnPropertyMetadata(isComputable = false, isKey = false, isOpen = false, name = "name", valueType = primitiveTypeStringNotNullable, withDefault = false),
+OwnPropertyMetadata(isComputable = false, isKey = false, isOpen = false, name = "childrenEntitiesCollection", valueType = ValueTypeMetadata.EntityReference(connectionType = ConnectionId.ConnectionType.ONE_TO_MANY, entityFqName = "com.intellij.workspaceModel.test.api.SimpleEntity", isChild = true, isNullable = false), withDefault = false)), extProperties = listOf(), isAbstract = false)
+
+ addMetadata(typeMetadata)
+
+ typeMetadata = EntityMetadata(fqName = "com.intellij.workspaceModel.test.api.SimpleEntity", entityDataFqName = "com.intellij.workspaceModel.test.api.impl.SimpleEntityData", supertypes = listOf("com.intellij.platform.workspace.storage.WorkspaceEntity"), properties = listOf(OwnPropertyMetadata(isComputable = false, isKey = false, isOpen = false, name = "entitySource", valueType = ValueTypeMetadata.SimpleType.CustomType(isNullable = false, typeMetadata = FinalClassMetadata.KnownClass(fqName = "com.intellij.platform.workspace.storage.EntitySource")), withDefault = false),
+OwnPropertyMetadata(isComputable = false, isKey = false, isOpen = false, name = "version", valueType = primitiveTypeIntNotNullable, withDefault = false),
+OwnPropertyMetadata(isComputable = false, isKey = false, isOpen = false, name = "name", valueType = primitiveTypeStringNotNullable, withDefault = false),
+OwnPropertyMetadata(isComputable = false, isKey = false, isOpen = false, name = "isSimple", valueType = primitiveTypeBooleanNotNullable, withDefault = false),
+OwnPropertyMetadata(isComputable = false, isKey = false, isOpen = false, name = "parent", valueType = ValueTypeMetadata.EntityReference(connectionType = ConnectionId.ConnectionType.ONE_TO_MANY, entityFqName = "com.intellij.workspaceModel.test.api.ChildrenCollectionFieldEntity", isChild = false, isNullable = false), withDefault = false)), extProperties = listOf(), isAbstract = false)
+
+ addMetadata(typeMetadata)
+ }
+
+ override fun initializeMetadataHash() {
+ addMetadataHash(typeFqn = "com.intellij.workspaceModel.test.api.ChildrenCollectionFieldEntity", metadataHash = -454547805)
+ addMetadataHash(typeFqn = "com.intellij.workspaceModel.test.api.SimpleEntity", metadataHash = -41718252)
+ }
+
+}
diff --git a/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/codeGen/entityWithChildrenCollection/after/gen/impl/SimpleEntityImpl.kt b/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/codeGen/entityWithChildrenCollection/after/gen/impl/SimpleEntityImpl.kt
new file mode 100644
index 000000000000..2346e23264eb
--- /dev/null
+++ b/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/codeGen/entityWithChildrenCollection/after/gen/impl/SimpleEntityImpl.kt
@@ -0,0 +1,299 @@
+package com.intellij.workspaceModel.test.api.impl
+
+import com.intellij.platform.workspace.storage.ConnectionId
+import com.intellij.platform.workspace.storage.EntitySource
+import com.intellij.platform.workspace.storage.GeneratedCodeApiVersion
+import com.intellij.platform.workspace.storage.GeneratedCodeImplVersion
+import com.intellij.platform.workspace.storage.MutableEntityStorage
+import com.intellij.platform.workspace.storage.WorkspaceEntity
+import com.intellij.platform.workspace.storage.WorkspaceEntityInternalApi
+import com.intellij.platform.workspace.storage.annotations.Child
+import com.intellij.platform.workspace.storage.impl.EntityLink
+import com.intellij.platform.workspace.storage.impl.ModifiableWorkspaceEntityBase
+import com.intellij.platform.workspace.storage.impl.WorkspaceEntityBase
+import com.intellij.platform.workspace.storage.impl.WorkspaceEntityData
+import com.intellij.platform.workspace.storage.impl.extractOneToManyParent
+import com.intellij.platform.workspace.storage.impl.updateOneToManyParentOfChild
+import com.intellij.platform.workspace.storage.instrumentation.EntityStorageInstrumentation
+import com.intellij.platform.workspace.storage.instrumentation.EntityStorageInstrumentationApi
+import com.intellij.platform.workspace.storage.instrumentation.MutableEntityStorageInstrumentation
+import com.intellij.platform.workspace.storage.metadata.model.EntityMetadata
+import com.intellij.workspaceModel.test.api.ChildrenCollectionFieldEntity
+import com.intellij.workspaceModel.test.api.SimpleEntity
+
+@GeneratedCodeApiVersion(3)
+@GeneratedCodeImplVersion(6)
+@OptIn(WorkspaceEntityInternalApi::class)
+internal class SimpleEntityImpl(private val dataSource: SimpleEntityData) : SimpleEntity, WorkspaceEntityBase(dataSource) {
+
+ private companion object {
+ internal val PARENT_CONNECTION_ID: ConnectionId = ConnectionId.create(
+ ChildrenCollectionFieldEntity::class.java, SimpleEntity::class.java, ConnectionId.ConnectionType.ONE_TO_MANY, false
+ )
+
+ private val connections = listOf(
+ PARENT_CONNECTION_ID,
+ )
+
+ }
+
+ override val version: Int
+ get() {
+ readField("version")
+ return dataSource.version
+ }
+ override val name: String
+ get() {
+ readField("name")
+ return dataSource.name
+ }
+
+ override val isSimple: Boolean
+ get() {
+ readField("isSimple")
+ return dataSource.isSimple
+ }
+ override val parent: ChildrenCollectionFieldEntity
+ get() = snapshot.extractOneToManyParent(PARENT_CONNECTION_ID, this)!!
+
+ override val entitySource: EntitySource
+ get() {
+ readField("entitySource")
+ return dataSource.entitySource
+ }
+
+ override fun connectionIdList(): List {
+ return connections
+ }
+
+
+ internal class Builder(result: SimpleEntityData?) : ModifiableWorkspaceEntityBase(result),
+ SimpleEntity.Builder {
+ internal constructor() : this(SimpleEntityData())
+
+ override fun applyToBuilder(builder: MutableEntityStorage) {
+ if (this.diff != null) {
+ if (existsInBuilder(builder)) {
+ this.diff = builder
+ return
+ }
+ else {
+ error("Entity SimpleEntity is already created in a different builder")
+ }
+ }
+
+ this.diff = builder
+ addToBuilder()
+ this.id = getEntityData().createEntityId()
+ // After adding entity data to the builder, we need to unbind it and move the control over entity data to builder
+ // Builder may switch to snapshot at any moment and lock entity data to modification
+ this.currentEntityData = null
+
+ // Process linked entities that are connected without a builder
+ processLinkedEntities(builder)
+ checkInitialization() // TODO uncomment and check failed tests
+ }
+
+ private fun checkInitialization() {
+ val _diff = diff
+ if (!getEntityData().isEntitySourceInitialized()) {
+ error("Field WorkspaceEntity#entitySource should be initialized")
+ }
+ if (!getEntityData().isNameInitialized()) {
+ error("Field SimpleEntity#name should be initialized")
+ }
+ if (_diff != null) {
+ if (_diff.extractOneToManyParent(PARENT_CONNECTION_ID, this) == null) {
+ error("Field SimpleEntity#parent should be initialized")
+ }
+ }
+ else {
+ if (this.entityLinks[EntityLink(false, PARENT_CONNECTION_ID)] == null) {
+ error("Field SimpleEntity#parent should be initialized")
+ }
+ }
+ }
+
+ override fun connectionIdList(): List {
+ return connections
+ }
+
+ // Relabeling code, move information from dataSource to this builder
+ override fun relabel(dataSource: WorkspaceEntity, parents: Set?) {
+ dataSource as SimpleEntity
+ if (this.entitySource != dataSource.entitySource) this.entitySource = dataSource.entitySource
+ if (this.version != dataSource.version) this.version = dataSource.version
+ if (this.name != dataSource.name) this.name = dataSource.name
+ if (this.isSimple != dataSource.isSimple) this.isSimple = dataSource.isSimple
+ updateChildToParentReferences(parents)
+ }
+
+
+ override var entitySource: EntitySource
+ get() = getEntityData().entitySource
+ set(value) {
+ checkModificationAllowed()
+ getEntityData(true).entitySource = value
+ changedProperty.add("entitySource")
+
+ }
+
+ override var version: Int
+ get() = getEntityData().version
+ set(value) {
+ checkModificationAllowed()
+ getEntityData(true).version = value
+ changedProperty.add("version")
+ }
+
+ override var name: String
+ get() = getEntityData().name
+ set(value) {
+ checkModificationAllowed()
+ getEntityData(true).name = value
+ changedProperty.add("name")
+ }
+
+ override var isSimple: Boolean
+ get() = getEntityData().isSimple
+ set(value) {
+ checkModificationAllowed()
+ getEntityData(true).isSimple = value
+ changedProperty.add("isSimple")
+ }
+
+ override var parent: ChildrenCollectionFieldEntity.Builder
+ get() {
+ val _diff = diff
+ return if (_diff != null) {
+ @OptIn(EntityStorageInstrumentationApi::class)
+ ((_diff as MutableEntityStorageInstrumentation).getParentBuilder(
+ PARENT_CONNECTION_ID, this
+ ) as? ChildrenCollectionFieldEntity.Builder)
+ ?: (this.entityLinks[EntityLink(false, PARENT_CONNECTION_ID)]!! as ChildrenCollectionFieldEntity.Builder)
+ }
+ else {
+ this.entityLinks[EntityLink(false, PARENT_CONNECTION_ID)]!! as ChildrenCollectionFieldEntity.Builder
+ }
+ }
+ set(value) {
+ checkModificationAllowed()
+ val _diff = diff
+ if (_diff != null && value is ModifiableWorkspaceEntityBase<*, *> && value.diff == null) {
+ // Setting backref of the list
+ if (value is ModifiableWorkspaceEntityBase<*, *>) {
+ val data = (value.entityLinks[EntityLink(true, PARENT_CONNECTION_ID)] as? List ?: emptyList()) + this
+ value.entityLinks[EntityLink(true, PARENT_CONNECTION_ID)] = data
+ }
+ // else you're attaching a new entity to an existing entity that is not modifiable
+ _diff.addEntity(value as ModifiableWorkspaceEntityBase)
+ }
+ if (_diff != null && (value !is ModifiableWorkspaceEntityBase<*, *> || value.diff != null)) {
+ _diff.updateOneToManyParentOfChild(PARENT_CONNECTION_ID, this, value)
+ }
+ else {
+ // Setting backref of the list
+ if (value is ModifiableWorkspaceEntityBase<*, *>) {
+ val data = (value.entityLinks[EntityLink(true, PARENT_CONNECTION_ID)] as? List ?: emptyList()) + this
+ value.entityLinks[EntityLink(true, PARENT_CONNECTION_ID)] = data
+ }
+ // else you're attaching a new entity to an existing entity that is not modifiable
+
+ this.entityLinks[EntityLink(false, PARENT_CONNECTION_ID)] = value
+ }
+ changedProperty.add("parent")
+ }
+
+ override fun getEntityClass(): Class = SimpleEntity::class.java
+ }
+}
+
+@OptIn(WorkspaceEntityInternalApi::class)
+internal class SimpleEntityData : WorkspaceEntityData() {
+ var version: Int = 0
+ lateinit var name: String
+ var isSimple: Boolean = false
+
+
+ internal fun isNameInitialized(): Boolean = ::name.isInitialized
+
+
+ override fun wrapAsModifiable(diff: MutableEntityStorage): WorkspaceEntity.Builder {
+ val modifiable = SimpleEntityImpl.Builder(null)
+ modifiable.diff = diff
+ modifiable.id = createEntityId()
+ return modifiable
+ }
+
+ @OptIn(EntityStorageInstrumentationApi::class)
+ override fun createEntity(snapshot: EntityStorageInstrumentation): SimpleEntity {
+ val entityId = createEntityId()
+ return snapshot.initializeEntity(entityId) {
+ val entity = SimpleEntityImpl(this)
+ entity.snapshot = snapshot
+ entity.id = entityId
+ entity
+ }
+ }
+
+ override fun getMetadata(): EntityMetadata {
+ return MetadataStorageImpl.getMetadataByTypeFqn("com.intellij.workspaceModel.test.api.SimpleEntity") as EntityMetadata
+ }
+
+ override fun getEntityInterface(): Class {
+ return SimpleEntity::class.java
+ }
+
+ override fun createDetachedEntity(parents: List>): WorkspaceEntity.Builder<*> {
+ return SimpleEntity(version, name, isSimple, entitySource) {
+ parents.filterIsInstance().singleOrNull()?.let { this.parent = it }
+ }
+ }
+
+ override fun getRequiredParents(): List> {
+ val res = mutableListOf>()
+ res.add(ChildrenCollectionFieldEntity::class.java)
+ return res
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (other == null) return false
+ if (this.javaClass != other.javaClass) return false
+
+ other as SimpleEntityData
+
+ if (this.entitySource != other.entitySource) return false
+ if (this.version != other.version) return false
+ if (this.name != other.name) return false
+ if (this.isSimple != other.isSimple) return false
+ return true
+ }
+
+ override fun equalsIgnoringEntitySource(other: Any?): Boolean {
+ if (other == null) return false
+ if (this.javaClass != other.javaClass) return false
+
+ other as SimpleEntityData
+
+ if (this.version != other.version) return false
+ if (this.name != other.name) return false
+ if (this.isSimple != other.isSimple) return false
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = entitySource.hashCode()
+ result = 31 * result + version.hashCode()
+ result = 31 * result + name.hashCode()
+ result = 31 * result + isSimple.hashCode()
+ return result
+ }
+
+ override fun hashCodeIgnoringEntitySource(): Int {
+ var result = javaClass.hashCode()
+ result = 31 * result + version.hashCode()
+ result = 31 * result + name.hashCode()
+ result = 31 * result + isSimple.hashCode()
+ return result
+ }
+}
diff --git a/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/codeGen/entityWithChildrenCollection/before/entity.kt b/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/codeGen/entityWithChildrenCollection/before/entity.kt
new file mode 100644
index 000000000000..2f88d4fa944f
--- /dev/null
+++ b/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/codeGen/entityWithChildrenCollection/before/entity.kt
@@ -0,0 +1,16 @@
+package com.intellij.workspaceModel.test.api
+
+import com.intellij.platform.workspace.storage.WorkspaceEntity
+import com.intellij.platform.workspace.storage.annotations.Child
+
+interface ChildrenCollectionFieldEntity : WorkspaceEntity {
+ val name: String
+ val childrenEntitiesCollection: List<@Child SimpleEntity>
+}
+
+interface SimpleEntity : WorkspaceEntity {
+ val version: Int
+ val name: String
+ val isSimple: Boolean
+ val parent: ChildrenCollectionFieldEntity
+}
diff --git a/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/codeGen/entityWithCollections/after/entity.kt b/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/codeGen/entityWithCollections/after/entity.kt
index 034086df6844..453087628a5f 100644
--- a/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/codeGen/entityWithCollections/after/entity.kt
+++ b/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/codeGen/entityWithCollections/after/entity.kt
@@ -11,6 +11,7 @@ import com.intellij.platform.workspace.storage.impl.containers.toMutableWorkspac
interface CollectionFieldEntity : WorkspaceEntity {
val versions: Set
val names: List
+ val manifestAttributes: Map
//region generated code
@GeneratedCodeApiVersion(3)
@@ -18,6 +19,7 @@ interface CollectionFieldEntity : WorkspaceEntity {
override var entitySource: EntitySource
var versions: MutableSet
var names: MutableList
+ var manifestAttributes: Map
}
companion object : EntityType() {
@@ -27,12 +29,14 @@ interface CollectionFieldEntity : WorkspaceEntity {
operator fun invoke(
versions: Set,
names: List,
+ manifestAttributes: Map,
entitySource: EntitySource,
init: (Builder.() -> Unit)? = null,
): Builder {
val builder = builder()
builder.versions = versions.toMutableWorkspaceSet()
builder.names = names.toMutableWorkspaceList()
+ builder.manifestAttributes = manifestAttributes
builder.entitySource = entitySource
init?.invoke(builder)
return builder
diff --git a/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/codeGen/entityWithCollections/after/gen/impl/CollectionFieldEntityImpl.kt b/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/codeGen/entityWithCollections/after/gen/impl/CollectionFieldEntityImpl.kt
index fb66c257f937..037c64a1a92d 100644
--- a/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/codeGen/entityWithCollections/after/gen/impl/CollectionFieldEntityImpl.kt
+++ b/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/codeGen/entityWithCollections/after/gen/impl/CollectionFieldEntityImpl.kt
@@ -45,6 +45,12 @@ internal class CollectionFieldEntityImpl(private val dataSource: CollectionField
return dataSource.names
}
+ override val manifestAttributes: Map
+ get() {
+ readField("manifestAttributes")
+ return dataSource.manifestAttributes
+ }
+
override val entitySource: EntitySource
get() {
readField("entitySource")
@@ -94,6 +100,9 @@ internal class CollectionFieldEntityImpl(private val dataSource: CollectionField
if (!getEntityData().isNamesInitialized()) {
error("Field CollectionFieldEntity#names should be initialized")
}
+ if (!getEntityData().isManifestAttributesInitialized()) {
+ error("Field CollectionFieldEntity#manifestAttributes should be initialized")
+ }
}
override fun connectionIdList(): List {
@@ -117,6 +126,7 @@ internal class CollectionFieldEntityImpl(private val dataSource: CollectionField
if (this.entitySource != dataSource.entitySource) this.entitySource = dataSource.entitySource
if (this.versions != dataSource.versions) this.versions = dataSource.versions.toMutableSet()
if (this.names != dataSource.names) this.names = dataSource.names.toMutableList()
+ if (this.manifestAttributes != dataSource.manifestAttributes) this.manifestAttributes = dataSource.manifestAttributes.toMutableMap()
updateChildToParentReferences(parents)
}
@@ -174,6 +184,14 @@ internal class CollectionFieldEntityImpl(private val dataSource: CollectionField
namesUpdater.invoke(value)
}
+ override var manifestAttributes: Map
+ get() = getEntityData().manifestAttributes
+ set(value) {
+ checkModificationAllowed()
+ getEntityData(true).manifestAttributes = value
+ changedProperty.add("manifestAttributes")
+ }
+
override fun getEntityClass(): Class = CollectionFieldEntity::class.java
}
}
@@ -182,9 +200,11 @@ internal class CollectionFieldEntityImpl(private val dataSource: CollectionField
internal class CollectionFieldEntityData : WorkspaceEntityData() {
lateinit var versions: MutableSet
lateinit var names: MutableList
+ lateinit var manifestAttributes: Map
internal fun isVersionsInitialized(): Boolean = ::versions.isInitialized
internal fun isNamesInitialized(): Boolean = ::names.isInitialized
+ internal fun isManifestAttributesInitialized(): Boolean = ::manifestAttributes.isInitialized
override fun wrapAsModifiable(diff: MutableEntityStorage): WorkspaceEntity.Builder {
val modifiable = CollectionFieldEntityImpl.Builder(null)
@@ -221,7 +241,7 @@ internal class CollectionFieldEntityData : WorkspaceEntityData>): WorkspaceEntity.Builder<*> {
- return CollectionFieldEntity(versions, names, entitySource) {
+ return CollectionFieldEntity(versions, names, manifestAttributes, entitySource) {
}
}
@@ -239,6 +259,7 @@ internal class CollectionFieldEntityData : WorkspaceEntityData
val names: List
-}
\ No newline at end of file
+ val manifestAttributes: Map
+}
diff --git a/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/codeGen/entityWithDifferentChildrenTargets/after/entity.kt b/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/codeGen/entityWithDifferentChildrenTargets/after/entity.kt
new file mode 100644
index 000000000000..a64fc3221711
--- /dev/null
+++ b/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/codeGen/entityWithDifferentChildrenTargets/after/entity.kt
@@ -0,0 +1,130 @@
+package com.intellij.workspaceModel.test.api
+
+import com.intellij.platform.workspace.storage.EntitySource
+import com.intellij.platform.workspace.storage.EntityType
+import com.intellij.platform.workspace.storage.GeneratedCodeApiVersion
+import com.intellij.platform.workspace.storage.MutableEntityStorage
+import com.intellij.platform.workspace.storage.WorkspaceEntity
+import com.intellij.platform.workspace.storage.annotations.Child
+
+interface EntityWithChildren : WorkspaceEntity {
+ val name: String
+ val propertyChild: @Child ChildEntityType1?
+ @Child val typeChild: ChildEntityType2?
+
+ //region generated code
+ @GeneratedCodeApiVersion(3)
+ interface Builder : WorkspaceEntity.Builder {
+ override var entitySource: EntitySource
+ var name: String
+ var propertyChild: ChildEntityType1.Builder?
+ var typeChild: ChildEntityType2.Builder?
+ }
+
+ companion object : EntityType() {
+ @JvmOverloads
+ @JvmStatic
+ @JvmName("create")
+ operator fun invoke(
+ name: String,
+ entitySource: EntitySource,
+ init: (Builder.() -> Unit)? = null,
+ ): Builder {
+ val builder = builder()
+ builder.name = name
+ builder.entitySource = entitySource
+ init?.invoke(builder)
+ return builder
+ }
+ }
+ //endregion
+}
+
+//region generated code
+fun MutableEntityStorage.modifyEntityWithChildren(
+ entity: EntityWithChildren,
+ modification: EntityWithChildren.Builder.() -> Unit,
+): EntityWithChildren {
+ return modifyEntity(EntityWithChildren.Builder::class.java, entity, modification)
+}
+//endregion
+
+interface ChildEntityType1 : WorkspaceEntity {
+ val version: Int
+ val parent: EntityWithChildren
+
+ //region generated code
+ @GeneratedCodeApiVersion(3)
+ interface Builder : WorkspaceEntity.Builder {
+ override var entitySource: EntitySource
+ var version: Int
+ var parent: EntityWithChildren.Builder
+ }
+
+ companion object : EntityType() {
+ @JvmOverloads
+ @JvmStatic
+ @JvmName("create")
+ operator fun invoke(
+ version: Int,
+ entitySource: EntitySource,
+ init: (Builder.() -> Unit)? = null,
+ ): Builder {
+ val builder = builder()
+ builder.version = version
+ builder.entitySource = entitySource
+ init?.invoke(builder)
+ return builder
+ }
+ }
+ //endregion
+}
+
+//region generated code
+fun MutableEntityStorage.modifyChildEntityType1(
+ entity: ChildEntityType1,
+ modification: ChildEntityType1.Builder.() -> Unit,
+): ChildEntityType1 {
+ return modifyEntity(ChildEntityType1.Builder::class.java, entity, modification)
+}
+//endregion
+
+interface ChildEntityType2 : WorkspaceEntity {
+ val version: Int
+ val parent: EntityWithChildren
+
+ //region generated code
+ @GeneratedCodeApiVersion(3)
+ interface Builder : WorkspaceEntity.Builder {
+ override var entitySource: EntitySource
+ var version: Int
+ var parent: EntityWithChildren.Builder
+ }
+
+ companion object : EntityType() {
+ @JvmOverloads
+ @JvmStatic
+ @JvmName("create")
+ operator fun invoke(
+ version: Int,
+ entitySource: EntitySource,
+ init: (Builder.() -> Unit)? = null,
+ ): Builder {
+ val builder = builder()
+ builder.version = version
+ builder.entitySource = entitySource
+ init?.invoke(builder)
+ return builder
+ }
+ }
+ //endregion
+}
+
+//region generated code
+fun MutableEntityStorage.modifyChildEntityType2(
+ entity: ChildEntityType2,
+ modification: ChildEntityType2.Builder.() -> Unit,
+): ChildEntityType2 {
+ return modifyEntity(ChildEntityType2.Builder::class.java, entity, modification)
+}
+//endregion
diff --git a/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/codeGen/entityWithDifferentChildrenTargets/after/gen/impl/ChildEntityType1Impl.kt b/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/codeGen/entityWithDifferentChildrenTargets/after/gen/impl/ChildEntityType1Impl.kt
new file mode 100644
index 000000000000..5ee5bec4bb62
--- /dev/null
+++ b/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/codeGen/entityWithDifferentChildrenTargets/after/gen/impl/ChildEntityType1Impl.kt
@@ -0,0 +1,247 @@
+package com.intellij.workspaceModel.test.api.impl
+
+import com.intellij.platform.workspace.storage.ConnectionId
+import com.intellij.platform.workspace.storage.EntitySource
+import com.intellij.platform.workspace.storage.GeneratedCodeApiVersion
+import com.intellij.platform.workspace.storage.GeneratedCodeImplVersion
+import com.intellij.platform.workspace.storage.MutableEntityStorage
+import com.intellij.platform.workspace.storage.WorkspaceEntity
+import com.intellij.platform.workspace.storage.WorkspaceEntityInternalApi
+import com.intellij.platform.workspace.storage.annotations.Child
+import com.intellij.platform.workspace.storage.impl.EntityLink
+import com.intellij.platform.workspace.storage.impl.ModifiableWorkspaceEntityBase
+import com.intellij.platform.workspace.storage.impl.WorkspaceEntityBase
+import com.intellij.platform.workspace.storage.impl.WorkspaceEntityData
+import com.intellij.platform.workspace.storage.impl.extractOneToOneParent
+import com.intellij.platform.workspace.storage.impl.updateOneToOneParentOfChild
+import com.intellij.platform.workspace.storage.instrumentation.EntityStorageInstrumentation
+import com.intellij.platform.workspace.storage.instrumentation.EntityStorageInstrumentationApi
+import com.intellij.platform.workspace.storage.instrumentation.MutableEntityStorageInstrumentation
+import com.intellij.platform.workspace.storage.metadata.model.EntityMetadata
+import com.intellij.workspaceModel.test.api.ChildEntityType1
+import com.intellij.workspaceModel.test.api.EntityWithChildren
+
+@GeneratedCodeApiVersion(3)
+@GeneratedCodeImplVersion(6)
+@OptIn(WorkspaceEntityInternalApi::class)
+internal class ChildEntityType1Impl(private val dataSource: ChildEntityType1Data) : ChildEntityType1, WorkspaceEntityBase(dataSource) {
+
+ private companion object {
+ internal val PARENT_CONNECTION_ID: ConnectionId =
+ ConnectionId.create(EntityWithChildren::class.java, ChildEntityType1::class.java, ConnectionId.ConnectionType.ONE_TO_ONE, false)
+
+ private val connections = listOf(
+ PARENT_CONNECTION_ID,
+ )
+
+ }
+
+ override val version: Int
+ get() {
+ readField("version")
+ return dataSource.version
+ }
+ override val parent: EntityWithChildren
+ get() = snapshot.extractOneToOneParent(PARENT_CONNECTION_ID, this)!!
+
+ override val entitySource: EntitySource
+ get() {
+ readField("entitySource")
+ return dataSource.entitySource
+ }
+
+ override fun connectionIdList(): List {
+ return connections
+ }
+
+
+ internal class Builder(result: ChildEntityType1Data?) : ModifiableWorkspaceEntityBase(result),
+ ChildEntityType1.Builder {
+ internal constructor() : this(ChildEntityType1Data())
+
+ override fun applyToBuilder(builder: MutableEntityStorage) {
+ if (this.diff != null) {
+ if (existsInBuilder(builder)) {
+ this.diff = builder
+ return
+ }
+ else {
+ error("Entity ChildEntityType1 is already created in a different builder")
+ }
+ }
+
+ this.diff = builder
+ addToBuilder()
+ this.id = getEntityData().createEntityId()
+ // After adding entity data to the builder, we need to unbind it and move the control over entity data to builder
+ // Builder may switch to snapshot at any moment and lock entity data to modification
+ this.currentEntityData = null
+
+ // Process linked entities that are connected without a builder
+ processLinkedEntities(builder)
+ checkInitialization() // TODO uncomment and check failed tests
+ }
+
+ private fun checkInitialization() {
+ val _diff = diff
+ if (!getEntityData().isEntitySourceInitialized()) {
+ error("Field WorkspaceEntity#entitySource should be initialized")
+ }
+ if (_diff != null) {
+ if (_diff.extractOneToOneParent(PARENT_CONNECTION_ID, this) == null) {
+ error("Field ChildEntityType1#parent should be initialized")
+ }
+ }
+ else {
+ if (this.entityLinks[EntityLink(false, PARENT_CONNECTION_ID)] == null) {
+ error("Field ChildEntityType1#parent should be initialized")
+ }
+ }
+ }
+
+ override fun connectionIdList(): List {
+ return connections
+ }
+
+ // Relabeling code, move information from dataSource to this builder
+ override fun relabel(dataSource: WorkspaceEntity, parents: Set?) {
+ dataSource as ChildEntityType1
+ if (this.entitySource != dataSource.entitySource) this.entitySource = dataSource.entitySource
+ if (this.version != dataSource.version) this.version = dataSource.version
+ updateChildToParentReferences(parents)
+ }
+
+
+ override var entitySource: EntitySource
+ get() = getEntityData().entitySource
+ set(value) {
+ checkModificationAllowed()
+ getEntityData(true).entitySource = value
+ changedProperty.add("entitySource")
+
+ }
+
+ override var version: Int
+ get() = getEntityData().version
+ set(value) {
+ checkModificationAllowed()
+ getEntityData(true).version = value
+ changedProperty.add("version")
+ }
+
+ override var parent: EntityWithChildren.Builder
+ get() {
+ val _diff = diff
+ return if (_diff != null) {
+ @OptIn(EntityStorageInstrumentationApi::class)
+ ((_diff as MutableEntityStorageInstrumentation).getParentBuilder(PARENT_CONNECTION_ID, this) as? EntityWithChildren.Builder)
+ ?: (this.entityLinks[EntityLink(false, PARENT_CONNECTION_ID)]!! as EntityWithChildren.Builder)
+ }
+ else {
+ this.entityLinks[EntityLink(false, PARENT_CONNECTION_ID)]!! as EntityWithChildren.Builder
+ }
+ }
+ set(value) {
+ checkModificationAllowed()
+ val _diff = diff
+ if (_diff != null && value is ModifiableWorkspaceEntityBase<*, *> && value.diff == null) {
+ if (value is ModifiableWorkspaceEntityBase<*, *>) {
+ value.entityLinks[EntityLink(true, PARENT_CONNECTION_ID)] = this
+ }
+ // else you're attaching a new entity to an existing entity that is not modifiable
+ _diff.addEntity(value as ModifiableWorkspaceEntityBase)
+ }
+ if (_diff != null && (value !is ModifiableWorkspaceEntityBase<*, *> || value.diff != null)) {
+ _diff.updateOneToOneParentOfChild(PARENT_CONNECTION_ID, this, value)
+ }
+ else {
+ if (value is ModifiableWorkspaceEntityBase<*, *>) {
+ value.entityLinks[EntityLink(true, PARENT_CONNECTION_ID)] = this
+ }
+ // else you're attaching a new entity to an existing entity that is not modifiable
+
+ this.entityLinks[EntityLink(false, PARENT_CONNECTION_ID)] = value
+ }
+ changedProperty.add("parent")
+ }
+
+ override fun getEntityClass(): Class = ChildEntityType1::class.java
+ }
+}
+
+@OptIn(WorkspaceEntityInternalApi::class)
+internal class ChildEntityType1Data : WorkspaceEntityData() {
+ var version: Int = 0
+
+
+ override fun wrapAsModifiable(diff: MutableEntityStorage): WorkspaceEntity.Builder {
+ val modifiable = ChildEntityType1Impl.Builder(null)
+ modifiable.diff = diff
+ modifiable.id = createEntityId()
+ return modifiable
+ }
+
+ @OptIn(EntityStorageInstrumentationApi::class)
+ override fun createEntity(snapshot: EntityStorageInstrumentation): ChildEntityType1 {
+ val entityId = createEntityId()
+ return snapshot.initializeEntity(entityId) {
+ val entity = ChildEntityType1Impl(this)
+ entity.snapshot = snapshot
+ entity.id = entityId
+ entity
+ }
+ }
+
+ override fun getMetadata(): EntityMetadata {
+ return MetadataStorageImpl.getMetadataByTypeFqn("com.intellij.workspaceModel.test.api.ChildEntityType1") as EntityMetadata
+ }
+
+ override fun getEntityInterface(): Class {
+ return ChildEntityType1::class.java
+ }
+
+ override fun createDetachedEntity(parents: List>): WorkspaceEntity.Builder<*> {
+ return ChildEntityType1(version, entitySource) {
+ parents.filterIsInstance().singleOrNull()?.let { this.parent = it }
+ }
+ }
+
+ override fun getRequiredParents(): List> {
+ val res = mutableListOf>()
+ res.add(EntityWithChildren::class.java)
+ return res
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (other == null) return false
+ if (this.javaClass != other.javaClass) return false
+
+ other as ChildEntityType1Data
+
+ if (this.entitySource != other.entitySource) return false
+ if (this.version != other.version) return false
+ return true
+ }
+
+ override fun equalsIgnoringEntitySource(other: Any?): Boolean {
+ if (other == null) return false
+ if (this.javaClass != other.javaClass) return false
+
+ other as ChildEntityType1Data
+
+ if (this.version != other.version) return false
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = entitySource.hashCode()
+ result = 31 * result + version.hashCode()
+ return result
+ }
+
+ override fun hashCodeIgnoringEntitySource(): Int {
+ var result = javaClass.hashCode()
+ result = 31 * result + version.hashCode()
+ return result
+ }
+}
diff --git a/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/codeGen/entityWithDifferentChildrenTargets/after/gen/impl/ChildEntityType2Impl.kt b/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/codeGen/entityWithDifferentChildrenTargets/after/gen/impl/ChildEntityType2Impl.kt
new file mode 100644
index 000000000000..34a2cab1e487
--- /dev/null
+++ b/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/codeGen/entityWithDifferentChildrenTargets/after/gen/impl/ChildEntityType2Impl.kt
@@ -0,0 +1,247 @@
+package com.intellij.workspaceModel.test.api.impl
+
+import com.intellij.platform.workspace.storage.ConnectionId
+import com.intellij.platform.workspace.storage.EntitySource
+import com.intellij.platform.workspace.storage.GeneratedCodeApiVersion
+import com.intellij.platform.workspace.storage.GeneratedCodeImplVersion
+import com.intellij.platform.workspace.storage.MutableEntityStorage
+import com.intellij.platform.workspace.storage.WorkspaceEntity
+import com.intellij.platform.workspace.storage.WorkspaceEntityInternalApi
+import com.intellij.platform.workspace.storage.annotations.Child
+import com.intellij.platform.workspace.storage.impl.EntityLink
+import com.intellij.platform.workspace.storage.impl.ModifiableWorkspaceEntityBase
+import com.intellij.platform.workspace.storage.impl.WorkspaceEntityBase
+import com.intellij.platform.workspace.storage.impl.WorkspaceEntityData
+import com.intellij.platform.workspace.storage.impl.extractOneToOneParent
+import com.intellij.platform.workspace.storage.impl.updateOneToOneParentOfChild
+import com.intellij.platform.workspace.storage.instrumentation.EntityStorageInstrumentation
+import com.intellij.platform.workspace.storage.instrumentation.EntityStorageInstrumentationApi
+import com.intellij.platform.workspace.storage.instrumentation.MutableEntityStorageInstrumentation
+import com.intellij.platform.workspace.storage.metadata.model.EntityMetadata
+import com.intellij.workspaceModel.test.api.ChildEntityType2
+import com.intellij.workspaceModel.test.api.EntityWithChildren
+
+@GeneratedCodeApiVersion(3)
+@GeneratedCodeImplVersion(6)
+@OptIn(WorkspaceEntityInternalApi::class)
+internal class ChildEntityType2Impl(private val dataSource: ChildEntityType2Data) : ChildEntityType2, WorkspaceEntityBase(dataSource) {
+
+ private companion object {
+ internal val PARENT_CONNECTION_ID: ConnectionId =
+ ConnectionId.create(EntityWithChildren::class.java, ChildEntityType2::class.java, ConnectionId.ConnectionType.ONE_TO_ONE, false)
+
+ private val connections = listOf(
+ PARENT_CONNECTION_ID,
+ )
+
+ }
+
+ override val version: Int
+ get() {
+ readField("version")
+ return dataSource.version
+ }
+ override val parent: EntityWithChildren
+ get() = snapshot.extractOneToOneParent(PARENT_CONNECTION_ID, this)!!
+
+ override val entitySource: EntitySource
+ get() {
+ readField("entitySource")
+ return dataSource.entitySource
+ }
+
+ override fun connectionIdList(): List {
+ return connections
+ }
+
+
+ internal class Builder(result: ChildEntityType2Data?) : ModifiableWorkspaceEntityBase(result),
+ ChildEntityType2.Builder {
+ internal constructor() : this(ChildEntityType2Data())
+
+ override fun applyToBuilder(builder: MutableEntityStorage) {
+ if (this.diff != null) {
+ if (existsInBuilder(builder)) {
+ this.diff = builder
+ return
+ }
+ else {
+ error("Entity ChildEntityType2 is already created in a different builder")
+ }
+ }
+
+ this.diff = builder
+ addToBuilder()
+ this.id = getEntityData().createEntityId()
+ // After adding entity data to the builder, we need to unbind it and move the control over entity data to builder
+ // Builder may switch to snapshot at any moment and lock entity data to modification
+ this.currentEntityData = null
+
+ // Process linked entities that are connected without a builder
+ processLinkedEntities(builder)
+ checkInitialization() // TODO uncomment and check failed tests
+ }
+
+ private fun checkInitialization() {
+ val _diff = diff
+ if (!getEntityData().isEntitySourceInitialized()) {
+ error("Field WorkspaceEntity#entitySource should be initialized")
+ }
+ if (_diff != null) {
+ if (_diff.extractOneToOneParent(PARENT_CONNECTION_ID, this) == null) {
+ error("Field ChildEntityType2#parent should be initialized")
+ }
+ }
+ else {
+ if (this.entityLinks[EntityLink(false, PARENT_CONNECTION_ID)] == null) {
+ error("Field ChildEntityType2#parent should be initialized")
+ }
+ }
+ }
+
+ override fun connectionIdList(): List {
+ return connections
+ }
+
+ // Relabeling code, move information from dataSource to this builder
+ override fun relabel(dataSource: WorkspaceEntity, parents: Set?) {
+ dataSource as ChildEntityType2
+ if (this.entitySource != dataSource.entitySource) this.entitySource = dataSource.entitySource
+ if (this.version != dataSource.version) this.version = dataSource.version
+ updateChildToParentReferences(parents)
+ }
+
+
+ override var entitySource: EntitySource
+ get() = getEntityData().entitySource
+ set(value) {
+ checkModificationAllowed()
+ getEntityData(true).entitySource = value
+ changedProperty.add("entitySource")
+
+ }
+
+ override var version: Int
+ get() = getEntityData().version
+ set(value) {
+ checkModificationAllowed()
+ getEntityData(true).version = value
+ changedProperty.add("version")
+ }
+
+ override var parent: EntityWithChildren.Builder
+ get() {
+ val _diff = diff
+ return if (_diff != null) {
+ @OptIn(EntityStorageInstrumentationApi::class)
+ ((_diff as MutableEntityStorageInstrumentation).getParentBuilder(PARENT_CONNECTION_ID, this) as? EntityWithChildren.Builder)
+ ?: (this.entityLinks[EntityLink(false, PARENT_CONNECTION_ID)]!! as EntityWithChildren.Builder)
+ }
+ else {
+ this.entityLinks[EntityLink(false, PARENT_CONNECTION_ID)]!! as EntityWithChildren.Builder
+ }
+ }
+ set(value) {
+ checkModificationAllowed()
+ val _diff = diff
+ if (_diff != null && value is ModifiableWorkspaceEntityBase<*, *> && value.diff == null) {
+ if (value is ModifiableWorkspaceEntityBase<*, *>) {
+ value.entityLinks[EntityLink(true, PARENT_CONNECTION_ID)] = this
+ }
+ // else you're attaching a new entity to an existing entity that is not modifiable
+ _diff.addEntity(value as ModifiableWorkspaceEntityBase)
+ }
+ if (_diff != null && (value !is ModifiableWorkspaceEntityBase<*, *> || value.diff != null)) {
+ _diff.updateOneToOneParentOfChild(PARENT_CONNECTION_ID, this, value)
+ }
+ else {
+ if (value is ModifiableWorkspaceEntityBase<*, *>) {
+ value.entityLinks[EntityLink(true, PARENT_CONNECTION_ID)] = this
+ }
+ // else you're attaching a new entity to an existing entity that is not modifiable
+
+ this.entityLinks[EntityLink(false, PARENT_CONNECTION_ID)] = value
+ }
+ changedProperty.add("parent")
+ }
+
+ override fun getEntityClass(): Class = ChildEntityType2::class.java
+ }
+}
+
+@OptIn(WorkspaceEntityInternalApi::class)
+internal class ChildEntityType2Data : WorkspaceEntityData() {
+ var version: Int = 0
+
+
+ override fun wrapAsModifiable(diff: MutableEntityStorage): WorkspaceEntity.Builder {
+ val modifiable = ChildEntityType2Impl.Builder(null)
+ modifiable.diff = diff
+ modifiable.id = createEntityId()
+ return modifiable
+ }
+
+ @OptIn(EntityStorageInstrumentationApi::class)
+ override fun createEntity(snapshot: EntityStorageInstrumentation): ChildEntityType2 {
+ val entityId = createEntityId()
+ return snapshot.initializeEntity(entityId) {
+ val entity = ChildEntityType2Impl(this)
+ entity.snapshot = snapshot
+ entity.id = entityId
+ entity
+ }
+ }
+
+ override fun getMetadata(): EntityMetadata {
+ return MetadataStorageImpl.getMetadataByTypeFqn("com.intellij.workspaceModel.test.api.ChildEntityType2") as EntityMetadata
+ }
+
+ override fun getEntityInterface(): Class {
+ return ChildEntityType2::class.java
+ }
+
+ override fun createDetachedEntity(parents: List>): WorkspaceEntity.Builder<*> {
+ return ChildEntityType2(version, entitySource) {
+ parents.filterIsInstance().singleOrNull()?.let { this.parent = it }
+ }
+ }
+
+ override fun getRequiredParents(): List> {
+ val res = mutableListOf>()
+ res.add(EntityWithChildren::class.java)
+ return res
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (other == null) return false
+ if (this.javaClass != other.javaClass) return false
+
+ other as ChildEntityType2Data
+
+ if (this.entitySource != other.entitySource) return false
+ if (this.version != other.version) return false
+ return true
+ }
+
+ override fun equalsIgnoringEntitySource(other: Any?): Boolean {
+ if (other == null) return false
+ if (this.javaClass != other.javaClass) return false
+
+ other as ChildEntityType2Data
+
+ if (this.version != other.version) return false
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = entitySource.hashCode()
+ result = 31 * result + version.hashCode()
+ return result
+ }
+
+ override fun hashCodeIgnoringEntitySource(): Int {
+ var result = javaClass.hashCode()
+ result = 31 * result + version.hashCode()
+ return result
+ }
+}
diff --git a/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/codeGen/entityWithDifferentChildrenTargets/after/gen/impl/EntityWithChildrenImpl.kt b/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/codeGen/entityWithDifferentChildrenTargets/after/gen/impl/EntityWithChildrenImpl.kt
new file mode 100644
index 000000000000..cc5cc4e7e6f8
--- /dev/null
+++ b/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/codeGen/entityWithDifferentChildrenTargets/after/gen/impl/EntityWithChildrenImpl.kt
@@ -0,0 +1,286 @@
+package com.intellij.workspaceModel.test.api.impl
+
+import com.intellij.platform.workspace.storage.ConnectionId
+import com.intellij.platform.workspace.storage.EntitySource
+import com.intellij.platform.workspace.storage.GeneratedCodeApiVersion
+import com.intellij.platform.workspace.storage.GeneratedCodeImplVersion
+import com.intellij.platform.workspace.storage.MutableEntityStorage
+import com.intellij.platform.workspace.storage.WorkspaceEntity
+import com.intellij.platform.workspace.storage.WorkspaceEntityInternalApi
+import com.intellij.platform.workspace.storage.annotations.Child
+import com.intellij.platform.workspace.storage.impl.EntityLink
+import com.intellij.platform.workspace.storage.impl.ModifiableWorkspaceEntityBase
+import com.intellij.platform.workspace.storage.impl.WorkspaceEntityBase
+import com.intellij.platform.workspace.storage.impl.WorkspaceEntityData
+import com.intellij.platform.workspace.storage.impl.extractOneToOneChild
+import com.intellij.platform.workspace.storage.impl.updateOneToOneChildOfParent
+import com.intellij.platform.workspace.storage.instrumentation.EntityStorageInstrumentation
+import com.intellij.platform.workspace.storage.instrumentation.EntityStorageInstrumentationApi
+import com.intellij.platform.workspace.storage.instrumentation.MutableEntityStorageInstrumentation
+import com.intellij.platform.workspace.storage.metadata.model.EntityMetadata
+import com.intellij.workspaceModel.test.api.ChildEntityType1
+import com.intellij.workspaceModel.test.api.ChildEntityType2
+import com.intellij.workspaceModel.test.api.EntityWithChildren
+
+@GeneratedCodeApiVersion(3)
+@GeneratedCodeImplVersion(6)
+@OptIn(WorkspaceEntityInternalApi::class)
+internal class EntityWithChildrenImpl(private val dataSource: EntityWithChildrenData) : EntityWithChildren,
+ WorkspaceEntityBase(dataSource) {
+
+ private companion object {
+ internal val PROPERTYCHILD_CONNECTION_ID: ConnectionId =
+ ConnectionId.create(EntityWithChildren::class.java, ChildEntityType1::class.java, ConnectionId.ConnectionType.ONE_TO_ONE, false)
+ internal val TYPECHILD_CONNECTION_ID: ConnectionId =
+ ConnectionId.create(EntityWithChildren::class.java, ChildEntityType2::class.java, ConnectionId.ConnectionType.ONE_TO_ONE, false)
+
+ private val connections = listOf(
+ PROPERTYCHILD_CONNECTION_ID,
+ TYPECHILD_CONNECTION_ID,
+ )
+
+ }
+
+ override val name: String
+ get() {
+ readField("name")
+ return dataSource.name
+ }
+
+ override val propertyChild: ChildEntityType1?
+ get() = snapshot.extractOneToOneChild(PROPERTYCHILD_CONNECTION_ID, this)
+
+ override val typeChild: ChildEntityType2?
+ get() = snapshot.extractOneToOneChild(TYPECHILD_CONNECTION_ID, this)
+
+ override val entitySource: EntitySource
+ get() {
+ readField("entitySource")
+ return dataSource.entitySource
+ }
+
+ override fun connectionIdList(): List {
+ return connections
+ }
+
+
+ internal class Builder(result: EntityWithChildrenData?) :
+ ModifiableWorkspaceEntityBase(result), EntityWithChildren.Builder {
+ internal constructor() : this(EntityWithChildrenData())
+
+ override fun applyToBuilder(builder: MutableEntityStorage) {
+ if (this.diff != null) {
+ if (existsInBuilder(builder)) {
+ this.diff = builder
+ return
+ }
+ else {
+ error("Entity EntityWithChildren is already created in a different builder")
+ }
+ }
+
+ this.diff = builder
+ addToBuilder()
+ this.id = getEntityData().createEntityId()
+ // After adding entity data to the builder, we need to unbind it and move the control over entity data to builder
+ // Builder may switch to snapshot at any moment and lock entity data to modification
+ this.currentEntityData = null
+
+ // Process linked entities that are connected without a builder
+ processLinkedEntities(builder)
+ checkInitialization() // TODO uncomment and check failed tests
+ }
+
+ private fun checkInitialization() {
+ val _diff = diff
+ if (!getEntityData().isEntitySourceInitialized()) {
+ error("Field WorkspaceEntity#entitySource should be initialized")
+ }
+ if (!getEntityData().isNameInitialized()) {
+ error("Field EntityWithChildren#name should be initialized")
+ }
+ }
+
+ override fun connectionIdList(): List {
+ return connections
+ }
+
+ // Relabeling code, move information from dataSource to this builder
+ override fun relabel(dataSource: WorkspaceEntity, parents: Set?) {
+ dataSource as EntityWithChildren
+ if (this.entitySource != dataSource.entitySource) this.entitySource = dataSource.entitySource
+ if (this.name != dataSource.name) this.name = dataSource.name
+ updateChildToParentReferences(parents)
+ }
+
+
+ override var entitySource: EntitySource
+ get() = getEntityData().entitySource
+ set(value) {
+ checkModificationAllowed()
+ getEntityData(true).entitySource = value
+ changedProperty.add("entitySource")
+
+ }
+
+ override var name: String
+ get() = getEntityData().name
+ set(value) {
+ checkModificationAllowed()
+ getEntityData(true).name = value
+ changedProperty.add("name")
+ }
+
+ override var propertyChild: ChildEntityType1.Builder?
+ get() {
+ val _diff = diff
+ return if (_diff != null) {
+ @OptIn(EntityStorageInstrumentationApi::class)
+ ((_diff as MutableEntityStorageInstrumentation).getOneChildBuilder(
+ PROPERTYCHILD_CONNECTION_ID, this
+ ) as? ChildEntityType1.Builder)
+ ?: (this.entityLinks[EntityLink(true, PROPERTYCHILD_CONNECTION_ID)] as? ChildEntityType1.Builder)
+ }
+ else {
+ this.entityLinks[EntityLink(true, PROPERTYCHILD_CONNECTION_ID)] as? ChildEntityType1.Builder
+ }
+ }
+ set(value) {
+ checkModificationAllowed()
+ val _diff = diff
+ if (_diff != null && value is ModifiableWorkspaceEntityBase<*, *> && value.diff == null) {
+ if (value is ModifiableWorkspaceEntityBase<*, *>) {
+ value.entityLinks[EntityLink(false, PROPERTYCHILD_CONNECTION_ID)] = this
+ }
+ // else you're attaching a new entity to an existing entity that is not modifiable
+ _diff.addEntity(value as ModifiableWorkspaceEntityBase)
+ }
+ if (_diff != null && (value !is ModifiableWorkspaceEntityBase<*, *> || value.diff != null)) {
+ _diff.updateOneToOneChildOfParent(PROPERTYCHILD_CONNECTION_ID, this, value)
+ }
+ else {
+ if (value is ModifiableWorkspaceEntityBase<*, *>) {
+ value.entityLinks[EntityLink(false, PROPERTYCHILD_CONNECTION_ID)] = this
+ }
+ // else you're attaching a new entity to an existing entity that is not modifiable
+
+ this.entityLinks[EntityLink(true, PROPERTYCHILD_CONNECTION_ID)] = value
+ }
+ changedProperty.add("propertyChild")
+ }
+
+ override var typeChild: ChildEntityType2.Builder?
+ get() {
+ val _diff = diff
+ return if (_diff != null) {
+ @OptIn(EntityStorageInstrumentationApi::class)
+ ((_diff as MutableEntityStorageInstrumentation).getOneChildBuilder(TYPECHILD_CONNECTION_ID, this) as? ChildEntityType2.Builder)
+ ?: (this.entityLinks[EntityLink(true, TYPECHILD_CONNECTION_ID)] as? ChildEntityType2.Builder)
+ }
+ else {
+ this.entityLinks[EntityLink(true, TYPECHILD_CONNECTION_ID)] as? ChildEntityType2.Builder
+ }
+ }
+ set(value) {
+ checkModificationAllowed()
+ val _diff = diff
+ if (_diff != null && value is ModifiableWorkspaceEntityBase<*, *> && value.diff == null) {
+ if (value is ModifiableWorkspaceEntityBase<*, *>) {
+ value.entityLinks[EntityLink(false, TYPECHILD_CONNECTION_ID)] = this
+ }
+ // else you're attaching a new entity to an existing entity that is not modifiable
+ _diff.addEntity(value as ModifiableWorkspaceEntityBase)
+ }
+ if (_diff != null && (value !is ModifiableWorkspaceEntityBase<*, *> || value.diff != null)) {
+ _diff.updateOneToOneChildOfParent(TYPECHILD_CONNECTION_ID, this, value)
+ }
+ else {
+ if (value is ModifiableWorkspaceEntityBase<*, *>) {
+ value.entityLinks[EntityLink(false, TYPECHILD_CONNECTION_ID)] = this
+ }
+ // else you're attaching a new entity to an existing entity that is not modifiable
+
+ this.entityLinks[EntityLink(true, TYPECHILD_CONNECTION_ID)] = value
+ }
+ changedProperty.add("typeChild")
+ }
+
+ override fun getEntityClass(): Class