From 161539f3da9c0003d07c0c0689f17ac90b75ebca Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Thu, 2 Jul 2015 15:17:53 +0300 Subject: [PATCH] Change Signature: val/var parameter support --- .../idea/refactoring/CallableRefactoring.kt | 2 +- .../changeSignature/JetChangeInfo.kt | 4 +- .../JetChangePropertySignatureDialog.kt | 61 ++++++++++--------- .../changeSignature/JetChangeSignature.kt | 5 +- .../JetChangeSignatureHandler.java | 27 +++++--- .../JetChangeSignatureUsageProcessor.java | 3 +- .../usages/JetCallableDefinitionUsage.java | 6 +- .../usages/JetFunctionCallUsage.java | 3 +- .../ChangeClassParameterAfter.1.java | 38 ++++++++++++ .../ChangeClassParameterAfter.kt | 21 +++++++ .../ChangeClassParameterBefore.1.java | 35 +++++++++++ .../ChangeClassParameterBefore.kt | 21 +++++++ .../ConstructorSwapArgumentsBefore.kt | 2 +- .../JetChangeSignatureTest.java | 7 +++ 14 files changed, 186 insertions(+), 49 deletions(-) create mode 100644 idea/testData/refactoring/changeSignature/ChangeClassParameterAfter.1.java create mode 100644 idea/testData/refactoring/changeSignature/ChangeClassParameterAfter.kt create mode 100644 idea/testData/refactoring/changeSignature/ChangeClassParameterBefore.1.java create mode 100644 idea/testData/refactoring/changeSignature/ChangeClassParameterBefore.kt diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/CallableRefactoring.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/CallableRefactoring.kt index 8ec8a4bc1eb..3efcd5c053c 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/CallableRefactoring.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/CallableRefactoring.kt @@ -150,7 +150,7 @@ public abstract class CallableRefactoring( return true } - if (closestModifiableDescriptors.size() == 1 && deepestSuperDeclarations == closestModifiableDescriptors) { + if (closestModifiableDescriptors.size() == 1 && deepestSuperDeclarations.subtract(closestModifiableDescriptors).isEmpty()) { performRefactoring(closestModifiableDescriptors) return true } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetChangeInfo.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetChangeInfo.kt index 2b48e1eca63..79a0838311d 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetChangeInfo.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetChangeInfo.kt @@ -104,7 +104,7 @@ public class JetChangeInfo( fun getNonReceiverParametersCount(): Int = newParameters.size() - (if (receiverParameterInfo != null) 1 else 0) fun getNonReceiverParameters(): List { - if (methodDescriptor.baseDeclaration is JetProperty) return emptyList() + methodDescriptor.baseDeclaration.let { if (it is JetProperty || it is JetParameter) return emptyList() } return receiverParameterInfo?.let { receiver -> newParameters.filter { it != receiver } } ?: newParameters } @@ -309,7 +309,7 @@ public class JetChangeInfo( when (method) { is JetFunction, is JetClassOrObject -> createJavaChangeInfoForFunctionOrGetter(originalPsiMethod, currentPsiMethod, false) - is JetProperty -> { + is JetProperty, is JetParameter -> { val accessorName = originalPsiMethod.getName() when { accessorName.startsWith(JvmAbi.GETTER_PREFIX) -> diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetChangePropertySignatureDialog.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetChangePropertySignatureDialog.kt index bce53cd6946..623b78f1e60 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetChangePropertySignatureDialog.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetChangePropertySignatureDialog.kt @@ -51,7 +51,7 @@ public class JetChangePropertySignatureDialog( ) private val nameField = EditorTextField(methodDescriptor.getName()) private var returnTypeField: EditorTextField by Delegates.notNull() - private var receiverTypeCheckBox: JCheckBox by Delegates.notNull() + private var receiverTypeCheckBox: JCheckBox? = null var receiverTypeLabel: JLabel by Delegates.notNull() private var receiverTypeField: EditorTextField by Delegates.notNull() var receiverDefaultValueLabel: JLabel? = null @@ -66,7 +66,7 @@ public class JetChangePropertySignatureDialog( override fun createCenterPanel(): JComponent? { fun updateReceiverUI() { - val withReceiver = receiverTypeCheckBox.isSelected() + val withReceiver = receiverTypeCheckBox!!.isSelected() receiverTypeLabel.setEnabled(withReceiver) receiverTypeField.setEnabled(withReceiver) receiverDefaultValueLabel?.setEnabled(withReceiver) @@ -77,7 +77,8 @@ public class JetChangePropertySignatureDialog( val psiFactory = JetPsiFactory(myProject) return with(FormBuilder.createFormBuilder()) { - if (!((methodDescriptor.baseDeclaration as? JetProperty)?.isLocal() ?: false)) { + val baseDeclaration = methodDescriptor.baseDeclaration + if (!((baseDeclaration as? JetProperty)?.isLocal() ?: false)) { visibilityCombo.setSelectedItem(methodDescriptor.getVisibility()) addLabeledComponent("&Visibility: ", visibilityCombo) } @@ -85,38 +86,40 @@ public class JetChangePropertySignatureDialog( addLabeledComponent("&Name: ", nameField) val returnTypeCodeFragment = psiFactory.createTypeCodeFragment(methodDescriptor.renderOriginalReturnType(), - methodDescriptor.baseDeclaration) + baseDeclaration) returnTypeField = EditorTextField(documentManager.getDocument(returnTypeCodeFragment), myProject, JetFileType.INSTANCE) addLabeledComponent("&Type: ", returnTypeField) - addSeparator() + if (baseDeclaration is JetProperty) { + addSeparator() - val receiverTypeCheckBox = JCheckBox("Extension property: ") - receiverTypeCheckBox.setMnemonic('x') - receiverTypeCheckBox.addActionListener { updateReceiverUI() } - receiverTypeCheckBox.setSelected(methodDescriptor.receiver != null) - addComponent(receiverTypeCheckBox) - this@JetChangePropertySignatureDialog.receiverTypeCheckBox = receiverTypeCheckBox + val receiverTypeCheckBox = JCheckBox("Extension property: ") + receiverTypeCheckBox.setMnemonic('x') + receiverTypeCheckBox.addActionListener { updateReceiverUI() } + receiverTypeCheckBox.setSelected(methodDescriptor.receiver != null) + addComponent(receiverTypeCheckBox) + this@JetChangePropertySignatureDialog.receiverTypeCheckBox = receiverTypeCheckBox - val receiverTypeCodeFragment = psiFactory.createTypeCodeFragment(methodDescriptor.renderOriginalReceiverType() ?: "", - methodDescriptor.baseDeclaration) - receiverTypeField = EditorTextField(documentManager.getDocument(receiverTypeCodeFragment), myProject, JetFileType.INSTANCE) - receiverTypeLabel = JLabel("Receiver type: ") - receiverTypeLabel.setDisplayedMnemonic('t') - addLabeledComponent(receiverTypeLabel, receiverTypeField) + val receiverTypeCodeFragment = psiFactory.createTypeCodeFragment(methodDescriptor.renderOriginalReceiverType() ?: "", + methodDescriptor.baseDeclaration) + receiverTypeField = EditorTextField(documentManager.getDocument(receiverTypeCodeFragment), myProject, JetFileType.INSTANCE) + receiverTypeLabel = JLabel("Receiver type: ") + receiverTypeLabel.setDisplayedMnemonic('t') + addLabeledComponent(receiverTypeLabel, receiverTypeField) - if (methodDescriptor.receiver == null) { - val receiverDefaultValueCodeFragment = psiFactory.createExpressionCodeFragment("", methodDescriptor.baseDeclaration) - receiverDefaultValueField = EditorTextField(documentManager.getDocument(receiverDefaultValueCodeFragment), - myProject, - JetFileType.INSTANCE) - receiverDefaultValueLabel = JLabel("Default receiver value: ") - receiverDefaultValueLabel!!.setDisplayedMnemonic('D') - addLabeledComponent(receiverDefaultValueLabel, receiverDefaultValueField!!) + if (methodDescriptor.receiver == null) { + val receiverDefaultValueCodeFragment = psiFactory.createExpressionCodeFragment("", methodDescriptor.baseDeclaration) + receiverDefaultValueField = EditorTextField(documentManager.getDocument(receiverDefaultValueCodeFragment), + myProject, + JetFileType.INSTANCE) + receiverDefaultValueLabel = JLabel("Default receiver value: ") + receiverDefaultValueLabel!!.setDisplayedMnemonic('D') + addLabeledComponent(receiverDefaultValueLabel, receiverDefaultValueField!!) + } + + updateReceiverUI() } - updateReceiverUI() - getPanel() } } @@ -131,7 +134,7 @@ public class JetChangePropertySignatureDialog( psiFactory.createSimpleName(nameField.getText()).validateElement("Invalid name") psiFactory.createType(returnTypeField.getText()).validateElement("Invalid return type") - if (receiverTypeCheckBox.isSelected()) { + if (receiverTypeCheckBox?.isSelected() ?: false) { psiFactory.createType(receiverTypeField.getText()).validateElement("Invalid receiver type") } getDefaultReceiverValue()?.validateElement("Invalid default receiver value") @@ -140,7 +143,7 @@ public class JetChangePropertySignatureDialog( override fun doAction() { val descriptor = (methodDescriptor as? JetMutableMethodDescriptor)?.original ?: methodDescriptor - val receiver = if (receiverTypeCheckBox.isSelected()) { + val receiver = if (receiverTypeCheckBox?.isSelected() ?: false) { descriptor.receiver ?: JetParameterInfo(callableDescriptor = descriptor.baseDescriptor, name = "receiver", defaultValueForCall = getDefaultReceiverValue()) diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetChangeSignature.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetChangeSignature.kt index 7abb411c31e..5aea8ec9027 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetChangeSignature.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetChangeSignature.kt @@ -30,6 +30,7 @@ import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde import org.jetbrains.kotlin.idea.refactoring.CallableRefactoring import org.jetbrains.kotlin.psi.JetClass import org.jetbrains.kotlin.psi.JetFunction +import org.jetbrains.kotlin.psi.JetParameter import org.jetbrains.kotlin.psi.JetProperty import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext import org.jetbrains.kotlin.resolve.BindingContext @@ -79,7 +80,7 @@ public class JetChangeSignature(project: Project, is JetFunction, is JetClass -> { JetChangeSignatureDialog.createRefactoringProcessorForSilentChangeSignature(project, commandName, descriptor, defaultValueContext) } - is JetProperty -> { + is JetProperty, is JetParameter -> { JetChangePropertySignatureDialog.createProcessorForSilentRefactoring(project, commandName, descriptor) } else -> throw AssertionError("Unexpected declaration: ${descriptor.baseDeclaration.getElementTextWithContext()}") @@ -90,7 +91,7 @@ public class JetChangeSignature(project: Project, private fun runInteractiveRefactoring(descriptor: JetMethodDescriptor) { val dialog = when (descriptor.baseDeclaration) { is JetFunction, is JetClass -> JetChangeSignatureDialog(project, descriptor, defaultValueContext, commandName) - is JetProperty -> JetChangePropertySignatureDialog(project, descriptor, commandName) + is JetProperty, is JetParameter -> JetChangePropertySignatureDialog(project, descriptor, commandName) else -> throw AssertionError("Unexpected declaration: ${descriptor.baseDeclaration.getElementTextWithContext()}") } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetChangeSignatureHandler.java b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetChangeSignatureHandler.java index 2bf88b1831d..3b287850290 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetChangeSignatureHandler.java +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetChangeSignatureHandler.java @@ -53,6 +53,22 @@ import static org.jetbrains.kotlin.idea.refactoring.changeSignature.ChangeSignat public class JetChangeSignatureHandler implements ChangeSignatureHandler { @Nullable public static PsiElement findTargetForRefactoring(@NotNull PsiElement element) { + PsiElement elementParent = element.getParent(); + if ((elementParent instanceof JetNamedFunction || elementParent instanceof JetClass || elementParent instanceof JetProperty) + && ((JetNamedDeclaration) elementParent).getNameIdentifier() == element) return elementParent; + + if (elementParent instanceof JetParameter) { + JetParameter parameter = (JetParameter) elementParent; + JetPrimaryConstructor primaryConstructor = PsiTreeUtil.getParentOfType(parameter, JetPrimaryConstructor.class); + if (parameter.hasValOrVar() + && (parameter.getNameIdentifier() == element || parameter.getValOrVarKeyword() == element) + && primaryConstructor != null + && primaryConstructor.getValueParameterList() == parameter.getParent()) return parameter; + } + + if (elementParent instanceof JetSecondaryConstructor && + ((JetSecondaryConstructor) elementParent).getConstructorKeyword() == element) return elementParent; + if (PsiTreeUtil.getParentOfType(element, JetParameterList.class) != null) { return PsiTreeUtil.getParentOfType(element, JetFunction.class, JetProperty.class, JetClass.class); } @@ -62,13 +78,6 @@ public class JetChangeSignatureHandler implements ChangeSignatureHandler { return PsiTreeUtil.getParentOfType(typeParameterList, JetFunction.class, JetProperty.class, JetClass.class); } - PsiElement elementParent = element.getParent(); - if ((elementParent instanceof JetNamedFunction || elementParent instanceof JetClass || elementParent instanceof JetProperty) - && ((JetNamedDeclaration) elementParent).getNameIdentifier() == element) return elementParent; - - if (elementParent instanceof JetSecondaryConstructor && - ((JetSecondaryConstructor) elementParent).getConstructorKeyword() == element) return elementParent; - JetExpression calleeExpr; JetCallElement call = PsiTreeUtil.getParentOfType(element, JetCallExpression.class, @@ -241,8 +250,8 @@ public class JetChangeSignatureHandler implements ChangeSignatureHandler { return (FunctionDescriptor) descriptor; } - else if (descriptor instanceof PropertyDescriptor) { - return (PropertyDescriptor) descriptor; + else if (descriptor instanceof PropertyDescriptor || descriptor instanceof ValueParameterDescriptor) { + return (CallableDescriptor) descriptor; } else { String message = RefactoringBundle.getCannotRefactorMessage(JetRefactoringBundle.message( diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetChangeSignatureUsageProcessor.java b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetChangeSignatureUsageProcessor.java index d9904cd5c54..3294100a876 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetChangeSignatureUsageProcessor.java +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetChangeSignatureUsageProcessor.java @@ -155,7 +155,8 @@ public class JetChangeSignatureUsageProcessor implements ChangeSignatureUsagePro if (parent instanceof JetConstructorCalleeExpression && parent.getParent() instanceof JetDelegatorToSuperCall) result.add(new JetFunctionCallUsage((JetDelegatorToSuperCall)parent.getParent(), functionUsageInfo)); } - else if (element instanceof JetSimpleNameExpression && functionPsi instanceof JetProperty) { + else if (element instanceof JetSimpleNameExpression + && (functionPsi instanceof JetProperty || functionPsi instanceof JetParameter)) { result.add(new JetPropertyCallUsage((JetSimpleNameExpression) element)); } } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/usages/JetCallableDefinitionUsage.java b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/usages/JetCallableDefinitionUsage.java index c2189182b48..ae5d4c1cb52 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/usages/JetCallableDefinitionUsage.java +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/usages/JetCallableDefinitionUsage.java @@ -151,7 +151,7 @@ public class JetCallableDefinitionUsage extends JetUsageIn if (currentCallableDescriptor == null) { PsiElement element = getDeclaration(); - if (element instanceof JetFunction || element instanceof JetProperty) { + if (element instanceof JetFunction || element instanceof JetProperty || element instanceof JetParameter) { currentCallableDescriptor = (CallableDescriptor) ResolvePackage.resolveToDescriptor((JetDeclaration) element); } else if (element instanceof JetClass) { @@ -227,7 +227,7 @@ public class JetCallableDefinitionUsage extends JetUsageIn callable.getTypeReference() != null); } else { - returnTypeIsNeeded = element instanceof JetProperty; + returnTypeIsNeeded = element instanceof JetProperty || element instanceof JetParameter; } if (changeInfo.isReturnTypeChanged() && returnTypeIsNeeded) { @@ -273,7 +273,7 @@ public class JetCallableDefinitionUsage extends JetUsageIn canReplaceEntireList = true; } } - else if (!(element instanceof JetProperty)) { + else if (!(element instanceof JetProperty || element instanceof JetParameter)) { newParameterList = psiFactory.createParameterList(changeInfo.getNewParametersSignature( (JetCallableDefinitionUsage) this) ); diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/usages/JetFunctionCallUsage.java b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/usages/JetFunctionCallUsage.java index 7468e5ea0b1..785ecd6cf3f 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/usages/JetFunctionCallUsage.java +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/usages/JetFunctionCallUsage.java @@ -117,7 +117,8 @@ public class JetFunctionCallUsage extends JetUsageInfo { } private boolean isPropertyJavaUsage() { - return this.callee.getElement() instanceof JetProperty + PsiElement calleeElement = this.callee.getElement(); + return (calleeElement instanceof JetProperty || calleeElement instanceof JetParameter) && resolvedCall != null && resolvedCall.getResultingDescriptor() instanceof JavaMethodDescriptor; } diff --git a/idea/testData/refactoring/changeSignature/ChangeClassParameterAfter.1.java b/idea/testData/refactoring/changeSignature/ChangeClassParameterAfter.1.java new file mode 100644 index 00000000000..cc7c2eee483 --- /dev/null +++ b/idea/testData/refactoring/changeSignature/ChangeClassParameterAfter.1.java @@ -0,0 +1,38 @@ +import org.jetbrains.annotations.NotNull; + +import java.lang.Override; + +class J extends A { + private int p; + + public J() { + super(0); + } + + @NotNull + @Override + public String getS() { + return p; + } + + @Override + public void setS(@NotNull String value) { + p = value; + } +} + +class Test { + static void test() { + new A(0).getS(); + new A(0).setS(1); + + new B(0).getS(); + new B(0).setS(2); + + new C().getS(); + new C().setS(3); + + new J().getS(); + new J().setS(4); + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/changeSignature/ChangeClassParameterAfter.kt b/idea/testData/refactoring/changeSignature/ChangeClassParameterAfter.kt new file mode 100644 index 00000000000..4a3e1ee1c61 --- /dev/null +++ b/idea/testData/refactoring/changeSignature/ChangeClassParameterAfter.kt @@ -0,0 +1,21 @@ +open class A(open var s: String) + +class B(override var s: String): A(s) + +class C: A(0) { + override var s: String = 1 +} + +fun test() { + val t1 = A(0).s + A(0).s = 1 + + val t2 = B(0).s + B(0).s = 2 + + val t3 = C().s + C().s = 3 + + val t4 = J().getS() + J().setS(4) +} \ No newline at end of file diff --git a/idea/testData/refactoring/changeSignature/ChangeClassParameterBefore.1.java b/idea/testData/refactoring/changeSignature/ChangeClassParameterBefore.1.java new file mode 100644 index 00000000000..8b02953c102 --- /dev/null +++ b/idea/testData/refactoring/changeSignature/ChangeClassParameterBefore.1.java @@ -0,0 +1,35 @@ +import java.lang.Override; + +class J extends A { + private int p; + + public J() { + super(0); + } + + @Override + public int getP() { + return p; + } + + @Override + public void setP(int value) { + p = value; + } +} + +class Test { + static void test() { + new A(0).getP(); + new A(0).setP(1); + + new B(0).getP(); + new B(0).setP(2); + + new C().getP(); + new C().setP(3); + + new J().getP(); + new J().setP(4); + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/changeSignature/ChangeClassParameterBefore.kt b/idea/testData/refactoring/changeSignature/ChangeClassParameterBefore.kt new file mode 100644 index 00000000000..4d900d38252 --- /dev/null +++ b/idea/testData/refactoring/changeSignature/ChangeClassParameterBefore.kt @@ -0,0 +1,21 @@ +open class A(open var p: Int) + +class B(override var p: Int): A(p) + +class C: A(0) { + override var p: Int = 1 +} + +fun test() { + val t1 = A(0).p + A(0).p = 1 + + val t2 = B(0).p + B(0).p = 2 + + val t3 = C().p + C().p = 3 + + val t4 = J().getP() + J().setP(4) +} \ No newline at end of file diff --git a/idea/testData/refactoring/changeSignature/ConstructorSwapArgumentsBefore.kt b/idea/testData/refactoring/changeSignature/ConstructorSwapArgumentsBefore.kt index e66856e92e8..b66d50dc46a 100644 --- a/idea/testData/refactoring/changeSignature/ConstructorSwapArgumentsBefore.kt +++ b/idea/testData/refactoring/changeSignature/ConstructorSwapArgumentsBefore.kt @@ -1,4 +1,4 @@ -open class C1 protected (val x1: Int = 1, var x2: Float, x3: ((Int) -> Int)?) { +open class C1 protected (val x1: Int = 1, var x2: Float, x3: ((Int) -> Int)?) { fun bar() { val y1 = x1; val y2 = x2; diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetChangeSignatureTest.java b/idea/tests/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetChangeSignatureTest.java index afe85398e31..9ca0a99a743 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetChangeSignatureTest.java +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetChangeSignatureTest.java @@ -1056,6 +1056,13 @@ public class JetChangeSignatureTest extends KotlinCodeInsightTestCase { doTest(changeInfo); } + public void testChangeClassParameter() throws Exception { + JetChangeInfo changeInfo = getChangeInfo(); + changeInfo.setNewName("s"); + changeInfo.setNewReturnTypeText("String"); + doTest(changeInfo); + } + @NotNull @Override protected String getTestDataPath() {