001 /* 002 * Copyright 2010-2015 JetBrains s.r.o. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017 package org.jetbrains.kotlin.codegen; 018 019 import com.intellij.openapi.util.Pair; 020 import com.intellij.openapi.util.text.StringUtil; 021 import com.intellij.psi.PsiElement; 022 import org.jetbrains.annotations.NotNull; 023 import org.jetbrains.annotations.Nullable; 024 import org.jetbrains.kotlin.codegen.context.*; 025 import org.jetbrains.kotlin.codegen.state.GenerationState; 026 import org.jetbrains.kotlin.codegen.state.JetTypeMapper; 027 import org.jetbrains.kotlin.descriptors.*; 028 import org.jetbrains.kotlin.load.java.JvmAbi; 029 import org.jetbrains.kotlin.name.Name; 030 import org.jetbrains.kotlin.psi.*; 031 import org.jetbrains.kotlin.psi.psiUtil.PsiUtilPackage; 032 import org.jetbrains.kotlin.resolve.BindingContext; 033 import org.jetbrains.kotlin.resolve.DescriptorFactory; 034 import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils; 035 import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall; 036 import org.jetbrains.kotlin.resolve.constants.CompileTimeConstant; 037 import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature; 038 import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedPropertyDescriptor; 039 import org.jetbrains.kotlin.types.ErrorUtils; 040 import org.jetbrains.kotlin.types.JetType; 041 import org.jetbrains.org.objectweb.asm.FieldVisitor; 042 import org.jetbrains.org.objectweb.asm.MethodVisitor; 043 import org.jetbrains.org.objectweb.asm.Opcodes; 044 import org.jetbrains.org.objectweb.asm.Type; 045 import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter; 046 import org.jetbrains.org.objectweb.asm.commons.Method; 047 048 import java.util.List; 049 050 import static org.jetbrains.kotlin.codegen.AsmUtil.*; 051 import static org.jetbrains.kotlin.codegen.JvmCodegenUtil.isInterface; 052 import static org.jetbrains.kotlin.codegen.JvmSerializationBindings.*; 053 import static org.jetbrains.kotlin.resolve.DescriptorUtils.isCompanionObject; 054 import static org.jetbrains.kotlin.resolve.DescriptorUtils.isTrait; 055 import static org.jetbrains.kotlin.resolve.jvm.AsmTypes.PROPERTY_METADATA_TYPE; 056 import static org.jetbrains.kotlin.resolve.jvm.diagnostics.DiagnosticsPackage.OtherOrigin; 057 import static org.jetbrains.org.objectweb.asm.Opcodes.*; 058 059 public class PropertyCodegen { 060 private final GenerationState state; 061 private final ClassBuilder v; 062 private final FunctionCodegen functionCodegen; 063 private final JetTypeMapper typeMapper; 064 private final BindingContext bindingContext; 065 private final FieldOwnerContext context; 066 private final MemberCodegen<?> memberCodegen; 067 private final OwnerKind kind; 068 069 public PropertyCodegen( 070 @NotNull FieldOwnerContext context, 071 @NotNull ClassBuilder v, 072 @NotNull FunctionCodegen functionCodegen, 073 @NotNull MemberCodegen<?> memberCodegen 074 ) { 075 this.state = functionCodegen.state; 076 this.v = v; 077 this.functionCodegen = functionCodegen; 078 this.typeMapper = state.getTypeMapper(); 079 this.bindingContext = state.getBindingContext(); 080 this.context = context; 081 this.memberCodegen = memberCodegen; 082 this.kind = context.getContextKind(); 083 } 084 085 public void gen(@NotNull JetProperty property) { 086 VariableDescriptor variableDescriptor = bindingContext.get(BindingContext.VARIABLE, property); 087 assert variableDescriptor instanceof PropertyDescriptor : "Property " + property.getText() + " should have a property descriptor: " + variableDescriptor; 088 089 PropertyDescriptor propertyDescriptor = (PropertyDescriptor) variableDescriptor; 090 gen(property, propertyDescriptor, property.getGetter(), property.getSetter()); 091 } 092 093 public void generateInPackageFacade(@NotNull DeserializedPropertyDescriptor deserializedProperty) { 094 assert context instanceof PackageFacadeContext : "should be called only for generating package facade: " + context; 095 gen(null, deserializedProperty, null, null); 096 } 097 098 private void gen( 099 @Nullable JetProperty declaration, 100 @NotNull PropertyDescriptor descriptor, 101 @Nullable JetPropertyAccessor getter, 102 @Nullable JetPropertyAccessor setter 103 ) { 104 assert kind == OwnerKind.PACKAGE || kind == OwnerKind.IMPLEMENTATION || kind == OwnerKind.TRAIT_IMPL 105 : "Generating property with a wrong kind (" + kind + "): " + descriptor; 106 107 if (context instanceof PackageFacadeContext) { 108 Type ownerType = ((PackageFacadeContext) context).getDelegateToClassType(); 109 v.getSerializationBindings().put(IMPL_CLASS_NAME_FOR_CALLABLE, descriptor, shortNameByAsmType(ownerType)); 110 } 111 else { 112 assert declaration != null : "Declaration is null for different context: " + context; 113 if (!generateBackingField(declaration, descriptor)) { 114 generateSyntheticMethodIfNeeded(descriptor); 115 } 116 } 117 118 if (isAccessorNeeded(declaration, descriptor, getter)) { 119 generateGetter(declaration, descriptor, getter); 120 } 121 if (isAccessorNeeded(declaration, descriptor, setter)) { 122 generateSetter(declaration, descriptor, setter); 123 } 124 125 context.recordSyntheticAccessorIfNeeded(descriptor, bindingContext); 126 } 127 128 /** 129 * Determines if it's necessary to generate an accessor to the property, i.e. if this property can be referenced via getter/setter 130 * for any reason 131 * 132 * @see JvmCodegenUtil#couldUseDirectAccessToProperty 133 */ 134 private boolean isAccessorNeeded( 135 @Nullable JetProperty declaration, 136 @NotNull PropertyDescriptor descriptor, 137 @Nullable JetPropertyAccessor accessor 138 ) { 139 boolean isDefaultAccessor = accessor == null || !accessor.hasBody(); 140 141 // Don't generate accessors for trait properties with default accessors in TRAIT_IMPL 142 if (kind == OwnerKind.TRAIT_IMPL && isDefaultAccessor) return false; 143 144 if (declaration == null) return true; 145 146 // Delegated or extension properties can only be referenced via accessors 147 if (declaration.hasDelegate() || declaration.getReceiverTypeReference() != null) return true; 148 149 // Companion object properties always should have accessors, because their backing fields are moved/copied to the outer class 150 if (isCompanionObject(descriptor.getContainingDeclaration())) return true; 151 152 // Private class properties have accessors only in cases when those accessors are non-trivial 153 if (kind == OwnerKind.IMPLEMENTATION && Visibilities.isPrivate(descriptor.getVisibility())) { 154 return !isDefaultAccessor; 155 } 156 157 return true; 158 } 159 160 public void generatePrimaryConstructorProperty(JetParameter p, PropertyDescriptor descriptor) { 161 generateBackingField(p, descriptor); 162 if (!Visibilities.isPrivate(descriptor.getVisibility())) { 163 generateGetter(p, descriptor, null); 164 if (descriptor.isVar()) { 165 generateSetter(p, descriptor, null); 166 } 167 } 168 } 169 170 public void generateConstructorPropertyAsMethodForAnnotationClass(JetParameter p, PropertyDescriptor descriptor) { 171 JvmMethodSignature signature = typeMapper.mapAnnotationParameterSignature(descriptor); 172 String name = p.getName(); 173 if (name == null) return; 174 MethodVisitor mv = v.newMethod( 175 OtherOrigin(p, descriptor), ACC_PUBLIC | ACC_ABSTRACT, name, 176 signature.getAsmMethod().getDescriptor(), 177 signature.getGenericsSignature(), 178 null 179 ); 180 181 if (state.getClassBuilderMode() == ClassBuilderMode.FULL) { 182 JetExpression defaultValue = p.getDefaultValue(); 183 if (defaultValue != null) { 184 CompileTimeConstant<?> constant = ExpressionCodegen.getCompileTimeConstant(defaultValue, bindingContext); 185 assert constant != null : "Default value for annotation parameter should be compile time value: " + defaultValue.getText(); 186 AnnotationCodegen annotationCodegen = AnnotationCodegen.forAnnotationDefaultValue(mv, typeMapper); 187 annotationCodegen.generateAnnotationDefaultValue(constant, descriptor.getType()); 188 } 189 } 190 191 mv.visitEnd(); 192 } 193 194 private boolean generateBackingField(@NotNull JetNamedDeclaration p, @NotNull PropertyDescriptor descriptor) { 195 if (isInterface(descriptor.getContainingDeclaration()) || kind == OwnerKind.TRAIT_IMPL) { 196 return false; 197 } 198 199 if (p instanceof JetProperty && ((JetProperty) p).hasDelegate()) { 200 generatePropertyDelegateAccess((JetProperty) p, descriptor); 201 } 202 else if (Boolean.TRUE.equals(bindingContext.get(BindingContext.BACKING_FIELD_REQUIRED, descriptor))) { 203 generateBackingFieldAccess(p, descriptor); 204 } 205 else { 206 return false; 207 } 208 return true; 209 } 210 211 // Annotations on properties without backing fields are stored in bytecode on an empty synthetic method. This way they're still 212 // accessible via reflection, and 'deprecated' and 'private' flags prevent this method from being called accidentally 213 private void generateSyntheticMethodIfNeeded(@NotNull PropertyDescriptor descriptor) { 214 if (descriptor.getAnnotations().isEmpty()) return; 215 216 ReceiverParameterDescriptor receiver = descriptor.getExtensionReceiverParameter(); 217 String name = JvmAbi.getSyntheticMethodNameForAnnotatedProperty(descriptor.getName()); 218 String desc = receiver == null ? "()V" : "(" + typeMapper.mapType(receiver.getType()) + ")V"; 219 220 if (!isTrait(context.getContextDescriptor()) || kind == OwnerKind.TRAIT_IMPL) { 221 int flags = ACC_DEPRECATED | ACC_FINAL | ACC_PRIVATE | ACC_STATIC | ACC_SYNTHETIC; 222 MethodVisitor mv = v.newMethod(OtherOrigin(descriptor), flags, name, desc, null, null); 223 AnnotationCodegen.forMethod(mv, typeMapper).genAnnotations(descriptor, Type.VOID_TYPE); 224 mv.visitCode(); 225 mv.visitInsn(Opcodes.RETURN); 226 mv.visitEnd(); 227 } 228 else { 229 Type tImplType = typeMapper.mapTraitImpl((ClassDescriptor) context.getContextDescriptor()); 230 v.getSerializationBindings().put(IMPL_CLASS_NAME_FOR_CALLABLE, descriptor, shortNameByAsmType(tImplType)); 231 } 232 233 if (kind != OwnerKind.TRAIT_IMPL) { 234 v.getSerializationBindings().put(SYNTHETIC_METHOD_FOR_PROPERTY, descriptor, new Method(name, desc)); 235 } 236 } 237 238 private void generateBackingField(JetNamedDeclaration element, PropertyDescriptor propertyDescriptor, boolean isDelegate, JetType jetType, Object defaultValue) { 239 int modifiers = getDeprecatedAccessFlag(propertyDescriptor); 240 241 for (AnnotationCodegen.JvmFlagAnnotation flagAnnotation : AnnotationCodegen.FIELD_FLAGS) { 242 if (flagAnnotation.hasAnnotation(propertyDescriptor.getOriginal())) { 243 modifiers |= flagAnnotation.getJvmFlag(); 244 } 245 } 246 247 if (kind == OwnerKind.PACKAGE) { 248 modifiers |= ACC_STATIC; 249 } 250 251 if (!propertyDescriptor.isVar() || isDelegate) { 252 modifiers |= ACC_FINAL; 253 } 254 255 Type type = typeMapper.mapType(jetType); 256 257 ClassBuilder builder = v; 258 259 FieldOwnerContext backingFieldContext = context; 260 if (AsmUtil.isInstancePropertyWithStaticBackingField(propertyDescriptor) ) { 261 modifiers |= ACC_STATIC | getVisibilityForSpecialPropertyBackingField(propertyDescriptor, isDelegate); 262 if (AsmUtil.isPropertyWithBackingFieldInOuterClass(propertyDescriptor)) { 263 ImplementationBodyCodegen codegen = (ImplementationBodyCodegen) memberCodegen.getParentCodegen(); 264 builder = codegen.v; 265 backingFieldContext = codegen.context; 266 v.getSerializationBindings().put(STATIC_FIELD_IN_OUTER_CLASS, propertyDescriptor); 267 } 268 } 269 else if (kind != OwnerKind.PACKAGE || isDelegate) { 270 modifiers |= ACC_PRIVATE; 271 } 272 273 if (AsmUtil.isPropertyWithBackingFieldCopyInOuterClass(propertyDescriptor)) { 274 ImplementationBodyCodegen parentBodyCodegen = (ImplementationBodyCodegen) memberCodegen.getParentCodegen(); 275 parentBodyCodegen.addCompanionObjectPropertyToCopy(propertyDescriptor, defaultValue); 276 } 277 278 String name = backingFieldContext.getFieldName(propertyDescriptor, isDelegate); 279 280 v.getSerializationBindings().put(FIELD_FOR_PROPERTY, propertyDescriptor, Pair.create(type, name)); 281 282 FieldVisitor fv = builder.newField(OtherOrigin(element, propertyDescriptor), modifiers, name, type.getDescriptor(), 283 typeMapper.mapFieldSignature(jetType), defaultValue); 284 AnnotationCodegen.forField(fv, typeMapper).genAnnotations(propertyDescriptor, type); 285 } 286 287 private void generatePropertyDelegateAccess(JetProperty p, PropertyDescriptor propertyDescriptor) { 288 JetExpression delegateExpression = p.getDelegateExpression(); 289 JetType delegateType = delegateExpression != null ? bindingContext.getType(p.getDelegateExpression()) : null; 290 if (delegateType == null) { 291 // If delegate expression is unresolved reference 292 delegateType = ErrorUtils.createErrorType("Delegate type"); 293 } 294 295 generateBackingField(p, propertyDescriptor, true, delegateType, null); 296 } 297 298 private void generateBackingFieldAccess(JetNamedDeclaration p, PropertyDescriptor propertyDescriptor) { 299 Object value = null; 300 301 if (shouldWriteFieldInitializer(propertyDescriptor)) { 302 CompileTimeConstant<?> initializer = propertyDescriptor.getCompileTimeInitializer(); 303 if (initializer != null) { 304 value = initializer.getValue(); 305 } 306 } 307 308 generateBackingField(p, propertyDescriptor, false, propertyDescriptor.getType(), value); 309 } 310 311 private boolean shouldWriteFieldInitializer(@NotNull PropertyDescriptor descriptor) { 312 //final field of primitive or String type 313 if (!descriptor.isVar()) { 314 Type type = typeMapper.mapType(descriptor); 315 return AsmUtil.isPrimitive(type) || "java.lang.String".equals(type.getClassName()); 316 } 317 return false; 318 } 319 320 private void generateGetter(@Nullable JetNamedDeclaration p, @NotNull PropertyDescriptor descriptor, @Nullable JetPropertyAccessor getter) { 321 generateAccessor(p, getter, descriptor.getGetter() != null 322 ? descriptor.getGetter() 323 : DescriptorFactory.createDefaultGetter(descriptor)); 324 } 325 326 private void generateSetter(@Nullable JetNamedDeclaration p, @NotNull PropertyDescriptor descriptor, @Nullable JetPropertyAccessor setter) { 327 if (!descriptor.isVar()) return; 328 329 generateAccessor(p, setter, descriptor.getSetter() != null 330 ? descriptor.getSetter() 331 : DescriptorFactory.createDefaultSetter(descriptor)); 332 } 333 334 private void generateAccessor( 335 @Nullable JetNamedDeclaration p, 336 @Nullable JetPropertyAccessor accessor, 337 @NotNull PropertyAccessorDescriptor accessorDescriptor 338 ) { 339 FunctionGenerationStrategy strategy; 340 if (accessor == null || !accessor.hasBody()) { 341 if (p instanceof JetProperty && ((JetProperty) p).hasDelegate()) { 342 strategy = new DelegatedPropertyAccessorStrategy(state, accessorDescriptor, indexOfDelegatedProperty((JetProperty) p)); 343 } 344 else { 345 strategy = new DefaultPropertyAccessorStrategy(state, accessorDescriptor); 346 } 347 } 348 else { 349 strategy = new FunctionGenerationStrategy.FunctionDefault(state, accessorDescriptor, accessor); 350 } 351 352 functionCodegen.generateMethod(OtherOrigin(accessor != null ? accessor : p, accessorDescriptor), accessorDescriptor, strategy); 353 } 354 355 public static int indexOfDelegatedProperty(@NotNull JetProperty property) { 356 PsiElement parent = property.getParent(); 357 JetDeclarationContainer container; 358 if (parent instanceof JetClassBody) { 359 container = ((JetClassOrObject) parent.getParent()); 360 } 361 else if (parent instanceof JetFile) { 362 container = (JetFile) parent; 363 } 364 else { 365 throw new UnsupportedOperationException("Unknown delegated property container: " + parent); 366 } 367 368 int index = 0; 369 for (JetDeclaration declaration : container.getDeclarations()) { 370 if (declaration instanceof JetProperty && ((JetProperty) declaration).hasDelegate()) { 371 if (declaration == property) { 372 return index; 373 } 374 index++; 375 } 376 } 377 378 throw new IllegalStateException("Delegated property not found in its parent: " + PsiUtilPackage.getElementTextWithContext(property)); 379 } 380 381 382 private static class DefaultPropertyAccessorStrategy extends FunctionGenerationStrategy.CodegenBased<PropertyAccessorDescriptor> { 383 public DefaultPropertyAccessorStrategy(@NotNull GenerationState state, @NotNull PropertyAccessorDescriptor descriptor) { 384 super(state, descriptor); 385 } 386 387 @Override 388 public void doGenerateBody(@NotNull ExpressionCodegen codegen, @NotNull JvmMethodSignature signature) { 389 InstructionAdapter v = codegen.v; 390 PropertyDescriptor propertyDescriptor = callableDescriptor.getCorrespondingProperty(); 391 StackValue property = codegen.intermediateValueForProperty(propertyDescriptor, true, null, StackValue.LOCAL_0); 392 393 PsiElement jetProperty = DescriptorToSourceUtils.descriptorToDeclaration(propertyDescriptor); 394 if (jetProperty instanceof JetProperty || jetProperty instanceof JetParameter) { 395 codegen.markLineNumber((JetElement) jetProperty, false); 396 } 397 398 if (callableDescriptor instanceof PropertyGetterDescriptor) { 399 Type type = signature.getReturnType(); 400 property.put(type, v); 401 v.areturn(type); 402 } 403 else if (callableDescriptor instanceof PropertySetterDescriptor) { 404 List<ValueParameterDescriptor> valueParameters = callableDescriptor.getValueParameters(); 405 assert valueParameters.size() == 1 : "Property setter should have only one value parameter but has " + callableDescriptor; 406 int parameterIndex = codegen.lookupLocalIndex(valueParameters.get(0)); 407 assert parameterIndex >= 0 : "Local index for setter parameter should be positive or zero: " + callableDescriptor; 408 Type type = codegen.typeMapper.mapType(propertyDescriptor); 409 property.store(StackValue.local(parameterIndex, type), codegen.v); 410 v.visitInsn(RETURN); 411 } 412 else { 413 throw new IllegalStateException("Unknown property accessor: " + callableDescriptor); 414 } 415 } 416 } 417 418 public static StackValue invokeDelegatedPropertyConventionMethod( 419 @NotNull PropertyDescriptor propertyDescriptor, 420 @NotNull ExpressionCodegen codegen, 421 @NotNull JetTypeMapper typeMapper, 422 @NotNull ResolvedCall<FunctionDescriptor> resolvedCall, 423 final int indexInPropertyMetadataArray, 424 int propertyMetadataArgumentIndex 425 ) { 426 CodegenContext<? extends ClassOrPackageFragmentDescriptor> ownerContext = codegen.getContext().getClassOrPackageParentContext(); 427 final Type owner; 428 if (ownerContext instanceof ClassContext) { 429 owner = typeMapper.mapClass(((ClassContext) ownerContext).getContextDescriptor()); 430 } 431 else if (ownerContext instanceof PackageContext) { 432 owner = ((PackageContext) ownerContext).getPackagePartType(); 433 } 434 else { 435 throw new UnsupportedOperationException("Unknown context: " + ownerContext); 436 } 437 438 codegen.tempVariables.put( 439 resolvedCall.getCall().getValueArguments().get(propertyMetadataArgumentIndex).asElement(), 440 new StackValue(PROPERTY_METADATA_TYPE) { 441 @Override 442 public void putSelector(@NotNull Type type, @NotNull InstructionAdapter v) { 443 Field array = StackValue 444 .field(Type.getType("[" + PROPERTY_METADATA_TYPE), owner, JvmAbi.PROPERTY_METADATA_ARRAY_NAME, true, 445 StackValue.none()); 446 StackValue.arrayElement(PROPERTY_METADATA_TYPE, array, StackValue.constant(indexInPropertyMetadataArray, Type.INT_TYPE)).put(type, v); 447 } 448 } 449 ); 450 451 StackValue delegatedProperty = codegen.intermediateValueForProperty(propertyDescriptor, true, null, StackValue.LOCAL_0); 452 return codegen.invokeFunction(resolvedCall, delegatedProperty); 453 } 454 455 private static class DelegatedPropertyAccessorStrategy extends FunctionGenerationStrategy.CodegenBased<PropertyAccessorDescriptor> { 456 private final int index; 457 458 public DelegatedPropertyAccessorStrategy(@NotNull GenerationState state, @NotNull PropertyAccessorDescriptor descriptor, int index) { 459 super(state, descriptor); 460 this.index = index; 461 } 462 463 @Override 464 public void doGenerateBody(@NotNull ExpressionCodegen codegen, @NotNull JvmMethodSignature signature) { 465 InstructionAdapter v = codegen.v; 466 467 BindingContext bindingContext = state.getBindingContext(); 468 ResolvedCall<FunctionDescriptor> resolvedCall = 469 bindingContext.get(BindingContext.DELEGATED_PROPERTY_RESOLVED_CALL, callableDescriptor); 470 assert resolvedCall != null : "Resolve call should be recorded for delegate call " + signature.toString(); 471 472 StackValue lastValue = invokeDelegatedPropertyConventionMethod(callableDescriptor.getCorrespondingProperty(), 473 codegen, state.getTypeMapper(), resolvedCall, index, 1); 474 Type asmType = signature.getReturnType(); 475 lastValue.put(asmType, v); 476 v.areturn(asmType); 477 } 478 } 479 480 @NotNull 481 public static String getterName(Name propertyName) { 482 return JvmAbi.GETTER_PREFIX + StringUtil.capitalizeWithJavaBeanConvention(propertyName.asString()); 483 } 484 485 @NotNull 486 public static String setterName(Name propertyName) { 487 return JvmAbi.SETTER_PREFIX + StringUtil.capitalizeWithJavaBeanConvention(propertyName.asString()); 488 } 489 490 public void genDelegate(@NotNull PropertyDescriptor delegate, @NotNull PropertyDescriptor delegateTo, @NotNull StackValue field) { 491 ClassDescriptor toClass = (ClassDescriptor) delegateTo.getContainingDeclaration(); 492 493 PropertyGetterDescriptor getter = delegate.getGetter(); 494 if (getter != null) { 495 //noinspection ConstantConditions 496 functionCodegen.genDelegate(getter, delegateTo.getGetter().getOriginal(), toClass, field); 497 } 498 499 PropertySetterDescriptor setter = delegate.getSetter(); 500 if (setter != null) { 501 //noinspection ConstantConditions 502 functionCodegen.genDelegate(setter, delegateTo.getSetter().getOriginal(), toClass, field); 503 } 504 } 505 }