Name synthetic files after layout names
This commit is contained in:
+3
-1
@@ -22,7 +22,9 @@ import org.jetbrains.kotlin.lexer.JetTokens
|
|||||||
|
|
||||||
public object AndroidConst {
|
public object AndroidConst {
|
||||||
val ANDROID_USER_PACKAGE: Key<String> = Key.create<String>("ANDROID_USER_PACKAGE")
|
val ANDROID_USER_PACKAGE: Key<String> = Key.create<String>("ANDROID_USER_PACKAGE")
|
||||||
val SYNTHETIC_FILENAME: String = "ANDROIDXML"
|
val SYNTHETIC_FILENAME_PREFIX: String = "ANDROIDXML_"
|
||||||
|
val LAYOUT_POSTFIX: String = "_LAYOUT"
|
||||||
|
val VIEW_LAYOUT_POSTFIX: String = "_VIEW"
|
||||||
val SYNTHETIC_PACKAGE: String = "kotlinx.android.synthetic"
|
val SYNTHETIC_PACKAGE: String = "kotlinx.android.synthetic"
|
||||||
val SYNTHETIC_PACKAGE_PATH_LENGTH = SYNTHETIC_PACKAGE.count { it == '.' } + 1
|
val SYNTHETIC_PACKAGE_PATH_LENGTH = SYNTHETIC_PACKAGE.count { it == '.' } + 1
|
||||||
|
|
||||||
|
|||||||
+36
-18
@@ -54,6 +54,8 @@ import kotlin.properties.*
|
|||||||
import com.intellij.psi.impl.*
|
import com.intellij.psi.impl.*
|
||||||
import org.jetbrains.kotlin.types.Flexibility
|
import org.jetbrains.kotlin.types.Flexibility
|
||||||
|
|
||||||
|
public class AndroidSyntheticFile(val name: String, val contents: String)
|
||||||
|
|
||||||
public abstract class AndroidUIXmlProcessor(protected val project: Project) {
|
public abstract class AndroidUIXmlProcessor(protected val project: Project) {
|
||||||
|
|
||||||
public class NoAndroidManifestFound : Exception("No android manifest file found in project root")
|
public class NoAndroidManifestFound : Exception("No android manifest file found in project root")
|
||||||
@@ -62,15 +64,16 @@ public abstract class AndroidUIXmlProcessor(protected val project: Project) {
|
|||||||
|
|
||||||
public abstract val resourceManager: AndroidResourceManager
|
public abstract val resourceManager: AndroidResourceManager
|
||||||
|
|
||||||
protected abstract val cachedSources: CachedValue<List<String>>
|
protected abstract val cachedSources: CachedValue<List<AndroidSyntheticFile>>
|
||||||
|
|
||||||
private val cachedJetFiles: CachedValue<List<JetFile>> by Delegates.lazy {
|
private val cachedJetFiles: CachedValue<List<JetFile>> by Delegates.lazy {
|
||||||
cachedValue {
|
cachedValue {
|
||||||
val psiManager = PsiManager.getInstance(project)
|
val psiManager = PsiManager.getInstance(project)
|
||||||
val applicationPackage = resourceManager.androidModuleInfo?.applicationPackage
|
val applicationPackage = resourceManager.androidModuleInfo?.applicationPackage
|
||||||
|
|
||||||
val jetFiles = cachedSources.getValue().mapIndexed { index, text ->
|
val jetFiles = cachedSources.getValue().mapIndexed { index, syntheticFile ->
|
||||||
val virtualFile = LightVirtualFile(AndroidConst.SYNTHETIC_FILENAME + index + ".kt", text)
|
val fileName = AndroidConst.SYNTHETIC_FILENAME_PREFIX + syntheticFile.name + ".kt"
|
||||||
|
val virtualFile = LightVirtualFile(fileName, syntheticFile.contents)
|
||||||
val jetFile = psiManager.findFile(virtualFile) as JetFile
|
val jetFile = psiManager.findFile(virtualFile) as JetFile
|
||||||
if (applicationPackage != null) {
|
if (applicationPackage != null) {
|
||||||
jetFile.putUserData(AndroidConst.ANDROID_USER_PACKAGE, applicationPackage)
|
jetFile.putUserData(AndroidConst.ANDROID_USER_PACKAGE, applicationPackage)
|
||||||
@@ -82,10 +85,14 @@ public abstract class AndroidUIXmlProcessor(protected val project: Project) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun parse(generateCommonFiles: Boolean = true): List<String> {
|
public fun parse(generateCommonFiles: Boolean = true): List<AndroidSyntheticFile> {
|
||||||
val commonFiles = if (generateCommonFiles) {
|
val commonFiles = if (generateCommonFiles) {
|
||||||
val clearCacheFile = renderLayoutFile(AndroidConst.SYNTHETIC_PACKAGE) {} +
|
val clearCacheFile = renderSyntheticFile("clearCache") {
|
||||||
renderClearCacheFunction("android.app.Activity") + renderClearCacheFunction("android.app.Fragment")
|
writePackage(AndroidConst.SYNTHETIC_PACKAGE)
|
||||||
|
writeAndroidImports()
|
||||||
|
writeClearCacheFunction("android.app.Activity")
|
||||||
|
writeClearCacheFunction("android.app.Fragment")
|
||||||
|
}
|
||||||
|
|
||||||
listOf(clearCacheFile,
|
listOf(clearCacheFile,
|
||||||
FLEXIBLE_TYPE_FILE,
|
FLEXIBLE_TYPE_FILE,
|
||||||
@@ -98,14 +105,15 @@ public abstract class AndroidUIXmlProcessor(protected val project: Project) {
|
|||||||
val files = entry.getValue()
|
val files = entry.getValue()
|
||||||
val widgets = parseLayout(files)
|
val widgets = parseLayout(files)
|
||||||
|
|
||||||
val layoutPackage = files[0].genSyntheticPackageName()
|
val layoutName = files[0].getName().substringBefore('.')
|
||||||
|
val packageName = AndroidConst.SYNTHETIC_PACKAGE + "." + files[0].getEscapedLayoutName()
|
||||||
|
|
||||||
val mainLayoutFile = renderLayoutFile(layoutPackage, widgets) {
|
val mainLayoutFile = renderLayoutFile(layoutName + AndroidConst.LAYOUT_POSTFIX, packageName, widgets) {
|
||||||
writeSyntheticProperty("android.app.Activity", it, "findViewById(0)")
|
writeSyntheticProperty("android.app.Activity", it, "findViewById(0)")
|
||||||
writeSyntheticProperty("android.app.Fragment", it, "getView().findViewById(0)")
|
writeSyntheticProperty("android.app.Fragment", it, "getView().findViewById(0)")
|
||||||
}
|
}
|
||||||
|
|
||||||
val viewLayoutFile = renderLayoutFile("$layoutPackage.view", widgets) {
|
val viewLayoutFile = renderLayoutFile(layoutName + AndroidConst.VIEW_LAYOUT_POSTFIX, "$packageName.view", widgets) {
|
||||||
writeSyntheticProperty("android.view.View", it, "findViewById(0)")
|
writeSyntheticProperty("android.view.View", it, "findViewById(0)")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,16 +126,23 @@ public abstract class AndroidUIXmlProcessor(protected val project: Project) {
|
|||||||
protected abstract fun parseLayout(files: List<PsiFile>): List<AndroidWidget>
|
protected abstract fun parseLayout(files: List<PsiFile>): List<AndroidWidget>
|
||||||
|
|
||||||
private fun renderLayoutFile(
|
private fun renderLayoutFile(
|
||||||
|
name: String,
|
||||||
packageName: String,
|
packageName: String,
|
||||||
widgets: List<AndroidWidget> = listOf(),
|
widgets: List<AndroidWidget> = listOf(),
|
||||||
widgetWriter: KotlinStringWriter.(AndroidWidget) -> Unit
|
widgetWriter: KotlinStringWriter.(AndroidWidget) -> Unit
|
||||||
): String {
|
): AndroidSyntheticFile {
|
||||||
val stringWriter = KotlinStringWriter()
|
val stringWriter = KotlinStringWriter()
|
||||||
stringWriter.writePackage(packageName)
|
stringWriter.writePackage(packageName)
|
||||||
stringWriter.writeAndroidImports()
|
stringWriter.writeAndroidImports()
|
||||||
widgets.forEach { stringWriter.widgetWriter(it) }
|
widgets.forEach { stringWriter.widgetWriter(it) }
|
||||||
|
|
||||||
return stringWriter.toStringBuffer().toString()
|
return AndroidSyntheticFile(name, stringWriter.toStringBuffer().toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderSyntheticFile(filename: String, init: KotlinStringWriter.() -> Unit): AndroidSyntheticFile {
|
||||||
|
val stringWriter = KotlinStringWriter()
|
||||||
|
stringWriter.init()
|
||||||
|
return AndroidSyntheticFile(filename, stringWriter.toStringBuffer().toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun KotlinStringWriter.writeAndroidImports() {
|
private fun KotlinStringWriter.writeAndroidImports() {
|
||||||
@@ -135,8 +150,8 @@ public abstract class AndroidUIXmlProcessor(protected val project: Project) {
|
|||||||
writeEmptyLine()
|
writeEmptyLine()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun PsiFile.genSyntheticPackageName(): String {
|
private fun PsiFile.getEscapedLayoutName(): String {
|
||||||
return AndroidConst.SYNTHETIC_PACKAGE + "." + escapeAndroidIdentifier(getName().substringBefore('.'))
|
return escapeAndroidIdentifier(getName().substringBefore('.'))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun KotlinStringWriter.writeSyntheticProperty(receiver: String, widget: AndroidWidget, stubCall: String) {
|
private fun KotlinStringWriter.writeSyntheticProperty(receiver: String, widget: AndroidWidget, stubCall: String) {
|
||||||
@@ -149,7 +164,9 @@ public abstract class AndroidUIXmlProcessor(protected val project: Project) {
|
|||||||
getterBody = body)
|
getterBody = body)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderClearCacheFunction(receiver: String) = "public fun $receiver.${AndroidConst.CLEAR_FUNCTION_NAME}() {}\n"
|
private fun KotlinStringWriter.writeClearCacheFunction(receiver: String) {
|
||||||
|
writeText("public fun $receiver.${AndroidConst.CLEAR_FUNCTION_NAME}() {}\n")
|
||||||
|
}
|
||||||
|
|
||||||
protected fun <T> cachedValue(result: () -> CachedValueProvider.Result<T>): CachedValue<T> {
|
protected fun <T> cachedValue(result: () -> CachedValueProvider.Result<T>): CachedValue<T> {
|
||||||
return CachedValuesManager.getManager(project).createCachedValue(result, false)
|
return CachedValuesManager.getManager(project).createCachedValue(result, false)
|
||||||
@@ -187,13 +204,14 @@ public abstract class AndroidUIXmlProcessor(protected val project: Project) {
|
|||||||
"android.support.v4.widget.*",
|
"android.support.v4.widget.*",
|
||||||
Flexibility.FLEXIBLE_TYPE_CLASSIFIER.asSingleFqName().asString())
|
Flexibility.FLEXIBLE_TYPE_CLASSIFIER.asSingleFqName().asString())
|
||||||
|
|
||||||
private val FLEXIBLE_TYPE_FILE = "package $EXPLICIT_FLEXIBLE_PACKAGE\n\nclass $EXPLICIT_FLEXIBLE_CLASS_NAME<L, U>"
|
private val FLEXIBLE_TYPE_FILE =
|
||||||
|
AndroidSyntheticFile("ft", "package $EXPLICIT_FLEXIBLE_PACKAGE\n\nclass $EXPLICIT_FLEXIBLE_CLASS_NAME<L, U>")
|
||||||
|
|
||||||
private val FAKE_SUPPORT_V4_WIDGET_FILE = "package android.support.v4.widget"
|
private val FAKE_SUPPORT_V4_WIDGET_FILE = AndroidSyntheticFile("supportv4_widget", "package android.support.v4.widget")
|
||||||
|
|
||||||
private val FAKE_SUPPORT_V4_VIEW_FILE = "package android.support.v4.view"
|
private val FAKE_SUPPORT_V4_VIEW_FILE = AndroidSyntheticFile("supportv4_view", "package android.support.v4.view")
|
||||||
|
|
||||||
private val FAKE_SUPPORT_V4_APP_FILE = "package android.support.v4.app"
|
private val FAKE_SUPPORT_V4_APP_FILE = AndroidSyntheticFile("supportv4_app", "package android.support.v4.app")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+1
-1
@@ -37,7 +37,7 @@ public class CliAndroidUIXmlProcessor(
|
|||||||
CliAndroidResourceManager(project, manifestPath, mainResDirectory)
|
CliAndroidResourceManager(project, manifestPath, mainResDirectory)
|
||||||
}
|
}
|
||||||
|
|
||||||
override val cachedSources: CachedValue<List<String>> by Delegates.lazy {
|
override val cachedSources: CachedValue<List<AndroidSyntheticFile>> by Delegates.lazy {
|
||||||
cachedValue {
|
cachedValue {
|
||||||
Result.create(parse(), ModificationTracker.NEVER_CHANGED)
|
Result.create(parse(), ModificationTracker.NEVER_CHANGED)
|
||||||
}
|
}
|
||||||
|
|||||||
+4
@@ -69,6 +69,10 @@ class KotlinStringWriter : KotlinWriter {
|
|||||||
body.newLine()
|
body.newLine()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun writeText(text: String) {
|
||||||
|
body.writeln(text)
|
||||||
|
}
|
||||||
|
|
||||||
override fun toStringBuffer(): StringBuffer {
|
override fun toStringBuffer(): StringBuffer {
|
||||||
ctx.absorbChildren()
|
ctx.absorbChildren()
|
||||||
return ctx.buffer
|
return ctx.buffer
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package kotlinx.android.synthetic.layout
|
package kotlinx.android.synthetic.test
|
||||||
|
|
||||||
import android.app.*
|
import android.app.*
|
||||||
import android.view.*
|
import android.view.*
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package kotlinx.android.synthetic.layout.view
|
package kotlinx.android.synthetic.test.view
|
||||||
|
|
||||||
import android.app.*
|
import android.app.*
|
||||||
import android.view.*
|
import android.view.*
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package kotlinx.android.synthetic.layout
|
package kotlinx.android.synthetic.test
|
||||||
|
|
||||||
import android.app.*
|
import android.app.*
|
||||||
import android.view.*
|
import android.view.*
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package kotlinx.android.synthetic.layout.view
|
package kotlinx.android.synthetic.test.view
|
||||||
|
|
||||||
import android.app.*
|
import android.app.*
|
||||||
import android.view.*
|
import android.view.*
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package kotlinx.android.synthetic.layout
|
package kotlinx.android.synthetic.test
|
||||||
|
|
||||||
import android.app.*
|
import android.app.*
|
||||||
import android.view.*
|
import android.view.*
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package kotlinx.android.synthetic.layout.view
|
package kotlinx.android.synthetic.test.view
|
||||||
|
|
||||||
import android.app.*
|
import android.app.*
|
||||||
import android.view.*
|
import android.view.*
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package kotlinx.android.synthetic.layout1
|
package kotlinx.android.synthetic.test1
|
||||||
|
|
||||||
import android.app.*
|
import android.app.*
|
||||||
import android.view.*
|
import android.view.*
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package kotlinx.android.synthetic.layout1.view
|
package kotlinx.android.synthetic.test1.view
|
||||||
|
|
||||||
import android.app.*
|
import android.app.*
|
||||||
import android.view.*
|
import android.view.*
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package kotlinx.android.synthetic.layout
|
package kotlinx.android.synthetic.test
|
||||||
|
|
||||||
import android.app.*
|
import android.app.*
|
||||||
import android.view.*
|
import android.view.*
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package kotlinx.android.synthetic.layout.view
|
package kotlinx.android.synthetic.test.view
|
||||||
|
|
||||||
import android.app.*
|
import android.app.*
|
||||||
import android.view.*
|
import android.view.*
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package kotlinx.android.synthetic.layout
|
package kotlinx.android.synthetic.test
|
||||||
|
|
||||||
import android.app.*
|
import android.app.*
|
||||||
import android.view.*
|
import android.view.*
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package kotlinx.android.synthetic.layout.view
|
package kotlinx.android.synthetic.test.view
|
||||||
|
|
||||||
import android.app.*
|
import android.app.*
|
||||||
import android.view.*
|
import android.view.*
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package kotlinx.android.synthetic.layout
|
package kotlinx.android.synthetic.test
|
||||||
|
|
||||||
import android.app.*
|
import android.app.*
|
||||||
import android.view.*
|
import android.view.*
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package kotlinx.android.synthetic.layout.view
|
package kotlinx.android.synthetic.test.view
|
||||||
|
|
||||||
import android.app.*
|
import android.app.*
|
||||||
import android.view.*
|
import android.view.*
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package kotlinx.android.synthetic.layout
|
package kotlinx.android.synthetic.test
|
||||||
|
|
||||||
import android.app.*
|
import android.app.*
|
||||||
import android.view.*
|
import android.view.*
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package kotlinx.android.synthetic.layout.view
|
package kotlinx.android.synthetic.test.view
|
||||||
|
|
||||||
import android.app.*
|
import android.app.*
|
||||||
import android.view.*
|
import android.view.*
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package kotlinx.android.synthetic.layout
|
package kotlinx.android.synthetic.test
|
||||||
|
|
||||||
import android.app.*
|
import android.app.*
|
||||||
import android.view.*
|
import android.view.*
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package kotlinx.android.synthetic.layout.view
|
package kotlinx.android.synthetic.test.view
|
||||||
|
|
||||||
import android.app.*
|
import android.app.*
|
||||||
import android.view.*
|
import android.view.*
|
||||||
+12
-9
@@ -23,6 +23,7 @@ import org.jetbrains.kotlin.test.JetTestUtils
|
|||||||
import org.jetbrains.kotlin.test.ConfigurationKind
|
import org.jetbrains.kotlin.test.ConfigurationKind
|
||||||
import org.jetbrains.kotlin.test.TestJdkKind
|
import org.jetbrains.kotlin.test.TestJdkKind
|
||||||
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
|
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
|
||||||
|
import org.jetbrains.kotlin.lang.resolve.android.AndroidConst
|
||||||
import org.jetbrains.kotlin.lang.resolve.android.CliAndroidUIXmlProcessor
|
import org.jetbrains.kotlin.lang.resolve.android.CliAndroidUIXmlProcessor
|
||||||
import org.jetbrains.kotlin.lang.resolve.android.AndroidUIXmlProcessor
|
import org.jetbrains.kotlin.lang.resolve.android.AndroidUIXmlProcessor
|
||||||
import kotlin.test.*
|
import kotlin.test.*
|
||||||
@@ -30,19 +31,21 @@ import kotlin.test.*
|
|||||||
public abstract class AbstractAndroidXml2KConversionTest : UsefulTestCase() {
|
public abstract class AbstractAndroidXml2KConversionTest : UsefulTestCase() {
|
||||||
|
|
||||||
public fun doTest(path: String) {
|
public fun doTest(path: String) {
|
||||||
val jetCoreEnvironment = getEnvironment(path)
|
val jetCoreEnvironment = getEnvironment()
|
||||||
val parser = CliAndroidUIXmlProcessor(jetCoreEnvironment.project, path + "AndroidManifest.xml", path + "/res")
|
val parser = CliAndroidUIXmlProcessor(jetCoreEnvironment.project, path + "AndroidManifest.xml", path + "/res")
|
||||||
|
|
||||||
val actual = parser.parse(false)
|
val actual = parser.parse(false).toMap { it.name }
|
||||||
|
|
||||||
val layoutFiles = File(path).listFiles {
|
val expectedLayoutFiles = File(path).listFiles {
|
||||||
it.isFile() && it.name.startsWith("layout") && it.name.endsWith(".kt")
|
it.isFile() && it.name.endsWith(".kt")
|
||||||
}?.sortBy { it.name } ?: listOf()
|
}?.toMap { it.name.substringBefore(".kt") } ?: mapOf()
|
||||||
|
|
||||||
assertEquals(layoutFiles.size(), actual.size())
|
assertEquals(expectedLayoutFiles.size(), actual.size())
|
||||||
|
|
||||||
for ((index, file) in layoutFiles.withIndex()) {
|
for ((name, file) in expectedLayoutFiles) {
|
||||||
JetTestUtils.assertEqualsToFile(file, actual[index])
|
val actualContents = actual[name]
|
||||||
|
assertNotNull(actualContents, "File $name was not generated")
|
||||||
|
JetTestUtils.assertEqualsToFile(file, actualContents!!.contents)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +58,7 @@ public abstract class AbstractAndroidXml2KConversionTest : UsefulTestCase() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getEnvironment(testPath: String): KotlinCoreEnvironment {
|
private fun getEnvironment(): KotlinCoreEnvironment {
|
||||||
val configuration = JetTestUtils.compilerConfigurationForTests(ConfigurationKind.ALL, TestJdkKind.MOCK_JDK)
|
val configuration = JetTestUtils.compilerConfigurationForTests(ConfigurationKind.ALL, TestJdkKind.MOCK_JDK)
|
||||||
return KotlinCoreEnvironment.createForTests(getTestRootDisposable()!!, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES)
|
return KotlinCoreEnvironment.createForTests(getTestRootDisposable()!!, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -35,7 +35,7 @@ class IDEAndroidUIXmlProcessor(val module: Module) : AndroidUIXmlProcessor(modul
|
|||||||
module.getProject().getExtensions(PsiTreeChangePreprocessor.EP_NAME).first { it is AndroidPsiTreeChangePreprocessor }
|
module.getProject().getExtensions(PsiTreeChangePreprocessor.EP_NAME).first { it is AndroidPsiTreeChangePreprocessor }
|
||||||
}
|
}
|
||||||
|
|
||||||
override val cachedSources: CachedValue<List<String>> by Delegates.lazy {
|
override val cachedSources: CachedValue<List<AndroidSyntheticFile>> by Delegates.lazy {
|
||||||
cachedValue {
|
cachedValue {
|
||||||
Result.create(parse(), psiTreeChangePreprocessor)
|
Result.create(parse(), psiTreeChangePreprocessor)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user