mirror of
https://github.com/jlengrand/kotlin.git
synced 2026-03-10 08:31:29 +00:00
Initial implementation of KT-4046 Support 'Create constructor matching superclass'
#KT-4046 Fixed
This commit is contained in:
1
.idea/dictionaries/valentin.xml
generated
1
.idea/dictionaries/valentin.xml
generated
@@ -1,6 +1,7 @@
|
||||
<component name="ProjectDictionaryState">
|
||||
<dictionary name="valentin">
|
||||
<words>
|
||||
<w>delegator</w>
|
||||
<w>funs</w>
|
||||
<w>initializers</w>
|
||||
<w>inserter</w>
|
||||
|
||||
@@ -5,13 +5,7 @@
|
||||
<val name="value" val=""fun isAvailable(project: Project, editor: Editor, file: PsiFile): Boolean""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item
|
||||
name='com.intellij.codeInsight.intention.IntentionAction void invoke(com.intellij.openapi.project.Project, com.intellij.openapi.editor.Editor, com.intellij.psi.PsiFile)'>
|
||||
<annotation name='jet.runtime.typeinfo.KotlinSignature'>
|
||||
<val name="value" val=""fun invoke(project: Project, editor: Editor, file: PsiFile): Unit""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item
|
||||
<item
|
||||
name='com.intellij.codeInsight.intention.PsiElementBaseIntentionAction boolean isAvailable(com.intellij.openapi.project.Project, com.intellij.openapi.editor.Editor, com.intellij.psi.PsiElement) 1'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
|
||||
@@ -78,6 +78,24 @@ public class JetClass extends JetTypeParameterListOwnerStub<KotlinClassStub> imp
|
||||
return list.getParameters();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public JetPrimaryConstructor getOrCreatePrimaryConstructor() {
|
||||
JetPrimaryConstructor constructor = getPrimaryConstructor();
|
||||
if (constructor != null) return constructor;
|
||||
PsiElement anchor = getTypeParameterList();
|
||||
if (anchor == null) anchor = getNameIdentifier();
|
||||
if (anchor == null) anchor = getLastChild();
|
||||
return (JetPrimaryConstructor) addAfter(new JetPsiFactory(getProject()).createPrimaryConstructor(), anchor);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public JetParameterList getOrCreatePrimaryConstructorParameterList() {
|
||||
JetPrimaryConstructor constructor = getOrCreatePrimaryConstructor();
|
||||
JetParameterList parameterList = constructor.getValueParameterList();
|
||||
if (parameterList != null) return parameterList;
|
||||
return (JetParameterList) constructor.add(new JetPsiFactory(getProject()).createParameterList("()"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public JetDelegationSpecifierList getDelegationSpecifierList() {
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.psi;
|
||||
import com.intellij.lang.ASTNode;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.lexer.JetTokens;
|
||||
import org.jetbrains.kotlin.psi.stubs.KotlinPlaceHolderStub;
|
||||
import org.jetbrains.kotlin.psi.stubs.elements.JetStubElementTypes;
|
||||
@@ -44,6 +45,57 @@ public class JetParameterList extends JetElementImplStub<KotlinPlaceHolderStub<J
|
||||
return getStubOrPsiChildrenAsList(JetStubElementTypes.VALUE_PARAMETER);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public JetParameter addParameter(@NotNull JetParameter parameter) {
|
||||
return addParameterBefore(parameter, null);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public JetParameter addParameterAfter(@NotNull JetParameter parameter, @Nullable JetParameter anchor) {
|
||||
assert anchor == null || anchor.getParent() == this;
|
||||
List<JetParameter> parameters = getParameters();
|
||||
if (parameters.isEmpty()) {
|
||||
if (getFirstChild().getNode().getElementType() == JetTokens.LPAR) {
|
||||
return (JetParameter) addAfter(parameter, getFirstChild());
|
||||
}
|
||||
else {
|
||||
return (JetParameter) add(parameter);
|
||||
}
|
||||
}
|
||||
else {
|
||||
PsiElement comma = new JetPsiFactory(getProject()).createComma();
|
||||
if (anchor != null) {
|
||||
comma = addAfter(comma, anchor);
|
||||
return (JetParameter) addAfter(parameter, comma);
|
||||
}
|
||||
else {
|
||||
comma = addBefore(comma, parameters.get(0));
|
||||
return (JetParameter) addBefore(parameter, comma);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public JetParameter addParameterBefore(@NotNull JetParameter parameter, @Nullable JetParameter anchor) {
|
||||
List<JetParameter> parameters = getParameters();
|
||||
JetParameter anchorAfter;
|
||||
if (parameters.isEmpty()) {
|
||||
assert anchor == null;
|
||||
anchorAfter = null;
|
||||
}
|
||||
else {
|
||||
if (anchor != null) {
|
||||
int index = parameters.indexOf(anchor);
|
||||
assert index >= 0;
|
||||
anchorAfter = index > 0 ? parameters.get(index - 1) : null;
|
||||
}
|
||||
else {
|
||||
anchorAfter = parameters.get(parameters.size() - 1);
|
||||
}
|
||||
}
|
||||
return addParameterAfter(parameter, anchorAfter);
|
||||
}
|
||||
|
||||
// this method needed only for migrate lambda syntax
|
||||
@Deprecated
|
||||
public boolean isParenthesized() {
|
||||
|
||||
@@ -82,8 +82,7 @@ public object HeuristicSignatures {
|
||||
val type = typeFromText(typeStr, typeParameters, moduleDescriptor, project)
|
||||
|
||||
// now substitute type parameters with actual arguments
|
||||
val typeArgs = ownerType.getArguments()
|
||||
val typeArgsMap = typeParameters.indices.map { typeParameters[it] to typeArgs[it] }.toMap()
|
||||
val typeArgsMap = typeParameters.zip(ownerType.getArguments()).toMap()
|
||||
val substitutor = TypeUtils.makeSubstitutorForTypeParametersMap(typeArgsMap)
|
||||
return substitutor.substitute(type, Variance.INVARIANT)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.quickfix
|
||||
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
||||
import org.jetbrains.kotlin.idea.core.isVisible
|
||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||
import org.jetbrains.kotlin.idea.util.ShortenReferences
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyClassDescriptor
|
||||
import org.jetbrains.kotlin.types.TypeSubstitutor
|
||||
import org.jetbrains.kotlin.types.TypeUtils
|
||||
import java.util.ArrayList
|
||||
|
||||
public class AddConstructorParametersFromSuperFix private(
|
||||
element: JetDelegatorToSuperClass,
|
||||
val classDeclaration: JetClass,
|
||||
val superConstructor: ConstructorDescriptor
|
||||
) : JetIntentionAction<JetDelegatorToSuperClass>(element) {
|
||||
|
||||
override fun getFamilyName() = "Add constructor parameters from superclass"
|
||||
|
||||
override fun getText() = "Add constructor parameters and use them"
|
||||
|
||||
override fun invoke(project: Project, editor: Editor?, file: JetFile) {
|
||||
val factory = JetPsiFactory(project)
|
||||
val renderer = IdeDescriptorRenderers.SOURCE_CODE
|
||||
|
||||
val superParameters = superConstructor.getValueParameters()
|
||||
val parameterNames = ArrayList<String>()
|
||||
val typeRefsToShorten = ArrayList<JetTypeReference>()
|
||||
if (!superParameters.isEmpty()) {
|
||||
val parameterList = classDeclaration.getOrCreatePrimaryConstructorParameterList()
|
||||
for (parameter in superParameters) {
|
||||
//TODO: what if type is error?
|
||||
val name = renderer.renderName(parameter.getName())
|
||||
val parameterText = name + ":" + renderer.renderType(parameter.getType())
|
||||
val newParameter = parameterList.addParameter(factory.createParameter(parameterText))
|
||||
typeRefsToShorten.add(newParameter.getTypeReference())
|
||||
parameterNames.add(name)
|
||||
}
|
||||
}
|
||||
|
||||
val delegatorCall = factory.createDelegatorToSuperCall(element.getText() + "(" + parameterNames.joinToString(",") + ")")
|
||||
element.replace(delegatorCall)
|
||||
|
||||
ShortenReferences.DEFAULT.process(typeRefsToShorten)
|
||||
}
|
||||
|
||||
companion object : JetSingleIntentionActionFactory() {
|
||||
override fun createAction(diagnostic: Diagnostic): JetIntentionAction<JetDelegatorToSuperClass>? {
|
||||
val delegator = diagnostic.getPsiElement() as JetDelegatorToSuperClass
|
||||
val classDeclaration = delegator.getParent().getParent() as? JetClass ?: return null
|
||||
|
||||
val typeRef = delegator.getTypeReference() ?: return null
|
||||
val type = typeRef.analyze()[BindingContext.TYPE, typeRef] ?: return null
|
||||
if (type.isError()) return null
|
||||
|
||||
val superClass = (type.getConstructor().getDeclarationDescriptor() as? ClassDescriptor) ?: return null
|
||||
val classDescriptor = delegator.getResolutionFacade().resolveToDescriptor(classDeclaration) as ClassDescriptor
|
||||
val constructors = superClass.getConstructors().filter { it.isVisible(classDescriptor) }
|
||||
val constructorToUse = constructors.singleOrNull()
|
||||
?: constructors.singleOrNull { it.isPrimary() } //TODO: should we select it automatically in this case?
|
||||
?: return null
|
||||
//TODO: choose among multiple
|
||||
if (constructorToUse.getValueParameters().isEmpty()) return null
|
||||
|
||||
val superType = classDescriptor.getTypeConstructor().getSupertypes().first { it.getConstructor().getDeclarationDescriptor() == superClass }
|
||||
val typeArgsMap = superClass.getTypeConstructor().getParameters().zip(superType.getArguments()).toMap()
|
||||
val substitutor = TypeUtils.makeSubstitutorForTypeParametersMap(typeArgsMap)
|
||||
val substitutedConstructor = constructorToUse.substitute(substitutor) as ConstructorDescriptor
|
||||
|
||||
return AddConstructorParametersFromSuperFix(delegator, classDeclaration, substitutedConstructor)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.psi.JetCodeFragment;
|
||||
import org.jetbrains.kotlin.psi.JetFile;
|
||||
|
||||
@@ -42,7 +43,7 @@ public abstract class JetIntentionAction<T extends PsiElement> implements Intent
|
||||
//Don't override this method. Use the method with JetFile instead.
|
||||
@Deprecated
|
||||
@Override
|
||||
public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
|
||||
public void invoke(@NotNull Project project, @Nullable Editor editor, @NotNull PsiFile file) throws IncorrectOperationException {
|
||||
if (file instanceof JetFile) {
|
||||
if (FileModificationService.getInstance().prepareFileForWrite(element.getContainingFile())) {
|
||||
invoke(project, editor, (JetFile) file);
|
||||
@@ -50,8 +51,7 @@ public abstract class JetIntentionAction<T extends PsiElement> implements Intent
|
||||
}
|
||||
}
|
||||
|
||||
protected void invoke(@NotNull Project project, Editor editor, JetFile file) throws IncorrectOperationException {
|
||||
}
|
||||
protected abstract void invoke(@NotNull Project project, @Nullable Editor editor, @NotNull JetFile file) throws IncorrectOperationException;
|
||||
|
||||
@Override
|
||||
public boolean startInWriteAction() {
|
||||
|
||||
@@ -325,5 +325,7 @@ public class QuickFixRegistrar {
|
||||
|
||||
QuickFixes.factories.put(DEPRECATED_ANNOTATION_SYNTAX, DeprecatedAnnotationSyntaxFix.Companion);
|
||||
QuickFixes.factories.put(DEPRECATED_ANNOTATION_SYNTAX, DeprecatedAnnotationSyntaxFix.Companion.createWholeProjectFixFactory());
|
||||
|
||||
QuickFixes.factories.put(SUPERTYPE_NOT_INITIALIZED, AddConstructorParametersFromSuperFix.Companion);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,8 +192,6 @@ public open class KotlinIntroduceParameterHandler(
|
||||
return
|
||||
}
|
||||
|
||||
val parameterList = targetParent.getValueParameterList()
|
||||
|
||||
val descriptor = context[BindingContext.DECLARATION_TO_DESCRIPTOR, targetParent]
|
||||
val functionDescriptor: FunctionDescriptor =
|
||||
when (descriptor) {
|
||||
@@ -250,26 +248,9 @@ public open class KotlinIntroduceParameterHandler(
|
||||
|
||||
val addedParameter = if (inplaceIsAvailable) {
|
||||
runWriteAction {
|
||||
val newParameterList =
|
||||
if (parameterList == null) {
|
||||
val klass = targetParent as? JetClass
|
||||
val anchor = klass?.getTypeParameterList() ?: klass?.getNameIdentifier()
|
||||
assert(anchor != null) { "Invalid declaration: ${targetParent.getElementTextWithContext()}" }
|
||||
|
||||
val constructor = targetParent.addAfter(psiFactory.createPrimaryConstructor(), anchor) as JetPrimaryConstructor
|
||||
constructor.getValueParameterList()!!
|
||||
}
|
||||
else parameterList
|
||||
|
||||
val lastParameter = newParameterList.getChildren().lastOrNull { it is JetParameter } as? JetParameter
|
||||
if (lastParameter != null) {
|
||||
val comma = newParameterList.addAfter(psiFactory.createComma(), lastParameter)
|
||||
newParameterList.addAfter(newParameter, comma) as JetParameter
|
||||
}
|
||||
else {
|
||||
val singleParameterList = psiFactory.createParameterList("(${newParameter.getText()})")
|
||||
(newParameterList.replace(singleParameterList) as JetParameterList).getParameters().first()
|
||||
}
|
||||
val parameterList = targetParent.getValueParameterList()
|
||||
?: (targetParent as JetClass).getOrCreatePrimaryConstructorParameterList()
|
||||
parameterList.addParameter(newParameter)
|
||||
}
|
||||
}
|
||||
else newParameter
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
// "Add constructor parameters and use them" "true"
|
||||
import java.io.DataInputStream
|
||||
|
||||
class C : DataInputStream<caret>
|
||||
@@ -0,0 +1,5 @@
|
||||
// "Add constructor parameters and use them" "true"
|
||||
import java.io.DataInputStream
|
||||
import java.io.InputStream
|
||||
|
||||
class C(p0: InputStream) : DataInputStream<caret>(p0)
|
||||
@@ -0,0 +1,4 @@
|
||||
// "Add constructor parameters and use them" "true"
|
||||
open class Base(p1: Int, val p2: Int)
|
||||
|
||||
class C(p: Int) : Base<caret>
|
||||
@@ -0,0 +1,4 @@
|
||||
// "Add constructor parameters and use them" "true"
|
||||
open class Base(p1: Int, val p2: Int)
|
||||
|
||||
class C(p: Int, p1: Int, p2: Int) : Base<caret>(p1, p2)
|
||||
@@ -0,0 +1,4 @@
|
||||
// "Add constructor parameters and use them" "true"
|
||||
open class Base(p1: Int, val p2: Int)
|
||||
|
||||
class C : Base<caret>
|
||||
@@ -0,0 +1,4 @@
|
||||
// "Add constructor parameters and use them" "true"
|
||||
open class Base(p1: Int, val p2: Int)
|
||||
|
||||
class C(p1: Int, p2: Int) : Base<caret>(p1, p2)
|
||||
@@ -0,0 +1,6 @@
|
||||
// "Add constructor parameters and use them" "true"
|
||||
trait I
|
||||
|
||||
open class Base<T1, T2>(p1: T1, p2: T2, p3: Base<T1, T2>?)
|
||||
|
||||
class C<T> : I, Base<T, String><caret>
|
||||
@@ -0,0 +1,6 @@
|
||||
// "Add constructor parameters and use them" "true"
|
||||
trait I
|
||||
|
||||
open class Base<T1, T2>(p1: T1, p2: T2, p3: Base<T1, T2>?)
|
||||
|
||||
class C<T>(p1: T, p2: String, p3: Base<T, String>?) : I, Base<T, String><caret>(p1, p2, p3)
|
||||
@@ -0,0 +1,4 @@
|
||||
// "Add constructor parameters and use them" "true"
|
||||
open class Base(p1: Int, val p2: Int)
|
||||
|
||||
class C private : Base<caret>
|
||||
@@ -0,0 +1,4 @@
|
||||
// "Add constructor parameters and use them" "true"
|
||||
open class Base(p1: Int, val p2: Int)
|
||||
|
||||
class C private (p1: Int, p2: Int) : Base<caret>(p1, p2)
|
||||
@@ -0,0 +1,4 @@
|
||||
// "Add constructor parameters and use them" "true"
|
||||
open class Base(`fun`: Int, val `class`: Int)
|
||||
|
||||
class C : Base<caret>
|
||||
@@ -0,0 +1,4 @@
|
||||
// "Add constructor parameters and use them" "true"
|
||||
open class Base(`fun`: Int, val `class`: Int)
|
||||
|
||||
class C(`fun`: Int, `class`: Int) : Base(`fun`, `class`)
|
||||
@@ -0,0 +1,6 @@
|
||||
// "Add constructor parameters and use them" "false"
|
||||
// ACTION: Change to constructor invocation
|
||||
// ERROR: This type has a constructor, and thus must be initialized here
|
||||
open class Base
|
||||
|
||||
class C : Base<caret>
|
||||
@@ -0,0 +1,7 @@
|
||||
// "Add constructor parameters and use them" "true"
|
||||
open class Base private(p1: Int, val p2: Int) {
|
||||
private constructor() : this(0, 1)
|
||||
protected constructor(s: String) : this(s.length(), 1)
|
||||
}
|
||||
|
||||
class C : Base<caret>
|
||||
@@ -0,0 +1,7 @@
|
||||
// "Add constructor parameters and use them" "true"
|
||||
open class Base private(p1: Int, val p2: Int) {
|
||||
private constructor() : this(0, 1)
|
||||
protected constructor(s: String) : this(s.length(), 1)
|
||||
}
|
||||
|
||||
class C(s: String) : Base<caret>(s)
|
||||
@@ -176,6 +176,63 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/addConstructorParametersFromSuper")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class AddConstructorParametersFromSuper extends AbstractQuickFixTest {
|
||||
@TestMetadata("addImport.kt")
|
||||
public void testAddImport() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/addConstructorParametersFromSuper/addImport.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("addParameters.kt")
|
||||
public void testAddParameters() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/addConstructorParametersFromSuper/addParameters.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInAddConstructorParametersFromSuper() throws Exception {
|
||||
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/addConstructorParametersFromSuper"), Pattern.compile("^(\\w+)\\.kt$"), true);
|
||||
}
|
||||
|
||||
@TestMetadata("createConstructor.kt")
|
||||
public void testCreateConstructor() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/addConstructorParametersFromSuper/createConstructor.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("genericClass.kt")
|
||||
public void testGenericClass() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/addConstructorParametersFromSuper/genericClass.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("incompleteConstructor.kt")
|
||||
public void testIncompleteConstructor() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/addConstructorParametersFromSuper/incompleteConstructor.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("keywordName.kt")
|
||||
public void testKeywordName() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/addConstructorParametersFromSuper/keywordName.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("noParameters.kt")
|
||||
public void testNoParameters() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/addConstructorParametersFromSuper/noParameters.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("primaryConstructorInaccessible.kt")
|
||||
public void testPrimaryConstructorInaccessible() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/addConstructorParametersFromSuper/primaryConstructorInaccessible.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/addStarProjections")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
Reference in New Issue
Block a user