<s> package org . eclipse . jdt . internal . compiler . ast ; import org . eclipse . jdt . core . compiler . CharOperation ; import org . eclipse . jdt . internal . compiler . ASTVisitor ; import org . eclipse . jdt . internal . compiler . classfmt . ClassFileConstants ; import org . eclipse . jdt . internal . compiler . codegen . CodeStream ; import org . eclipse . jdt . internal . compiler . codegen . Opcodes ; import org . eclipse . jdt . internal . compiler . flow . FlowContext ; import org . eclipse . jdt . internal . compiler . flow . FlowInfo ; import org . eclipse . jdt . internal . compiler . impl . CompilerOptions ; import org . eclipse . jdt . internal . compiler . impl . Constant ; import org . eclipse . jdt . internal . compiler . impl . ReferenceContext ; import org . eclipse . jdt . internal . compiler . lookup . Binding ; import org . eclipse . jdt . internal . compiler . lookup . BlockScope ; import org . eclipse . jdt . internal . compiler . lookup . ExtraCompilerModifiers ; import org . eclipse . jdt . internal . compiler . lookup . InvocationSite ; import org . eclipse . jdt . internal . compiler . lookup . MethodBinding ; import org . eclipse . jdt . internal . compiler . lookup . MissingTypeBinding ; import org . eclipse . jdt . internal . compiler . lookup . PolymorphicMethodBinding ; import org . eclipse . jdt . internal . compiler . lookup . ProblemMethodBinding ; import org . eclipse . jdt . internal . compiler . lookup . ProblemReasons ; import org . eclipse . jdt . internal . compiler . lookup . ProblemReferenceBinding ; import org . eclipse . jdt . internal . compiler . lookup . RawTypeBinding ; import org . eclipse . jdt . internal . compiler . lookup . ReferenceBinding ; import org . eclipse . jdt . internal . compiler . lookup . Scope ; import org . eclipse . jdt . internal . compiler . lookup . SourceTypeBinding ; import org . eclipse . jdt . internal . compiler . lookup . TagBits ; import org . eclipse . jdt . internal . compiler . lookup . TypeBinding ; import org . eclipse . jdt . internal . compiler . lookup . TypeIds ; import org . eclipse . jdt . internal . compiler . problem . ProblemSeverities ; public class MessageSend extends Expression implements InvocationSite { public Expression receiver ; public char [ ] selector ; public Expression [ ] arguments ; public MethodBinding binding ; public MethodBinding syntheticAccessor ; public TypeBinding expectedType ; public long nameSourcePosition ; public TypeBinding actualReceiverType ; public TypeBinding valueCast ; public TypeReference [ ] typeArguments ; public TypeBinding [ ] genericTypeArguments ; public FlowInfo analyseCode ( BlockScope currentScope , FlowContext flowContext , FlowInfo flowInfo ) { boolean nonStatic = ! this . binding . isStatic ( ) ; flowInfo = this . receiver . analyseCode ( currentScope , flowContext , flowInfo , nonStatic ) . unconditionalInits ( ) ; if ( nonStatic ) { this . receiver . checkNPE ( currentScope , flowContext , flowInfo ) ; if ( this . receiver . isThis ( ) ) { currentScope . resetEnclosingMethodStaticFlag ( ) ; } } else if ( this . receiver . isThis ( ) ) { if ( ( this . receiver . bits & ASTNode . IsImplicitThis ) == 0 ) { currentScope . resetEnclosingMethodStaticFlag ( ) ; } } if ( this . arguments != null ) { int length = this . arguments . length ; for ( int i = 0 ; i < length ; i ++ ) { if ( ( this . arguments [ i ] . implicitConversion & TypeIds . UNBOXING ) != 0 ) { this . arguments [ i ] . checkNPE ( currentScope , flowContext , flowInfo ) ; } flowInfo = this . arguments [ i ] . analyseCode ( currentScope , flowContext , flowInfo ) . unconditionalInits ( ) ; } } ReferenceBinding [ ] thrownExceptions ; if ( ( thrownExceptions = this . binding . thrownExceptions ) != Binding . NO_EXCEPTIONS ) { if ( ( this . bits & ASTNode . Unchecked ) != 0 && this . genericTypeArguments == null ) { thrownExceptions = currentScope . environment ( ) . convertToRawTypes ( this . binding . thrownExceptions , true , true ) ; } flowContext . checkExceptionHandlers ( thrownExceptions , this , flowInfo . copy ( ) , currentScope ) ; } manageSyntheticAccessIfNecessary ( currentScope , flowInfo ) ; return flowInfo ; } public void computeConversion ( Scope scope , TypeBinding runtimeTimeType , TypeBinding compileTimeType ) { if ( runtimeTimeType == null || compileTimeType == null ) return ; if ( this . binding != null && this . binding . isValidBinding ( ) ) { MethodBinding originalBinding = this . binding . original ( ) ; TypeBinding originalType = originalBinding . returnType ; if ( originalType . leafComponentType ( ) . isTypeVariable ( ) ) { TypeBinding targetType = ( ! compileTimeType . isBaseType ( ) && runtimeTimeType . isBaseType ( ) ) ? compileTimeType : runtimeTimeType ; this . valueCast = originalType . genericCast ( targetType ) ; } else if ( this . binding == scope . environment ( ) . arrayClone && runtimeTimeType . id != TypeIds . T_JavaLangObject && scope . compilerOptions ( ) . sourceLevel >= ClassFileConstants . JDK1_5 ) { this . valueCast = runtimeTimeType ; } if ( this . valueCast instanceof ReferenceBinding ) { ReferenceBinding referenceCast = ( ReferenceBinding ) this . valueCast ; if ( ! referenceCast . canBeSeenBy ( scope ) ) { scope . problemReporter ( ) . invalidType ( this , new ProblemReferenceBinding ( CharOperation . splitOn ( '.' , referenceCast . shortReadableName ( ) ) , referenceCast , ProblemReasons . NotVisible ) ) ; } } } super . computeConversion ( scope , runtimeTimeType , compileTimeType ) ; } public void generateCode ( BlockScope currentScope , CodeStream codeStream , boolean valueRequired ) { int pc = codeStream . position ; MethodBinding codegenBinding = this . binding instanceof PolymorphicMethodBinding ? this . binding : this . binding . original ( ) ; boolean isStatic = codegenBinding . isStatic ( ) ; if ( isStatic ) { this . receiver . generateCode ( currentScope , codeStream , false ) ; } else if ( ( this . bits & ASTNode . DepthMASK ) != 0 && this . receiver . isImplicitThis ( ) ) { ReferenceBinding targetType = currentScope . enclosingSourceType ( ) . enclosingTypeAt ( ( this . bits & ASTNode . DepthMASK ) >> ASTNode . DepthSHIFT ) ; Object [ ] path = currentScope . getEmulationPath ( targetType , true , false ) ; codeStream . generateOuterAccess ( path , this , targetType , currentScope ) ; } else { this . receiver . generateCode ( currentScope , codeStream , true ) ; if ( ( this . bits & NeedReceiverGenericCast ) != 0 ) { codeStream . checkcast ( this . actualReceiverType ) ; } } codeStream . recordPositionsFrom ( pc , this . sourceStart ) ; generateArguments ( this . binding , this . arguments , currentScope , codeStream ) ; pc = codeStream . position ; if ( this . syntheticAccessor == null ) { TypeBinding constantPoolDeclaringClass = CodeStream . getConstantPoolDeclaringClass ( currentScope , codegenBinding , this . actualReceiverType , this . receiver . isImplicitThis ( ) ) ; if ( isStatic ) { codeStream . invoke ( Opcodes . OPC_invokestatic , codegenBinding , constantPoolDeclaringClass ) ; } else if ( ( this . receiver . isSuper ( ) ) || codegenBinding . isPrivate ( ) ) { codeStream . invoke ( Opcodes . OPC_invokespecial , codegenBinding , constantPoolDeclaringClass ) ; } else if ( constantPoolDeclaringClass . isInterface ( ) ) { codeStream . invoke ( Opcodes . OPC_invokeinterface , codegenBinding , constantPoolDeclaringClass ) ; } else { codeStream . invoke ( Opcodes . OPC_invokevirtual , codegenBinding , constantPoolDeclaringClass ) ; } } else { codeStream . invoke ( Opcodes . OPC_invokestatic , this . syntheticAccessor , null ) ; } if ( this . valueCast != null ) codeStream . checkcast ( this . valueCast ) ; if ( valueRequired ) { codeStream . generateImplicitConversion ( this . implicitConversion ) ; } else { boolean isUnboxing = ( this . implicitConversion & TypeIds . UNBOXING ) != 0 ; if ( isUnboxing ) codeStream . generateImplicitConversion ( this . implicitConversion ) ; switch ( isUnboxing ? postConversionType ( currentScope ) . id : codegenBinding . returnType . id ) { case T_long : case T_double : codeStream . pop2 ( ) ; break ; case T_void : break ; default : codeStream . pop ( ) ; } } codeStream . recordPositionsFrom ( pc , ( int ) ( this . nameSourcePosition >>> 32 ) ) ; } public TypeBinding [ ] genericTypeArguments ( ) { return this . genericTypeArguments ; } public boolean isSuperAccess ( ) { return this . receiver . isSuper ( ) ; } public boolean isTypeAccess ( ) { return this . receiver != null && this . receiver . isTypeReference ( ) ; } public void manageSyntheticAccessIfNecessary ( BlockScope currentScope , FlowInfo flowInfo ) { if ( ( flowInfo . tagBits & FlowInfo . UNREACHABLE_OR_DEAD ) != 0 ) return ; MethodBinding codegenBinding = this . binding . original ( ) ; if ( this . binding . isPrivate ( ) ) { if ( currentScope . enclosingSourceType ( ) != codegenBinding . declaringClass ) { this . syntheticAccessor = ( ( SourceTypeBinding ) codegenBinding . declaringClass ) . addSyntheticMethod ( codegenBinding , false ) ; currentScope . problemReporter ( ) . needToEmulateMethodAccess ( codegenBinding , this ) ; return ; } } else if ( this . receiver instanceof QualifiedSuperReference ) { SourceTypeBinding destinationType = ( SourceTypeBinding ) ( ( ( QualifiedSuperReference ) this . receiver ) . currentCompatibleType ) ; this . syntheticAccessor = destinationType . addSyntheticMethod ( codegenBinding , isSuperAccess ( ) ) ; currentScope . problemReporter ( ) . needToEmulateMethodAccess ( codegenBinding , this ) ; return ; } else if ( this . binding . isProtected ( ) ) { SourceTypeBinding enclosingSourceType ; if ( ( ( this . bits & ASTNode . DepthMASK ) != 0 ) && codegenBinding . declaringClass . getPackage ( ) != ( enclosingSourceType = currentScope . enclosingSourceType ( ) ) . getPackage ( ) ) { SourceTypeBinding currentCompatibleType = ( SourceTypeBinding ) enclosingSourceType . enclosingTypeAt ( ( this . bits & ASTNode . DepthMASK ) >> ASTNode . DepthSHIFT ) ; this . syntheticAccessor = currentCompatibleType . addSyntheticMethod ( codegenBinding , isSuperAccess ( ) ) ; currentScope . problemReporter ( ) . needToEmulateMethodAccess ( codegenBinding , this ) ; return ; } } } public int nullStatus ( FlowInfo flowInfo ) { return FlowInfo . UNKNOWN ; } public TypeBinding postConversionType ( Scope scope ) { TypeBinding convertedType = this . resolvedType ; if ( this . valueCast != null ) convertedType = this . valueCast ; int runtimeType = ( this . implicitConversion & TypeIds . IMPLICIT_CONVERSION_MASK ) >> 4 ; switch ( runtimeType ) { case T_boolean : convertedType = TypeBinding . BOOLEAN ; break ; case T_byte : convertedType = TypeBinding . BYTE ; break ; case T_short : convertedType = TypeBinding . SHORT ; break ; case T_char : convertedType = TypeBinding . CHAR ; break ; case T_int : convertedType = TypeBinding . INT ; break ; case T_float : convertedType = TypeBinding . FLOAT ; break ; case T_long : convertedType = TypeBinding . LONG ; break ; case T_double : convertedType = TypeBinding . DOUBLE ; break ; default : } if ( ( this . implicitConversion & TypeIds . BOXING ) != 0 ) { convertedType = scope . environment ( ) . computeBoxingType ( convertedType ) ; } return convertedType ; } public StringBuffer printExpression ( int indent , StringBuffer output ) { if ( ! this . receiver . isImplicitThis ( ) ) this . receiver . printExpression ( 0 , output ) . append ( '.' ) ; if ( this . typeArguments != null ) { output . append ( '<' ) ; int max = this . typeArguments . length - 1 ; for ( int j = 0 ; j < max ; j ++ ) { this . typeArguments [ j ] . print ( 0 , output ) ; output . append ( ", " ) ; } this . typeArguments [ max ] . print ( 0 , output ) ; output . append ( '>' ) ; } output . append ( this . selector ) . append ( '(' ) ; if ( this . arguments != null ) { for ( int i = 0 ; i < this . arguments . length ; i ++ ) { if ( i > 0 ) output . append ( ", " ) ; this . arguments [ i ] . printExpression ( 0 , output ) ; } } return output . append ( ')' ) ; } public TypeBinding resolveType ( BlockScope scope ) { this . constant = Constant . NotAConstant ; boolean receiverCast = false , argsContainCast = false ; if ( this . receiver instanceof CastExpression ) { this . receiver . bits |= ASTNode . DisableUnnecessaryCastCheck ; receiverCast = true ; } this . actualReceiverType = this . receiver . resolveType ( scope ) ; boolean receiverIsType = this . receiver instanceof NameReference && ( ( ( NameReference ) this . receiver ) . bits & Binding . TYPE ) != 0 ; if ( receiverCast && this . actualReceiverType != null ) { if ( ( ( CastExpression ) this . receiver ) . expression . resolvedType == this . actualReceiverType ) { scope . problemReporter ( ) . unnecessaryCast ( ( CastExpression ) this . receiver ) ; } } if ( this . typeArguments != null ) { int length = this . typeArguments . length ; boolean argHasError = scope . compilerOptions ( ) . sourceLevel < ClassFileConstants . JDK1_5 ; this . genericTypeArguments = new TypeBinding [ length ] ; for ( int i = 0 ; i < length ; i ++ ) { TypeReference typeReference = this . typeArguments [ i ] ; if ( ( this . genericTypeArguments [ i ] = typeReference . resolveType ( scope , true ) ) == null ) { argHasError = true ; } if ( argHasError && typeReference instanceof Wildcard ) { scope . problemReporter ( ) . illegalUsageOfWildcard ( typeReference ) ; } } if ( argHasError ) { if ( this . arguments != null ) { for ( int i = 0 , max = this . arguments . length ; i < max ; i ++ ) { this . arguments [ i ] . resolveType ( scope ) ; } } return null ; } } TypeBinding [ ] argumentTypes = Binding . NO_PARAMETERS ; if ( this . arguments != null ) { boolean argHasError = false ; int length = this . arguments . length ; argumentTypes = new TypeBinding [ length ] ; for ( int i = 0 ; i < length ; i ++ ) { Expression argument = this . arguments [ i ] ; if ( argument instanceof CastExpression ) { argument . bits |= ASTNode . DisableUnnecessaryCastCheck ; argsContainCast = true ; } if ( ( argumentTypes [ i ] = argument . resolveType ( scope ) ) == null ) { argHasError = true ; } } if ( argHasError ) { if ( this . actualReceiverType instanceof ReferenceBinding ) { TypeBinding [ ] pseudoArgs = new TypeBinding [ length ] ; for ( int i = length ; -- i >= 0 ; ) pseudoArgs [ i ] = argumentTypes [ i ] == null ? TypeBinding . NULL : argumentTypes [ i ] ; this . binding = this . receiver . isImplicitThis ( ) ? scope . getImplicitMethod ( this . selector , pseudoArgs , this ) : scope . findMethod ( ( ReferenceBinding ) this . actualReceiverType , this . selector , pseudoArgs , this ) ; if ( this . binding != null && ! this . binding . isValidBinding ( ) ) { MethodBinding closestMatch = ( ( ProblemMethodBinding ) this . binding ) . closestMatch ; if ( closestMatch != null ) { if ( closestMatch . original ( ) . typeVariables != Binding . NO_TYPE_VARIABLES ) { closestMatch = scope . environment ( ) . createParameterizedGenericMethod ( closestMatch . original ( ) , ( RawTypeBinding ) null ) ; } this . binding = closestMatch ; MethodBinding closestMatchOriginal = closestMatch . original ( ) ; if ( closestMatchOriginal . isOrEnclosedByPrivateType ( ) && ! scope . isDefinedInMethod ( closestMatchOriginal ) ) { closestMatchOriginal . modifiers |= ExtraCompilerModifiers . AccLocallyUsed ; } } } } return null ; } } if ( this . actualReceiverType == null ) { return null ; } if ( this . actualReceiverType . isBaseType ( ) ) { scope . problemReporter ( ) . errorNoMethodFor ( this , this . actualReceiverType , argumentTypes ) ; return null ; } this . binding = this . receiver . isImplicitThis ( ) ? scope . getImplicitMethod ( this . selector , argumentTypes , this ) : scope . getMethod ( this . actualReceiverType , this . selector , argumentTypes , this ) ; if ( ! this . binding . isValidBinding ( ) ) { if ( this . binding . declaringClass == null ) { if ( this . actualReceiverType instanceof ReferenceBinding ) { this . binding . declaringClass = ( ReferenceBinding ) this . actualReceiverType ; } else { scope . problemReporter ( ) . errorNoMethodFor ( this , this . actualReceiverType , argumentTypes ) ; return null ; } } ReferenceBinding declaringClass = this . binding . declaringClass ; boolean avoidSecondary = declaringClass != null && declaringClass . isAnonymousType ( ) && declaringClass . superclass ( ) instanceof MissingTypeBinding ; if ( ! avoidSecondary ) scope . problemReporter ( ) . invalidMethod ( this , this . binding ) ; MethodBinding closestMatch = ( ( ProblemMethodBinding ) this . binding ) . closestMatch ; switch ( this . binding . problemId ( ) ) { case ProblemReasons . Ambiguous : break ; case ProblemReasons . NotVisible : case ProblemReasons . NonStaticReferenceInConstructorInvocation : case ProblemReasons . NonStaticReferenceInStaticContext : case ProblemReasons . ReceiverTypeNotVisible : case ProblemReasons . ParameterBoundMismatch : if ( closestMatch != null ) this . resolvedType = closestMatch . returnType ; break ; } if ( closestMatch != null ) { this . binding = closestMatch ; MethodBinding closestMatchOriginal = closestMatch . original ( ) ; if ( closestMatchOriginal . isOrEnclosedByPrivateType ( ) && ! scope . isDefinedInMethod ( closestMatchOriginal ) ) { closestMatchOriginal . modifiers |= ExtraCompilerModifiers . AccLocallyUsed ; } } return ( this . resolvedType != null && ( this . resolvedType . tagBits & TagBits . HasMissingType ) == 0 ) ? this . resolvedType : null ; } final CompilerOptions compilerOptions = scope . compilerOptions ( ) ; if ( compilerOptions . complianceLevel <= ClassFileConstants . JDK1_6 && this . binding . isPolymorphic ( ) ) { scope . problemReporter ( ) . polymorphicMethodNotBelow17 ( this ) ; return null ; } if ( ( ( this . bits & ASTNode . InsideExpressionStatement ) != 0 ) && this . binding . isPolymorphic ( ) ) { this . binding = scope . environment ( ) . updatePolymorphicMethodReturnType ( ( PolymorphicMethodBinding ) this . binding , TypeBinding . VOID ) ; } if ( ( this . binding . tagBits & TagBits . HasMissingType ) != 0 ) { scope . problemReporter ( ) . missingTypeInMethod ( this , this . binding ) ; } if ( ! this . binding . isStatic ( ) ) { if ( receiverIsType ) { scope . problemReporter ( ) . mustUseAStaticMethod ( this , this . binding ) ; if ( this . actualReceiverType . isRawType ( ) && ( this . receiver . bits & ASTNode . IgnoreRawTypeCheck ) == 0 && compilerOptions . getSeverity ( CompilerOptions . RawTypeReference ) != ProblemSeverities . Ignore ) { scope . problemReporter ( ) . rawTypeReference ( this . receiver , this . actualReceiverType ) ; } } else { TypeBinding oldReceiverType = this . actualReceiverType ; this . actualReceiverType = this . actualReceiverType . getErasureCompatibleType ( this . binding . declaringClass ) ; this . receiver . computeConversion ( scope , this . actualReceiverType , this . actualReceiverType ) ; if ( this . actualReceiverType != oldReceiverType && this . receiver . postConversionType ( scope ) != this . actualReceiverType ) { this . bits |= NeedReceiverGenericCast ; } } } else { if ( ! ( this . receiver . isImplicitThis ( ) || this . receiver . isSuper ( ) || receiverIsType ) ) { scope . problemReporter ( ) . nonStaticAccessToStaticMethod ( this , this . binding ) ; } if ( ! this . receiver . isImplicitThis ( ) && this . binding . declaringClass != this . actualReceiverType ) { scope . problemReporter ( ) . indirectAccessToStaticMethod ( this , this . binding ) ; } } if ( checkInvocationArguments ( scope , this . receiver , this . actualReceiverType , this . binding , this . arguments , argumentTypes , argsContainCast , this ) ) { this . bits |= ASTNode . Unchecked ; } if ( this . binding . isAbstract ( ) ) { if ( this . receiver . isSuper ( ) ) { scope . problemReporter ( ) . cannotDireclyInvokeAbstractMethod ( this , this . binding ) ; } } if ( isMethodUseDeprecated ( this . binding , scope , true ) ) scope . problemReporter ( ) . deprecatedMethod ( this . binding , this ) ; if ( this . binding == scope . environment ( ) . arrayClone && compilerOptions . sourceLevel >= ClassFileConstants . JDK1_5 ) { this . resolvedType = this . actualReceiverType ; } else { TypeBinding returnType ; if ( ( this . bits & ASTNode . Unchecked ) != 0 && this . genericTypeArguments == null ) { returnType = this . binding . returnType ; if ( returnType != null ) { returnType = scope . environment ( ) . convertToRawType ( returnType . erasure ( ) , true ) ; } } else { returnType = this . binding . returnType ; if ( returnType != null ) { returnType = returnType . capture ( scope , this . sourceEnd ) ; } } this . resolvedType = returnType ; } if ( this . receiver . isSuper ( ) && compilerOptions . getSeverity ( CompilerOptions . OverridingMethodWithoutSuperInvocation ) != ProblemSeverities . Ignore ) { final ReferenceContext referenceContext = scope . methodScope ( ) . referenceContext ; if ( referenceContext instanceof AbstractMethodDeclaration ) { final AbstractMethodDeclaration abstractMethodDeclaration = ( AbstractMethodDeclaration ) referenceContext ; MethodBinding enclosingMethodBinding = abstractMethodDeclaration . binding ; if ( enclosingMethodBinding . isOverriding ( ) && CharOperation . equals ( this . binding . selector , enclosingMethodBinding . selector ) && this . binding . areParametersEqual ( enclosingMethodBinding ) ) { abstractMethodDeclaration . bits |= ASTNode . OverridingMethodWithSupercall ; } } } if ( this . typeArguments != null && this . binding . original ( ) . typeVariables == Binding . NO_TYPE_VARIABLES ) { scope . problemReporter ( ) . unnecessaryTypeArgumentsForMethodInvocation ( this . binding , this . genericTypeArguments , this . typeArguments ) ; } return ( this . resolvedType . tagBits & TagBits . HasMissingType ) == 0 ? this . resolvedType : null ; } public void setActualReceiverType ( ReferenceBinding receiverType ) { if ( receiverType == null ) return ; this . actualReceiverType = receiverType ; } public void setDepth ( int depth ) { this . bits &= ~ ASTNode . DepthMASK ; if ( depth > 0 ) { this . bits |= ( depth & 0xFF ) << ASTNode . DepthSHIFT ; } } public void setExpectedType ( TypeBinding expectedType ) { this . expectedType = expectedType ; } public void setFieldIndex ( int depth ) { } public TypeBinding expectedType ( ) { return this . expectedType ; } public void traverse ( ASTVisitor visitor , BlockScope blockScope ) { if ( visitor . visit ( this , blockScope ) ) { this . receiver . traverse ( visitor , blockScope ) ; if ( this . typeArguments != null ) { for ( int i = 0 , typeArgumentsLength = this . typeArguments . length ; i < typeArgumentsLength ; i ++ ) { this . typeArguments [ i ] . traverse ( visitor , blockScope ) ; } } if ( this . arguments != null ) { int argumentsLength = this . arguments . length ; for ( int i = 0 ; i < argumentsLength ; i ++ ) this . arguments [ i ] . traverse ( visitor , blockScope ) ; } } visitor . endVisit ( this , blockScope ) ; } } </s>