Convert ClassTranslator.java to Kotlin

This commit is contained in:
Zalim Bashorov
2015-05-27 19:27:05 +03:00
parent b61c1376a2
commit c589026a47
@@ -14,294 +14,269 @@
* limitations under the License. * limitations under the License.
*/ */
package org.jetbrains.kotlin.js.translate.declaration; package org.jetbrains.kotlin.js.translate.declaration
import com.google.dart.compiler.backend.js.ast.*; import com.google.dart.compiler.backend.js.ast.*
import com.intellij.util.SmartList; import com.intellij.util.SmartList
import org.jetbrains.annotations.NotNull; import org.jetbrains.kotlin.backend.common.CodegenUtil
import org.jetbrains.kotlin.backend.common.CodegenUtil; import org.jetbrains.kotlin.backend.common.bridges.Bridge
import org.jetbrains.kotlin.backend.common.bridges.Bridge; import org.jetbrains.kotlin.backend.common.bridges.*
import org.jetbrains.kotlin.backend.common.bridges.BridgesPackage; import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.builtins.KotlinBuiltIns; import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.*; import org.jetbrains.kotlin.js.translate.context.DefinitionPlace
import org.jetbrains.kotlin.js.translate.context.DefinitionPlace; import org.jetbrains.kotlin.js.translate.context.Namer
import org.jetbrains.kotlin.js.translate.context.Namer; import org.jetbrains.kotlin.js.translate.context.TranslationContext
import org.jetbrains.kotlin.js.translate.context.TranslationContext; import org.jetbrains.kotlin.js.translate.declaration.propertyTranslator.*
import org.jetbrains.kotlin.js.translate.declaration.propertyTranslator.PropertyTranslatorPackage; import org.jetbrains.kotlin.js.translate.expression.*
import org.jetbrains.kotlin.js.translate.expression.ExpressionPackage; import org.jetbrains.kotlin.js.translate.general.AbstractTranslator
import org.jetbrains.kotlin.js.translate.general.AbstractTranslator; import org.jetbrains.kotlin.js.translate.initializer.ClassInitializerTranslator
import org.jetbrains.kotlin.js.translate.initializer.ClassInitializerTranslator; import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils; import org.jetbrains.kotlin.js.translate.utils.*
import org.jetbrains.kotlin.js.translate.utils.UtilsPackage; import org.jetbrains.kotlin.psi.JetClassOrObject
import org.jetbrains.kotlin.psi.JetClassOrObject; import org.jetbrains.kotlin.psi.JetObjectDeclaration
import org.jetbrains.kotlin.psi.JetObjectDeclaration; import org.jetbrains.kotlin.psi.JetParameter
import org.jetbrains.kotlin.psi.JetParameter; import org.jetbrains.kotlin.types.JetType
import org.jetbrains.kotlin.types.JetType; import org.jetbrains.kotlin.types.TypeConstructor
import org.jetbrains.kotlin.types.TypeConstructor;
import java.util.*; import java.util.*
import static org.jetbrains.kotlin.js.translate.reference.ReferenceTranslator.translateAsFQReference; import org.jetbrains.kotlin.js.translate.reference.ReferenceTranslator.translateAsFQReference
import static org.jetbrains.kotlin.js.translate.utils.BindingUtils.getClassDescriptor; import org.jetbrains.kotlin.js.translate.utils.BindingUtils.getClassDescriptor
import static org.jetbrains.kotlin.js.translate.utils.BindingUtils.getPropertyDescriptorForConstructorParameter; import org.jetbrains.kotlin.js.translate.utils.BindingUtils.getPropertyDescriptorForConstructorParameter
import static org.jetbrains.kotlin.js.translate.utils.JsDescriptorUtils.getReceiverParameterForDeclaration; import org.jetbrains.kotlin.js.translate.utils.JsDescriptorUtils.getReceiverParameterForDeclaration
import static org.jetbrains.kotlin.js.translate.utils.JsDescriptorUtils.getSupertypesWithoutFakes; import org.jetbrains.kotlin.js.translate.utils.JsDescriptorUtils.getSupertypesWithoutFakes
import static org.jetbrains.kotlin.js.translate.utils.PsiUtils.getPrimaryConstructorParameters; import org.jetbrains.kotlin.js.translate.utils.PsiUtils.getPrimaryConstructorParameters
import static org.jetbrains.kotlin.js.translate.utils.TranslationUtils.simpleReturnFunction; import org.jetbrains.kotlin.js.translate.utils.TranslationUtils.simpleReturnFunction
import static org.jetbrains.kotlin.js.translate.utils.UtilsPackage.generateDelegateCall; import org.jetbrains.kotlin.js.translate.utils.UtilsPackage.generateDelegateCall
import static org.jetbrains.kotlin.resolve.DescriptorUtils.*; import org.jetbrains.kotlin.resolve.DescriptorUtils.*
import static org.jetbrains.kotlin.types.TypeUtils.topologicallySortSuperclassesAndRecordAllInstances; import org.jetbrains.kotlin.types.TypeUtils.topologicallySortSuperclassesAndRecordAllInstances
/** /**
* Generates a definition of a single class. * Generates a definition of a single class.
*/ */
public final class ClassTranslator extends AbstractTranslator { public class ClassTranslator private (private val classDeclaration: JetClassOrObject, context: TranslationContext) : AbstractTranslator(context) {
@NotNull
private final JetClassOrObject classDeclaration;
@NotNull private val descriptor: ClassDescriptor
private final ClassDescriptor descriptor;
@NotNull init {
public static JsInvocation generateClassCreation(@NotNull JetClassOrObject classDeclaration, @NotNull TranslationContext context) { this.descriptor = getClassDescriptor(context.bindingContext(), classDeclaration)
return new ClassTranslator(classDeclaration, context).translate();
} }
@NotNull private fun translateObjectLiteralExpression(): JsExpression {
public static JsExpression generateObjectLiteral(@NotNull JetObjectDeclaration objectDeclaration, @NotNull TranslationContext context) { val containingClass = getContainingClass(descriptor) ?: return translate(context())
return new ClassTranslator(objectDeclaration, context).translateObjectLiteralExpression();
return translateObjectInsideClass(context())
} }
private ClassTranslator(@NotNull JetClassOrObject classDeclaration, @NotNull TranslationContext context) { overloads public fun translate(declarationContext: TranslationContext = context()): JsInvocation {
super(context); return JsInvocation(context().namer().classCreateInvocation(descriptor), getClassCreateInvocationArguments(declarationContext))
this.classDeclaration = classDeclaration;
this.descriptor = getClassDescriptor(context.bindingContext(), classDeclaration);
} }
@NotNull private fun isTrait(): Boolean {
private JsExpression translateObjectLiteralExpression() { return descriptor.getKind() == ClassKind.INTERFACE
ClassDescriptor containingClass = getContainingClass(descriptor);
if (containingClass == null) {
return translate(context());
}
return translateObjectInsideClass(context());
} }
@NotNull private fun getClassCreateInvocationArguments(declarationContext: TranslationContext): List<JsExpression> {
public JsInvocation translate() { var declarationContext = declarationContext
return translate(context()); val invocationArguments = ArrayList<JsExpression>()
}
@NotNull val properties = SmartList<JsPropertyInitializer>()
public JsInvocation translate(@NotNull TranslationContext declarationContext) { val staticProperties = SmartList<JsPropertyInitializer>()
return new JsInvocation(context().namer().classCreateInvocation(descriptor), getClassCreateInvocationArguments(declarationContext));
}
private boolean isTrait() { val isTopLevelDeclaration = context() == declarationContext
return descriptor.getKind().equals(ClassKind.INTERFACE);
}
@NotNull var qualifiedReference: JsNameRef? = null
private List<JsExpression> getClassCreateInvocationArguments(@NotNull TranslationContext declarationContext) {
List<JsExpression> invocationArguments = new ArrayList<JsExpression>();
List<JsPropertyInitializer> properties = new SmartList<JsPropertyInitializer>();
List<JsPropertyInitializer> staticProperties = new SmartList<JsPropertyInitializer>();
boolean isTopLevelDeclaration = context() == declarationContext;
JsNameRef qualifiedReference = null;
if (isTopLevelDeclaration) { if (isTopLevelDeclaration) {
DefinitionPlace definitionPlace = null; var definitionPlace: DefinitionPlace? = null
if (!descriptor.getKind().isSingleton() && !isAnonymousObject(descriptor)) { if (!descriptor.getKind().isSingleton() && !isAnonymousObject(descriptor)) {
qualifiedReference = declarationContext.getQualifiedReference(descriptor); qualifiedReference = declarationContext.getQualifiedReference(descriptor)
JsScope scope = context().getScopeForDescriptor(descriptor); val scope = context().getScopeForDescriptor(descriptor)
definitionPlace = new DefinitionPlace((JsObjectScope) scope, qualifiedReference, staticProperties); definitionPlace = DefinitionPlace(scope as JsObjectScope, qualifiedReference, staticProperties)
} }
declarationContext = declarationContext.newDeclaration(descriptor, definitionPlace); declarationContext = declarationContext.newDeclaration(descriptor, definitionPlace)
} }
declarationContext = fixContextForCompanionObjectAccessing(declarationContext); declarationContext = fixContextForCompanionObjectAccessing(declarationContext)
invocationArguments.add(getSuperclassReferences(declarationContext)); invocationArguments.add(getSuperclassReferences(declarationContext))
DelegationTranslator delegationTranslator = new DelegationTranslator(classDeclaration, context()); val delegationTranslator = DelegationTranslator(classDeclaration, context())
if (!isTrait()) { if (!isTrait()) {
JsFunction initializer = new ClassInitializerTranslator(classDeclaration, declarationContext).generateInitializeMethod(delegationTranslator); val initializer = ClassInitializerTranslator(classDeclaration, declarationContext).generateInitializeMethod(delegationTranslator)
invocationArguments.add(initializer.getBody().getStatements().isEmpty() ? JsLiteral.NULL : initializer); invocationArguments.add(if (initializer.getBody().getStatements().isEmpty()) JsLiteral.NULL else initializer)
} }
translatePropertiesAsConstructorParameters(declarationContext, properties); translatePropertiesAsConstructorParameters(declarationContext, properties)
DeclarationBodyVisitor bodyVisitor = new DeclarationBodyVisitor(properties, staticProperties); val bodyVisitor = DeclarationBodyVisitor(properties, staticProperties)
bodyVisitor.traverseContainer(classDeclaration, declarationContext); bodyVisitor.traverseContainer(classDeclaration, declarationContext)
delegationTranslator.generateDelegated(properties); delegationTranslator.generateDelegated(properties)
if (KotlinBuiltIns.isData(descriptor)) { if (KotlinBuiltIns.isData(descriptor)) {
new JsDataClassGenerator(classDeclaration, declarationContext, properties).generate(); JsDataClassGenerator(classDeclaration, declarationContext, properties).generate()
} }
if (isEnumClass(descriptor)) { if (isEnumClass(descriptor)) {
JsObjectLiteral enumEntries = new JsObjectLiteral(bodyVisitor.getEnumEntryList(), true); val enumEntries = JsObjectLiteral(bodyVisitor.getEnumEntryList(), true)
JsFunction function = simpleReturnFunction(declarationContext.getScopeForDescriptor(descriptor), enumEntries); val function = simpleReturnFunction(declarationContext.getScopeForDescriptor(descriptor), enumEntries)
invocationArguments.add(function); invocationArguments.add(function)
} }
generatedBridgeMethods(properties); generatedBridgeMethods(properties)
boolean hasStaticProperties = !staticProperties.isEmpty(); val hasStaticProperties = !staticProperties.isEmpty()
if (!properties.isEmpty() || hasStaticProperties) { if (!properties.isEmpty() || hasStaticProperties) {
if (properties.isEmpty()) { if (properties.isEmpty()) {
invocationArguments.add(JsLiteral.NULL); invocationArguments.add(JsLiteral.NULL)
} }
else { else {
if (qualifiedReference != null) { if (qualifiedReference != null) {
// about "prototype" - see http://code.google.com/p/jsdoc-toolkit/wiki/TagLends // about "prototype" - see http://code.google.com/p/jsdoc-toolkit/wiki/TagLends
invocationArguments.add(new JsDocComment(JsAstUtils.LENDS_JS_DOC_TAG, new JsNameRef("prototype", qualifiedReference))); invocationArguments.add(JsDocComment(JsAstUtils.LENDS_JS_DOC_TAG, JsNameRef("prototype", qualifiedReference)))
} }
invocationArguments.add(new JsObjectLiteral(properties, true)); invocationArguments.add(JsObjectLiteral(properties, true))
} }
} }
if (hasStaticProperties) { if (hasStaticProperties) {
invocationArguments.add(new JsDocComment(JsAstUtils.LENDS_JS_DOC_TAG, qualifiedReference)); invocationArguments.add(JsDocComment(JsAstUtils.LENDS_JS_DOC_TAG, qualifiedReference))
invocationArguments.add(new JsObjectLiteral(staticProperties, true)); invocationArguments.add(JsObjectLiteral(staticProperties, true))
} }
return invocationArguments; return invocationArguments
} }
private TranslationContext fixContextForCompanionObjectAccessing(TranslationContext declarationContext) { private fun fixContextForCompanionObjectAccessing(declarationContext: TranslationContext): TranslationContext {
var declarationContext = declarationContext
// In Kotlin we can access to companion object members without qualifier just by name, but we should translate it to access with FQ name. // In Kotlin we can access to companion object members without qualifier just by name, but we should translate it to access with FQ name.
// So create alias for companion object receiver parameter. // So create alias for companion object receiver parameter.
ClassDescriptor companionObjectDescriptor = descriptor.getCompanionObjectDescriptor(); val companionObjectDescriptor = descriptor.getCompanionObjectDescriptor()
if (companionObjectDescriptor != null) { if (companionObjectDescriptor != null) {
JsExpression referenceToClass = translateAsFQReference(companionObjectDescriptor.getContainingDeclaration(), declarationContext); val referenceToClass = translateAsFQReference(companionObjectDescriptor.getContainingDeclaration(), declarationContext)
JsExpression companionObjectAccessor = Namer.getCompanionObjectAccessor(referenceToClass); val companionObjectAccessor = Namer.getCompanionObjectAccessor(referenceToClass)
ReceiverParameterDescriptor companionObjectReceiver = getReceiverParameterForDeclaration(companionObjectDescriptor); val companionObjectReceiver = getReceiverParameterForDeclaration(companionObjectDescriptor)
declarationContext.aliasingContext().registerAlias(companionObjectReceiver, companionObjectAccessor); declarationContext.aliasingContext().registerAlias(companionObjectReceiver, companionObjectAccessor)
} }
// Overlap alias of companion object receiver for accessing from containing class(see previous if block), // Overlap alias of companion object receiver for accessing from containing class(see previous if block),
// because inside companion object we should use simple name for access. // because inside companion object we should use simple name for access.
if (isCompanionObject(descriptor)) { if (isCompanionObject(descriptor)) {
declarationContext = declarationContext.innerContextWithAliased(descriptor.getThisAsReceiverParameter(), JsLiteral.THIS); declarationContext = declarationContext.innerContextWithAliased(descriptor.getThisAsReceiverParameter(), JsLiteral.THIS)
} }
return declarationContext; return declarationContext
} }
private JsExpression getSuperclassReferences(@NotNull TranslationContext declarationContext) { private fun getSuperclassReferences(declarationContext: TranslationContext): JsExpression {
List<JsExpression> superClassReferences = getSupertypesNameReferences(); val superClassReferences = getSupertypesNameReferences()
if (superClassReferences.isEmpty()) { if (superClassReferences.isEmpty()) {
return JsLiteral.NULL; return JsLiteral.NULL
} else { }
return simpleReturnFunction(declarationContext.scope(), new JsArrayLiteral(superClassReferences)); else {
return simpleReturnFunction(declarationContext.scope(), JsArrayLiteral(superClassReferences))
} }
} }
@NotNull private fun getSupertypesNameReferences(): List<JsExpression> {
private List<JsExpression> getSupertypesNameReferences() { val supertypes = getSupertypesWithoutFakes(descriptor)
List<JetType> supertypes = getSupertypesWithoutFakes(descriptor);
if (supertypes.isEmpty()) { if (supertypes.isEmpty()) {
return Collections.emptyList(); return emptyList()
} }
if (supertypes.size() == 1) { if (supertypes.size() == 1) {
JetType type = supertypes.get(0); val type = supertypes.get(0)
ClassDescriptor supertypeDescriptor = getClassDescriptorForType(type); val supertypeDescriptor = getClassDescriptorForType(type)
return Collections.<JsExpression>singletonList(getClassReference(supertypeDescriptor)); return listOf<JsExpression>(getClassReference(supertypeDescriptor))
} }
Set<TypeConstructor> supertypeConstructors = new HashSet<TypeConstructor>(); val supertypeConstructors = HashSet<TypeConstructor>()
for (JetType type : supertypes) { for (type in supertypes) {
supertypeConstructors.add(type.getConstructor()); supertypeConstructors.add(type.getConstructor())
} }
List<TypeConstructor> sortedAllSuperTypes = topologicallySortSuperclassesAndRecordAllInstances(descriptor.getDefaultType(), val sortedAllSuperTypes = topologicallySortSuperclassesAndRecordAllInstances(descriptor.getDefaultType(), HashMap<TypeConstructor, Set<JetType>>(), HashSet<TypeConstructor>())
new HashMap<TypeConstructor, Set<JetType>>(), val supertypesRefs = ArrayList<JsExpression>()
new HashSet<TypeConstructor>()); for (typeConstructor in sortedAllSuperTypes) {
List<JsExpression> supertypesRefs = new ArrayList<JsExpression>();
for (TypeConstructor typeConstructor : sortedAllSuperTypes) {
if (supertypeConstructors.contains(typeConstructor)) { if (supertypeConstructors.contains(typeConstructor)) {
ClassDescriptor supertypeDescriptor = getClassDescriptorForTypeConstructor(typeConstructor); val supertypeDescriptor = getClassDescriptorForTypeConstructor(typeConstructor)
supertypesRefs.add(getClassReference(supertypeDescriptor)); supertypesRefs.add(getClassReference(supertypeDescriptor))
} }
} }
return supertypesRefs; return supertypesRefs
} }
@NotNull private fun getClassReference(superClassDescriptor: ClassDescriptor): JsNameRef {
private JsNameRef getClassReference(@NotNull ClassDescriptor superClassDescriptor) { return context().getQualifiedReference(superClassDescriptor)
return context().getQualifiedReference(superClassDescriptor);
} }
private void translatePropertiesAsConstructorParameters(@NotNull TranslationContext classDeclarationContext, private fun translatePropertiesAsConstructorParameters(classDeclarationContext: TranslationContext, result: MutableList<JsPropertyInitializer>) {
@NotNull List<JsPropertyInitializer> result) { for (parameter in getPrimaryConstructorParameters(classDeclaration)) {
for (JetParameter parameter : getPrimaryConstructorParameters(classDeclaration)) { val descriptor = getPropertyDescriptorForConstructorParameter(bindingContext(), parameter)
PropertyDescriptor descriptor = getPropertyDescriptorForConstructorParameter(bindingContext(), parameter);
if (descriptor != null) { if (descriptor != null) {
PropertyTranslatorPackage.translateAccessors(descriptor, result, classDeclarationContext); translateAccessors(descriptor, result, classDeclarationContext)
} }
} }
} }
@NotNull private fun translateObjectInsideClass(outerClassContext: TranslationContext): JsExpression {
private JsExpression translateObjectInsideClass(@NotNull TranslationContext outerClassContext) { val `fun` = JsFunction(outerClassContext.scope(), JsBlock(), "initializer for " + descriptor.getName().asString())
JsFunction fun = new JsFunction(outerClassContext.scope(), new JsBlock(), "initializer for " + descriptor.getName().asString()); val funContext = outerClassContext.newFunctionBodyWithUsageTracker(`fun`, descriptor)
TranslationContext funContext = outerClassContext.newFunctionBodyWithUsageTracker(fun, descriptor);
fun.getBody().getStatements().add(new JsReturn(translate(funContext))); `fun`.getBody().getStatements().add(JsReturn(translate(funContext)))
return ExpressionPackage.withCapturedParameters(fun, funContext, outerClassContext, descriptor); return `fun`.withCapturedParameters(funContext, outerClassContext, descriptor)
} }
private void generatedBridgeMethods(@NotNull List<JsPropertyInitializer> properties) { private fun generatedBridgeMethods(properties: MutableList<JsPropertyInitializer>) {
if (isTrait()) return; if (isTrait()) return
generateBridgesToTraitImpl(properties); generateBridgesToTraitImpl(properties)
generateOtherBridges(properties); generateOtherBridges(properties)
} }
private void generateBridgesToTraitImpl(List<JsPropertyInitializer> properties) { private fun generateBridgesToTraitImpl(properties: MutableList<JsPropertyInitializer>) {
for(Map.Entry<FunctionDescriptor, FunctionDescriptor> entry : CodegenUtil.getTraitMethods(descriptor).entrySet()) { for (entry in CodegenUtil.getTraitMethods(descriptor).entrySet()) {
if (!areNamesEqual(entry.getKey(), entry.getValue())) { if (!areNamesEqual(entry.getKey(), entry.getValue())) {
properties.add(generateDelegateCall(entry.getValue(), entry.getKey(), JsLiteral.THIS, context())); properties.add(generateDelegateCall(entry.getValue(), entry.getKey(), JsLiteral.THIS, context()))
} }
} }
} }
private void generateOtherBridges(List<JsPropertyInitializer> properties) { private fun generateOtherBridges(properties: MutableList<JsPropertyInitializer>) {
for (DeclarationDescriptor memberDescriptor : descriptor.getDefaultType().getMemberScope().getAllDescriptors()) { for (memberDescriptor in descriptor.getDefaultType().getMemberScope().getAllDescriptors()) {
if (memberDescriptor instanceof FunctionDescriptor) { if (memberDescriptor is FunctionDescriptor) {
FunctionDescriptor functionDescriptor = (FunctionDescriptor) memberDescriptor; val bridgesToGenerate = generateBridgesForFunctionDescriptor<FunctionDescriptor>(memberDescriptor, ID)
Set<Bridge<FunctionDescriptor>> bridgesToGenerate =
BridgesPackage.generateBridgesForFunctionDescriptor(functionDescriptor, UtilsPackage.<FunctionDescriptor>getID());
for (Bridge<FunctionDescriptor> bridge : bridgesToGenerate) { for (bridge in bridgesToGenerate) {
generateBridge(bridge, properties); generateBridge(bridge, properties)
} }
} }
} }
} }
private void generateBridge( private fun generateBridge(bridge: Bridge<FunctionDescriptor>, properties: MutableList<JsPropertyInitializer>) {
@NotNull Bridge<FunctionDescriptor> bridge, val fromDescriptor = bridge.from
@NotNull List<JsPropertyInitializer> properties val toDescriptor = bridge.to
) { if (areNamesEqual(fromDescriptor, toDescriptor)) return
FunctionDescriptor fromDescriptor = bridge.getFrom();
FunctionDescriptor toDescriptor = bridge.getTo();
if (areNamesEqual(fromDescriptor, toDescriptor)) return;
if (fromDescriptor.getKind().isReal() && if (fromDescriptor.getKind().isReal() && fromDescriptor.getModality() != Modality.ABSTRACT && !toDescriptor.getKind().isReal())
fromDescriptor.getModality() != Modality.ABSTRACT && return
!toDescriptor.getKind().isReal()) return;
properties.add(generateDelegateCall(fromDescriptor, toDescriptor, JsLiteral.THIS, context())); properties.add(generateDelegateCall(fromDescriptor, toDescriptor, JsLiteral.THIS, context()))
} }
private boolean areNamesEqual(@NotNull FunctionDescriptor first, @NotNull FunctionDescriptor second) { private fun areNamesEqual(first: FunctionDescriptor, second: FunctionDescriptor): Boolean {
JsName firstName = context().getNameForDescriptor(first); val firstName = context().getNameForDescriptor(first)
JsName secondName = context().getNameForDescriptor(second); val secondName = context().getNameForDescriptor(second)
return firstName.getIdent().equals(secondName.getIdent()); return firstName.getIdent() == secondName.getIdent()
}
companion object {
public fun generateClassCreation(classDeclaration: JetClassOrObject, context: TranslationContext): JsInvocation {
return ClassTranslator(classDeclaration, context).translate()
}
public fun generateObjectLiteral(objectDeclaration: JetObjectDeclaration, context: TranslationContext): JsExpression {
return ClassTranslator(objectDeclaration, context).translateObjectLiteralExpression()
}
} }
} }