[Gradle] Ide Dependency Resolution: Provide attributes for binary coordinates
^KT-60053 Verification Pending
This commit is contained in:
committed by
Space Team
parent
6140854fa6
commit
3032d02ce9
@@ -298,34 +298,45 @@ public final class org/jetbrains/kotlin/gradle/idea/serialize/IdeaKotlinStringEx
|
||||
public fun serialize (Lorg/jetbrains/kotlin/gradle/idea/serialize/IdeaKotlinSerializationContext;Ljava/lang/String;)[B
|
||||
}
|
||||
|
||||
public final class org/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinBinaryCapability : java/io/Serializable {
|
||||
public static final field serialVersionUID J
|
||||
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
|
||||
public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lorg/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinBinaryCapability;
|
||||
public abstract interface class org/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinBinaryAttributes : java/io/Serializable, java/util/Map, kotlin/jvm/internal/markers/KMappedMarker {
|
||||
}
|
||||
|
||||
public final class org/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinBinaryAttributesKt {
|
||||
public static final fun IdeaKotlinBinaryAttributes ()Lorg/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinBinaryAttributes;
|
||||
public static final fun IdeaKotlinBinaryAttributes (Ljava/util/Map;)Lorg/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinBinaryAttributes;
|
||||
}
|
||||
|
||||
public abstract interface class org/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinBinaryCapability : java/io/Serializable {
|
||||
public abstract fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lorg/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinBinaryCapability;
|
||||
public abstract fun getGroup ()Ljava/lang/String;
|
||||
public abstract fun getName ()Ljava/lang/String;
|
||||
public abstract fun getVersion ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class org/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinBinaryCapability$DefaultImpls {
|
||||
public static synthetic fun copy$default (Lorg/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinBinaryCapability;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lorg/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinBinaryCapability;
|
||||
public fun equals (Ljava/lang/Object;)Z
|
||||
public final fun getGroup ()Ljava/lang/String;
|
||||
public final fun getName ()Ljava/lang/String;
|
||||
public final fun getVersion ()Ljava/lang/String;
|
||||
public fun hashCode ()I
|
||||
public fun toString ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class org/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinBinaryCapabilityKt {
|
||||
public static final fun IdeaKotlinBinaryCapability (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lorg/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinBinaryCapability;
|
||||
}
|
||||
|
||||
public final class org/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinBinaryCoordinates : org/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinDependencyCoordinates {
|
||||
public static final field serialVersionUID J
|
||||
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
|
||||
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Set;)V
|
||||
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Set;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Set;Lorg/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinBinaryAttributes;)V
|
||||
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Set;Lorg/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinBinaryAttributes;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public final fun component1 ()Ljava/lang/String;
|
||||
public final fun component2 ()Ljava/lang/String;
|
||||
public final fun component3 ()Ljava/lang/String;
|
||||
public final fun component4 ()Ljava/lang/String;
|
||||
public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lorg/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinBinaryCoordinates;
|
||||
public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Set;)Lorg/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinBinaryCoordinates;
|
||||
public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Set;Lorg/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinBinaryAttributes;)Lorg/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinBinaryCoordinates;
|
||||
public static synthetic fun copy$default (Lorg/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinBinaryCoordinates;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lorg/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinBinaryCoordinates;
|
||||
public static synthetic fun copy$default (Lorg/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinBinaryCoordinates;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Set;ILjava/lang/Object;)Lorg/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinBinaryCoordinates;
|
||||
public static synthetic fun copy$default (Lorg/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinBinaryCoordinates;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Set;Lorg/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinBinaryAttributes;ILjava/lang/Object;)Lorg/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinBinaryCoordinates;
|
||||
public fun equals (Ljava/lang/Object;)Z
|
||||
public final fun getAttributes ()Lorg/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinBinaryAttributes;
|
||||
public final fun getCapabilities ()Ljava/util/Set;
|
||||
public final fun getDisplayString ()Ljava/lang/String;
|
||||
public final fun getGroup ()Ljava/lang/String;
|
||||
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.gradle.idea.tcs
|
||||
|
||||
import org.jetbrains.kotlin.gradle.idea.utils.stringInterner
|
||||
import org.jetbrains.kotlin.tooling.core.WeakInterner
|
||||
import java.io.Serializable
|
||||
|
||||
fun IdeaKotlinBinaryAttributes(attributes: Map<String, String>): IdeaKotlinBinaryAttributes {
|
||||
return IdeaKotlinBinaryAttributesImpl.createAndIntern(attributes)
|
||||
}
|
||||
|
||||
fun IdeaKotlinBinaryAttributes(): IdeaKotlinBinaryAttributes {
|
||||
return IdeaKotlinBinaryAttributesImpl.createAndIntern(emptyMap())
|
||||
}
|
||||
|
||||
/**
|
||||
* Representing the 'Gradle attributes' associated with a given binary.
|
||||
* This can be used to identify binaries further than just their maven coordinates.
|
||||
*/
|
||||
@IdeaKotlinModel
|
||||
sealed interface IdeaKotlinBinaryAttributes : Map<String, String>, Serializable
|
||||
|
||||
private class IdeaKotlinBinaryAttributesImpl private constructor(
|
||||
private val attributes: Map<String, String>,
|
||||
) : IdeaKotlinBinaryAttributes, Map<String, String> by attributes {
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other === this) return true
|
||||
if (other !is IdeaKotlinBinaryAttributesImpl) return false
|
||||
return this.attributes == other.attributes
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return attributes.hashCode()
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return attributes.toString()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val serialVersionUID = 0L
|
||||
|
||||
private val interner = WeakInterner()
|
||||
|
||||
internal fun createAndIntern(attributes: Map<String, String>): IdeaKotlinBinaryAttributes {
|
||||
val internedMap = attributes.map { (key, value) -> stringInterner.getOrPut(key) to stringInterner.getOrPut(value) }.toMap()
|
||||
return interner.getOrPut(IdeaKotlinBinaryAttributesImpl(internedMap))
|
||||
}
|
||||
}
|
||||
}
|
||||
+31
-10
@@ -5,8 +5,17 @@
|
||||
|
||||
package org.jetbrains.kotlin.gradle.idea.tcs
|
||||
|
||||
import org.jetbrains.kotlin.gradle.idea.utils.stringInterner
|
||||
import java.io.Serializable
|
||||
|
||||
fun IdeaKotlinBinaryCapability(group: String, name: String, version: String?): IdeaKotlinBinaryCapability {
|
||||
return IdeaKotlinBinaryCapabilityImpl(
|
||||
group = stringInterner.getOrPut(group),
|
||||
name = stringInterner.getOrPut(name),
|
||||
version = if (version != null) stringInterner.getOrPut(version) else null
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Several variants can be published under a given set of maven coordinates (group, module, version),
|
||||
* Capabilities can classify those variants in a way that e.g. two variants with the same maven coordinates
|
||||
@@ -21,12 +30,24 @@ import java.io.Serializable
|
||||
* @since 1.9.20
|
||||
*/
|
||||
@IdeaKotlinModel
|
||||
class IdeaKotlinBinaryCapability(
|
||||
val group: String,
|
||||
val name: String,
|
||||
val version: String?,
|
||||
) : Serializable {
|
||||
internal companion object {
|
||||
sealed interface IdeaKotlinBinaryCapability : Serializable {
|
||||
val group: String
|
||||
val name: String
|
||||
val version: String?
|
||||
|
||||
fun copy(
|
||||
group: String = this.group,
|
||||
name: String = this.name,
|
||||
version: String? = this.version,
|
||||
): IdeaKotlinBinaryCapability
|
||||
}
|
||||
|
||||
private class IdeaKotlinBinaryCapabilityImpl(
|
||||
override val group: String,
|
||||
override val name: String,
|
||||
override val version: String?,
|
||||
) : IdeaKotlinBinaryCapability {
|
||||
companion object {
|
||||
const val serialVersionUID = 0L
|
||||
}
|
||||
|
||||
@@ -37,10 +58,10 @@ class IdeaKotlinBinaryCapability(
|
||||
}
|
||||
}
|
||||
|
||||
fun copy(
|
||||
group: String = this.group,
|
||||
name: String = this.name,
|
||||
version: String? = this.version,
|
||||
override fun copy(
|
||||
group: String,
|
||||
name: String,
|
||||
version: String?,
|
||||
): IdeaKotlinBinaryCapability {
|
||||
return IdeaKotlinBinaryCapability(group = group, name = name, version = version)
|
||||
}
|
||||
|
||||
+23
-6
@@ -23,6 +23,12 @@ class IdeaKotlinBinaryCoordinates(
|
||||
* @since 1.9.20
|
||||
*/
|
||||
val capabilities: Set<IdeaKotlinBinaryCapability> = emptySet(),
|
||||
|
||||
/**
|
||||
* @see IdeaKotlinBinaryAttributes
|
||||
* @since 1.9.20
|
||||
*/
|
||||
val attributes: IdeaKotlinBinaryAttributes = IdeaKotlinBinaryAttributes(),
|
||||
) : IdeaKotlinDependencyCoordinates {
|
||||
|
||||
constructor(
|
||||
@@ -55,6 +61,9 @@ class IdeaKotlinBinaryCoordinates(
|
||||
if (capabilities.isNotEmpty()) {
|
||||
append(capabilities.joinToString(", ", "(", ")"))
|
||||
}
|
||||
if (attributes.isNotEmpty()) {
|
||||
append("+attributes(${attributes.hashCode()})")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -119,7 +128,7 @@ class IdeaKotlinBinaryCoordinates(
|
||||
if (version != other.version) return false
|
||||
if (sourceSetName != other.sourceSetName) return false
|
||||
if (capabilities != other.capabilities) return false
|
||||
|
||||
if (attributes != other.attributes) return false
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -129,6 +138,7 @@ class IdeaKotlinBinaryCoordinates(
|
||||
result = 31 * result + (version?.hashCode() ?: 0)
|
||||
result = 31 * result + (sourceSetName?.hashCode() ?: 0)
|
||||
result = 31 * result + capabilities.hashCode()
|
||||
result = 31 * result + attributes.hashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -146,7 +156,8 @@ class IdeaKotlinBinaryCoordinates(
|
||||
module = module,
|
||||
version = version,
|
||||
sourceSetName = sourceSetName,
|
||||
capabilities = capabilities
|
||||
capabilities = capabilities,
|
||||
attributes = attributes,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -156,13 +167,15 @@ class IdeaKotlinBinaryCoordinates(
|
||||
version: String? = this.version,
|
||||
sourceSetName: String? = this.sourceSetName,
|
||||
capabilities: Set<IdeaKotlinBinaryCapability> = this.capabilities,
|
||||
attributes: IdeaKotlinBinaryAttributes = this.attributes,
|
||||
): IdeaKotlinBinaryCoordinates {
|
||||
return IdeaKotlinBinaryCoordinates(
|
||||
group = group,
|
||||
module = module,
|
||||
version = version,
|
||||
sourceSetName = sourceSetName,
|
||||
capabilities = capabilities
|
||||
capabilities = capabilities,
|
||||
attributes = attributes
|
||||
)
|
||||
}
|
||||
|
||||
@@ -184,10 +197,14 @@ class IdeaKotlinBinaryCoordinates(
|
||||
* will be 'null'. In this case we use the 'copy' function to provide an instance that will have an emptySet instead.
|
||||
*/
|
||||
private fun readResolve(): Any {
|
||||
@Suppress("SENSELESS_COMPARISON")
|
||||
if (capabilities == null) {
|
||||
return copy(capabilities = emptySet())
|
||||
@Suppress("SENSELESS_COMPARISON", "USELESS_ELVIS")
|
||||
if (capabilities == null || attributes == null) {
|
||||
return copy(
|
||||
capabilities = capabilities ?: emptySet(),
|
||||
attributes = attributes ?: IdeaKotlinBinaryAttributes()
|
||||
)
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.gradle.idea.utils
|
||||
|
||||
import org.jetbrains.kotlin.tooling.core.WeakInterner
|
||||
|
||||
/**
|
||||
* Can be used for all entities in this module to intern strings
|
||||
*/
|
||||
internal val stringInterner = WeakInterner()
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.gradle.idea.test.tcs
|
||||
|
||||
import org.jetbrains.kotlin.gradle.idea.tcs.IdeaKotlinBinaryAttributes
|
||||
import kotlin.test.*
|
||||
|
||||
class IdeaKotlinBinaryAttributesTest {
|
||||
@Test
|
||||
fun `test - empty instance is same`() {
|
||||
val instance1 = IdeaKotlinBinaryAttributes()
|
||||
val instance2 = IdeaKotlinBinaryAttributes()
|
||||
assertSame(instance1, instance2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test - equal instances are same`() {
|
||||
val instance1 = IdeaKotlinBinaryAttributes(
|
||||
mapOf("a" to "valueA", "b" to "valueB")
|
||||
)
|
||||
|
||||
val instance2 = IdeaKotlinBinaryAttributes(
|
||||
mapOf("a" to "valueA", "b" to "valueB")
|
||||
)
|
||||
|
||||
assertSame(instance1, instance2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test - instances string values are same`() {
|
||||
val instance1 = IdeaKotlinBinaryAttributes(
|
||||
mapOf("a" to "valueA", "b" to "valueB", "c" to "xxx")
|
||||
)
|
||||
|
||||
val instance2 = IdeaKotlinBinaryAttributes(
|
||||
mapOf("a" to "valueA", "b" to "valueB", "c" to "yyy")
|
||||
)
|
||||
|
||||
assertNotSame(instance1, instance2)
|
||||
assertNotEquals(instance1, instance2)
|
||||
|
||||
instance1.keys.toList().forEachIndexed { index, key ->
|
||||
assertSame(key, instance2.keys.toList()[index])
|
||||
}
|
||||
|
||||
assertSame(instance1["a"], instance2["a"])
|
||||
assertSame(instance1["b"], instance2["b"])
|
||||
assertEquals("valueA", instance1["a"])
|
||||
assertEquals("valueB", instance1["b"])
|
||||
assertEquals("xxx", instance1["c"])
|
||||
assertEquals("yyy", instance2["c"])
|
||||
}
|
||||
}
|
||||
+43
-3
@@ -5,10 +5,13 @@
|
||||
|
||||
package org.jetbrains.kotlin.gradle.idea.test.tcs
|
||||
|
||||
import org.jetbrains.kotlin.gradle.idea.tcs.IdeaKotlinBinaryAttributes
|
||||
import org.jetbrains.kotlin.gradle.idea.tcs.IdeaKotlinBinaryCapability
|
||||
import org.jetbrains.kotlin.gradle.idea.tcs.IdeaKotlinBinaryCoordinates
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotEquals
|
||||
import kotlin.test.assertNotSame
|
||||
|
||||
class IdeaKotlinBinaryCoordinatesTest {
|
||||
|
||||
@@ -108,7 +111,7 @@ class IdeaKotlinBinaryCoordinatesTest {
|
||||
assertEquals("myGroup:myModule-foo:1.0.0:commonMain", coordinates.displayString)
|
||||
|
||||
assertEquals(
|
||||
"myGroup:myModule:1.0.0:commonMain(myGroup:myModule-foo:1.0.0, notMyGroup:myModule-foo:1.0.0, notMyGroupEither:myModule-foo)",
|
||||
"myGroup:myModule:commonMain:1.0.0(myGroup:myModule-foo:1.0.0, notMyGroup:myModule-foo:1.0.0, notMyGroupEither:myModule-foo)",
|
||||
coordinates.identityString
|
||||
)
|
||||
}
|
||||
@@ -123,11 +126,48 @@ class IdeaKotlinBinaryCoordinatesTest {
|
||||
IdeaKotlinBinaryCapability("notMyGroup", "myModule-foo", null),
|
||||
)
|
||||
)
|
||||
assertEquals("myGroup:myModule-(foo, bar):1.0.0:commonMain", coordinates.displayString)
|
||||
assertEquals("myGroup:myModule-(foo, bar):commonMain:1.0.0", coordinates.displayString)
|
||||
|
||||
assertEquals(
|
||||
"myGroup:myModule:1.0.0:commonMain(myGroup:myModule-foo:1.0.0, myGroup:myModule-bar:1.0.0, notMyGroup:myModule-foo)",
|
||||
"myGroup:myModule:commonMain:1.0.0(myGroup:myModule-foo:1.0.0, myGroup:myModule-bar:1.0.0, notMyGroup:myModule-foo)",
|
||||
coordinates.identityString
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test - displayString & identityString - sample 9`() {
|
||||
val coordinates = IdeaKotlinBinaryCoordinates(
|
||||
"myGroup", "myModule", "1.0.0", "commonMain",
|
||||
capabilities = setOf(
|
||||
IdeaKotlinBinaryCapability("myGroup", "myModule-foo", "1.0.0"),
|
||||
IdeaKotlinBinaryCapability("myGroup", "myModule-bar", "1.0.0"),
|
||||
IdeaKotlinBinaryCapability("notMyGroup", "myModule-foo", null),
|
||||
),
|
||||
attributes = IdeaKotlinBinaryAttributes(
|
||||
mapOf("a" to "valueA")
|
||||
)
|
||||
)
|
||||
assertEquals("myGroup:myModule-(foo, bar):commonMain:1.0.0", coordinates.displayString)
|
||||
|
||||
assertEquals(
|
||||
"myGroup:myModule:commonMain:1.0.0(myGroup:myModule-foo:1.0.0, myGroup:myModule-bar:1.0.0, notMyGroup:myModule-foo)+attributes(-823812975)",
|
||||
coordinates.identityString
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test - equals`() {
|
||||
val baseline = IdeaKotlinBinaryCoordinates(
|
||||
"a", "b", "c", "d",
|
||||
capabilities = setOf(IdeaKotlinBinaryCapability("x", "y", "z")),
|
||||
attributes = IdeaKotlinBinaryAttributes(mapOf("a" to "valueA"))
|
||||
)
|
||||
assertNotSame(baseline, baseline.copy())
|
||||
assertEquals(baseline, baseline.copy())
|
||||
assertEquals(baseline.hashCode(), baseline.copy().hashCode())
|
||||
assertEquals(baseline.identityString, baseline.copy().identityString)
|
||||
assertNotEquals(baseline, baseline.copy(sourceSetName = null))
|
||||
assertNotEquals(baseline, baseline.copy(capabilities = emptySet()))
|
||||
assertNotEquals(baseline, baseline.copy(attributes = IdeaKotlinBinaryAttributes()))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user