1 /*
   2  * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package java.lang.invoke;
  27 
  28 import jdk.internal.org.objectweb.asm.*;
  29 import sun.misc.Unsafe;
  30 import sun.util.logging.PlatformLogger;
  31 
  32 import java.io.File;
  33 import java.io.FileOutputStream;
  34 import java.io.IOException;
  35 import java.lang.reflect.Constructor;
  36 import java.security.AccessController;
  37 import java.security.PrivilegedAction;
  38 import java.security.ProtectionDomain;
  39 import java.util.concurrent.atomic.AtomicBoolean;
  40 import java.util.concurrent.atomic.AtomicInteger;
  41 
  42 import static jdk.internal.org.objectweb.asm.Opcodes.*;
  43 
  44 /**
  45  * Lambda metafactory implementation which dynamically creates an
  46  * inner-class-like class per lambda callsite.
  47  *
  48  * @see LambdaMetafactory
  49  */
  50 /* package */ final class InnerClassLambdaMetafactory extends AbstractValidatingLambdaMetafactory {
  51     private static final Unsafe UNSAFE = Unsafe.getUnsafe();
  52 
  53     private static final int CLASSFILE_VERSION = 51;
  54     private static final String METHOD_DESCRIPTOR_VOID = Type.getMethodDescriptor(Type.VOID_TYPE);
  55     private static final String NAME_MAGIC_ACCESSOR_IMPL = "java/lang/invoke/MagicLambdaImpl";
  56     private static final String NAME_CTOR = "<init>";
  57 
  58     //Serialization support
  59     private static final char[] HEX = {
  60         '0', '1', '2', '3', '4', '5', '6', '7',
  61         '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
  62     };
  63     private static final char[] BAD_CHARS = {
  64         '\\', '/', ':', '*', '?', '"', '<', '>', '|'
  65     };
  66     private static final String[] REPLACEMENT = {
  67         "%5C", ".", "%3A", "%2A", "%3F", "%22", "%3C", "%3E", "%7C"
  68     };
  69 
  70     private static final String NAME_SERIALIZED_LAMBDA = "java/lang/invoke/SerializedLambda";
  71     private static final String DESCR_METHOD_WRITE_REPLACE = "()Ljava/lang/Object;";
  72     private static final String NAME_METHOD_WRITE_REPLACE = "writeReplace";
  73     private static final String NAME_OBJECT = "java/lang/Object";
  74     private static final String DESCR_CTOR_SERIALIZED_LAMBDA
  75             = MethodType.methodType(void.class,
  76                                     Class.class,
  77                                     String.class, String.class, String.class,
  78                                     int.class, String.class, String.class, String.class,
  79                                     String.class,
  80                                     Object[].class).toMethodDescriptorString();
  81 
  82     // Used to ensure that each spun class name is unique
  83     private static final AtomicInteger counter = new AtomicInteger(0);
  84 
  85     // For dumping generated classes to disk, for debugging purposes
  86     private static final String DUMP_PATH_PROPERTY = "jdk.internal.lambda.dumpProxyClasses";
  87     private static final String dumpDir;
  88     private static AtomicBoolean invalidDir = new AtomicBoolean(false);
  89     static {
  90         dumpDir = AccessController.doPrivileged(new PrivilegedAction<String>() {
  91             @Override
  92             public String run() {
  93                 String path = System.getProperty(DUMP_PATH_PROPERTY);
  94                 if (path != null) {
  95                     validateDumpDir(path);
  96                 }
  97                 return path;
  98             }
  99         });
 100     }
 101 
 102     // See context values in AbstractValidatingLambdaMetafactory
 103     private final String implMethodClassName;        // Name of type containing implementation "CC"
 104     private final String implMethodName;             // Name of implementation method "impl"
 105     private final String implMethodDesc;             // Type descriptor for implementation methods "(I)Ljava/lang/String;"
 106     private final Type[] implMethodArgumentTypes;    // ASM types for implementaion method parameters
 107     private final Type implMethodReturnType;         // ASM type for implementaion method return type "Ljava/lang/String;"
 108     private final MethodType constructorType;        // Generated class constructor type "(CC)void"
 109     private final String constructorDesc;            // Type descriptor for constructor "(LCC;)V"
 110     private final ClassWriter cw;                    // ASM class writer
 111     private final Type[] argTypes;                   // ASM types for the constructor arguments
 112     private final String[] argNames;                 // Generated names for the constructor arguments
 113     private final String lambdaClassName;            // Generated name for the generated class "X$$Lambda$1"
 114     private final Type[] instantiatedArgumentTypes;  // ASM types for the functional interface arguments
 115 
 116     /**
 117      * General meta-factory constructor, supporting both standard cases and
 118      * allowing for uncommon options such as serialization or bridging.
 119      *
 120      * @param caller Stacked automatically by VM; represents a lookup context
 121      *               with the accessibility privileges of the caller.
 122      * @param invokedType Stacked automatically by VM; the signature of the
 123      *                    invoked method, which includes the expected static
 124      *                    type of the returned lambda object, and the static
 125      *                    types of the captured arguments for the lambda.  In
 126      *                    the event that the implementation method is an
 127      *                    instance method, the first argument in the invocation
 128      *                    signature will correspond to the receiver.
 129      * @param samMethodName Name of the method in the functional interface to
 130      *                      which the lambda or method reference is being
 131      *                      converted, represented as a String.
 132      * @param samMethodType Type of the method in the functional interface to
 133      *                      which the lambda or method reference is being
 134      *                      converted, represented as a MethodType.
 135      * @param implMethod The implementation method which should be called (with
 136      *                   suitable adaptation of argument types, return types,
 137      *                   and adjustment for captured arguments) when methods of
 138      *                   the resulting functional interface instance are invoked.
 139      * @param instantiatedMethodType The signature of the primary functional
 140      *                               interface method after type variables are
 141      *                               substituted with their instantiation from
 142      *                               the capture site
 143      * @param isSerializable Should the lambda be made serializable?  If set,
 144      *                       either the target type or one of the additional SAM
 145      *                       types must extend {@code Serializable}.
 146      * @param markerInterfaces Additional interfaces which the lambda object
 147      *                       should implement.
 148      * @param additionalBridges Method types for additional signatures to be
 149      *                          bridged to the implementation method
 150      * @throws ReflectiveOperationException
 151      * @throws LambdaConversionException If any of the meta-factory protocol
 152      * invariants are violated
 153      */
 154     public InnerClassLambdaMetafactory(MethodHandles.Lookup caller,
 155                                        MethodType invokedType,
 156                                        String samMethodName,
 157                                        MethodType samMethodType,
 158                                        MethodHandle implMethod,
 159                                        MethodType instantiatedMethodType,
 160                                        boolean isSerializable,
 161                                        Class<?>[] markerInterfaces,
 162                                        MethodType[] additionalBridges)
 163             throws ReflectiveOperationException, LambdaConversionException {
 164         super(caller, invokedType, samMethodName, samMethodType,
 165               implMethod, instantiatedMethodType,
 166               isSerializable, markerInterfaces, additionalBridges);
 167         implMethodClassName = implDefiningClass.getName().replace('.', '/');
 168         implMethodName = implInfo.getName();
 169         implMethodDesc = implMethodType.toMethodDescriptorString();
 170         Type implMethodAsmType = Type.getMethodType(implMethodDesc);
 171         implMethodArgumentTypes = implMethodAsmType.getArgumentTypes();
 172         implMethodReturnType = (implKind == MethodHandleInfo.REF_newInvokeSpecial)
 173                 ? Type.getObjectType(implMethodClassName)
 174                 : implMethodAsmType.getReturnType();
 175         constructorType = invokedType.changeReturnType(Void.TYPE);
 176         constructorDesc = constructorType.toMethodDescriptorString();
 177         lambdaClassName = targetClass.getName().replace('.', '/') + "$$Lambda$" + counter.incrementAndGet();
 178         cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
 179         argTypes = Type.getArgumentTypes(constructorDesc);
 180         argNames = new String[argTypes.length];
 181         for (int i = 0; i < argTypes.length; i++) {
 182             argNames[i] = "arg$" + (i + 1);
 183         }
 184         instantiatedArgumentTypes = Type.getArgumentTypes(
 185                 instantiatedMethodType.toMethodDescriptorString());
 186     }
 187 
 188     /**
 189      * Build the CallSite. Generate a class file which implements the functional
 190      * interface, define the class, if there are no parameters create an instance
 191      * of the class which the CallSite will return, otherwise, generate handles
 192      * which will call the class' constructor.
 193      *
 194      * @return a CallSite, which, when invoked, will return an instance of the
 195      * functional interface
 196      * @throws ReflectiveOperationException
 197      * @throws LambdaConversionException If properly formed functional interface
 198      * is not found
 199      */
 200     @Override
 201     CallSite buildCallSite() throws ReflectiveOperationException, LambdaConversionException {
 202         final Class<?> innerClass = spinInnerClass();
 203         if (invokedType.parameterCount() == 0) {
 204             final Constructor[] ctrs = AccessController.doPrivileged(
 205                     new PrivilegedAction<Constructor[]>() {
 206                 @Override
 207                 public Constructor[] run() {
 208                     return innerClass.getDeclaredConstructors();
 209                 }
 210             });
 211             if (ctrs.length != 1) {
 212                 throw new ReflectiveOperationException("Expected one lambda constructor for "
 213                         + innerClass.getCanonicalName() + ", got " + ctrs.length);
 214             }
 215             // The lambda implementing inner class constructor is private, set
 216             // it accessible (by us) before creating the constant sole instance
 217             AccessController.doPrivileged(new PrivilegedAction<Void>() {
 218                 @Override
 219                 public Void run() {
 220                     ctrs[0].setAccessible(true);
 221                     return null;
 222                 }
 223             });
 224             Object inst = ctrs[0].newInstance();
 225             return new ConstantCallSite(MethodHandles.constant(samBase, inst));
 226         } else {
 227             return new ConstantCallSite(
 228                     MethodHandles.Lookup.IMPL_LOOKUP
 229                          .findConstructor(innerClass, constructorType)
 230                          .asType(constructorType.changeReturnType(samBase)));
 231         }
 232     }
 233 
 234     private static File validateDumpDir(String path) {
 235         File dirPath = new File(path);
 236         String errMsg = null;
 237         if (!dirPath.exists()) {
 238             errMsg = "Directory at jdk.internal.lambda.dumpProxyClasses does not exist";
 239         } else if (!dirPath.isDirectory()) {
 240             errMsg = "Path at jdk.internal.lambda.dumpProxyClasses is not a directory";
 241         } else if (!dirPath.canWrite()) {
 242             errMsg = "Directory at jdk.internal.lambda.dumpProxyClasses is not writable";
 243         } else {
 244             // validate dump directory, ready to go
 245             return dirPath;
 246         }
 247 
 248         // show error message about invalid directory once
 249         if (! invalidDir.getAndSet(true)) {
 250             PlatformLogger.getLogger(InnerClassLambdaMetafactory.class.getName())
 251                           .warning(errMsg);
 252         }
 253         return null;
 254     }
 255 
 256     private static String encodeForFilename(String className) {
 257         final int len = className.length();
 258         StringBuilder sb = new StringBuilder(len);
 259 
 260         for (int i = 0; i < len; i++) {
 261             char c = className.charAt(i);
 262             // control characters
 263             if (c <= 31) {
 264                 sb.append('%');
 265                 sb.append(HEX[c >> 4 & 0x0F]);
 266                 sb.append(HEX[c & 0x0F]);
 267             } else {
 268                 int j = 0;
 269                 for (; j < BAD_CHARS.length; j++) {
 270                     if (c == BAD_CHARS[j]) {
 271                         sb.append(REPLACEMENT[j]);
 272                         break;
 273                     }
 274                 }
 275                 if (j >= BAD_CHARS.length) {
 276                     sb.append(c);
 277                 }
 278             }
 279         }
 280 
 281         return sb.toString();
 282     }
 283 
 284     /**
 285      * Generate a class file which implements the functional
 286      * interface, define and return the class.
 287      *
 288      * @implNote The class that is generated does not include signature
 289      * information for exceptions that may be present on the SAM method.
 290      * This is to reduce classfile size, and is harmless as checked exceptions
 291      * are erased anyway, no one will ever compile against this classfile,
 292      * and we make no guarantees about the reflective properties of lambda
 293      * objects.
 294      *
 295      * @return a Class which implements the functional interface
 296      * @throws LambdaConversionException If properly formed functional interface
 297      * is not found
 298      */
 299     private Class<?> spinInnerClass() throws LambdaConversionException {
 300         String[] interfaces = new String[markerInterfaces.length + 1];
 301         interfaces[0] = samBase.getName().replace('.', '/');
 302         for (int i=0; i<markerInterfaces.length; i++) {
 303             interfaces[i+1] = markerInterfaces[i].getName().replace('.', '/');
 304         }
 305         cw.visit(CLASSFILE_VERSION, ACC_SUPER + ACC_FINAL + ACC_SYNTHETIC,
 306                  lambdaClassName, null,
 307                  NAME_MAGIC_ACCESSOR_IMPL, interfaces);
 308 
 309         // Generate final fields to be filled in by constructor
 310         for (int i = 0; i < argTypes.length; i++) {
 311             FieldVisitor fv = cw.visitField(ACC_PRIVATE + ACC_FINAL,
 312                                             argNames[i],
 313                                             argTypes[i].getDescriptor(),
 314                                             null, null);
 315             fv.visitEnd();
 316         }
 317 
 318         generateConstructor();
 319 
 320         // Forward the SAM method
 321         String methodDescriptor = samMethodType.toMethodDescriptorString();
 322         MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, samMethodName,
 323                                           methodDescriptor, null, null);
 324         new ForwardingMethodGenerator(mv).generate(methodDescriptor);
 325 
 326         // Forward the bridges
 327         if (additionalBridges != null) {
 328             for (MethodType mt : additionalBridges) {
 329                 methodDescriptor = mt.toMethodDescriptorString();
 330                 mv = cw.visitMethod(ACC_PUBLIC|ACC_BRIDGE, samMethodName,
 331                                     methodDescriptor, null, null);
 332                 new ForwardingMethodGenerator(mv).generate(methodDescriptor);
 333             }
 334         }
 335 
 336         if (isSerializable)
 337             generateWriteReplace();
 338 
 339         cw.visitEnd();
 340 
 341         // Define the generated class in this VM.
 342 
 343         final byte[] classBytes = cw.toByteArray();
 344 
 345         // If requested, dump out to a file for debugging purposes
 346         if (dumpDir != null) {
 347             AccessController.doPrivileged(new PrivilegedAction<Void>() {
 348                 @Override
 349                 public Void run() {
 350                     File dirPath = validateDumpDir(dumpDir);
 351                     if (dirPath != null) {
 352                         File out = new File(dirPath, encodeForFilename(lambdaClassName) + ".class");
 353                         try (FileOutputStream fos = new FileOutputStream(out)) {
 354                             fos.write(classBytes);
 355                         } catch (IOException ex) {
 356                             PlatformLogger.getLogger(InnerClassLambdaMetafactory.class.getName())
 357                                           .warning("Exception writing to path at jdk.internal.lambda.dumpProxyClasses");
 358                         }
 359                     }
 360                     return null;
 361                 }
 362             });
 363         }
 364 
 365         ClassLoader loader = targetClass.getClassLoader();
 366         ProtectionDomain pd = (loader == null)
 367                               ? null
 368                               : AccessController.doPrivileged(
 369                                       new PrivilegedAction<ProtectionDomain>() {
 370                                           @Override
 371                                           public ProtectionDomain run() {
 372                                               return targetClass.getProtectionDomain();
 373                                           }
 374                                       }
 375                               );
 376 
 377         return UNSAFE.defineClass(lambdaClassName,
 378                                   classBytes, 0, classBytes.length,
 379                                   loader, pd);
 380     }
 381 
 382     /**
 383      * Generate the constructor for the class
 384      */
 385     private void generateConstructor() {
 386         // Generate constructor
 387         MethodVisitor ctor = cw.visitMethod(ACC_PRIVATE, NAME_CTOR,
 388                                             constructorDesc, null, null);
 389         ctor.visitCode();
 390         ctor.visitVarInsn(ALOAD, 0);
 391         ctor.visitMethodInsn(INVOKESPECIAL, NAME_MAGIC_ACCESSOR_IMPL, NAME_CTOR,
 392                              METHOD_DESCRIPTOR_VOID);
 393         int lvIndex = 0;
 394         for (int i = 0; i < argTypes.length; i++) {
 395             ctor.visitVarInsn(ALOAD, 0);
 396             ctor.visitVarInsn(argTypes[i].getOpcode(ILOAD), lvIndex + 1);
 397             lvIndex += argTypes[i].getSize();
 398             ctor.visitFieldInsn(PUTFIELD, lambdaClassName, argNames[i],
 399                                 argTypes[i].getDescriptor());
 400         }
 401         ctor.visitInsn(RETURN);
 402         // Maxs computed by ClassWriter.COMPUTE_MAXS, these arguments ignored
 403         ctor.visitMaxs(-1, -1);
 404         ctor.visitEnd();
 405     }
 406 
 407     /**
 408      * Generate the writeReplace method (if needed for serialization)
 409      */
 410     private void generateWriteReplace() {
 411         TypeConvertingMethodAdapter mv
 412                 = new TypeConvertingMethodAdapter(
 413                     cw.visitMethod(ACC_PRIVATE + ACC_FINAL,
 414                     NAME_METHOD_WRITE_REPLACE, DESCR_METHOD_WRITE_REPLACE,
 415                     null, null));
 416 
 417         mv.visitCode();
 418         mv.visitTypeInsn(NEW, NAME_SERIALIZED_LAMBDA);
 419         mv.visitInsn(DUP);
 420         mv.visitLdcInsn(Type.getType(targetClass));
 421         mv.visitLdcInsn(invokedType.returnType().getName().replace('.', '/'));
 422         mv.visitLdcInsn(samMethodName);
 423         mv.visitLdcInsn(samMethodType.toMethodDescriptorString());
 424         mv.visitLdcInsn(implInfo.getReferenceKind());
 425         mv.visitLdcInsn(implInfo.getDeclaringClass().getName().replace('.', '/'));
 426         mv.visitLdcInsn(implInfo.getName());
 427         mv.visitLdcInsn(implInfo.getMethodType().toMethodDescriptorString());
 428         mv.visitLdcInsn(instantiatedMethodType.toMethodDescriptorString());
 429 
 430         mv.iconst(argTypes.length);
 431         mv.visitTypeInsn(ANEWARRAY, NAME_OBJECT);
 432         for (int i = 0; i < argTypes.length; i++) {
 433             mv.visitInsn(DUP);
 434             mv.iconst(i);
 435             mv.visitVarInsn(ALOAD, 0);
 436             mv.visitFieldInsn(GETFIELD, lambdaClassName, argNames[i],
 437                               argTypes[i].getDescriptor());
 438             mv.boxIfTypePrimitive(argTypes[i]);
 439             mv.visitInsn(AASTORE);
 440         }
 441         mv.visitMethodInsn(INVOKESPECIAL, NAME_SERIALIZED_LAMBDA, NAME_CTOR,
 442                 DESCR_CTOR_SERIALIZED_LAMBDA);
 443         mv.visitInsn(ARETURN);
 444         // Maxs computed by ClassWriter.COMPUTE_MAXS, these arguments ignored
 445         mv.visitMaxs(-1, -1);
 446         mv.visitEnd();
 447     }
 448 
 449     /**
 450      * This class generates a method body which calls the lambda implementation
 451      * method, converting arguments, as needed.
 452      */
 453     private class ForwardingMethodGenerator extends TypeConvertingMethodAdapter {
 454 
 455         ForwardingMethodGenerator(MethodVisitor mv) {
 456             super(mv);
 457         }
 458 
 459         void generate(String methodDescriptor) {
 460             visitCode();
 461 
 462             if (implKind == MethodHandleInfo.REF_newInvokeSpecial) {
 463                 visitTypeInsn(NEW, implMethodClassName);
 464                 visitInsn(DUP);
 465             }
 466             for (int i = 0; i < argTypes.length; i++) {
 467                 visitVarInsn(ALOAD, 0);
 468                 visitFieldInsn(GETFIELD, lambdaClassName, argNames[i],
 469                                argTypes[i].getDescriptor());
 470             }
 471 
 472             convertArgumentTypes(Type.getArgumentTypes(methodDescriptor));
 473 
 474             // Invoke the method we want to forward to
 475             visitMethodInsn(invocationOpcode(), implMethodClassName, implMethodName, implMethodDesc);
 476 
 477             // Convert the return value (if any) and return it
 478             // Note: if adapting from non-void to void, the 'return'
 479             // instruction will pop the unneeded result
 480             Type samReturnType = Type.getReturnType(methodDescriptor);
 481             convertType(implMethodReturnType, samReturnType, samReturnType);
 482             visitInsn(samReturnType.getOpcode(Opcodes.IRETURN));
 483             // Maxs computed by ClassWriter.COMPUTE_MAXS,these arguments ignored
 484             visitMaxs(-1, -1);
 485             visitEnd();
 486         }
 487 
 488         private void convertArgumentTypes(Type[] samArgumentTypes) {
 489             int lvIndex = 0;
 490             boolean samIncludesReceiver = implIsInstanceMethod &&
 491                                                    argTypes.length == 0;
 492             int samReceiverLength = samIncludesReceiver ? 1 : 0;
 493             if (samIncludesReceiver) {
 494                 // push receiver
 495                 Type rcvrType = samArgumentTypes[0];
 496                 Type instantiatedRcvrType = instantiatedArgumentTypes[0];
 497 
 498                 visitVarInsn(rcvrType.getOpcode(ILOAD), lvIndex + 1);
 499                 lvIndex += rcvrType.getSize();
 500                 convertType(rcvrType, Type.getType(implDefiningClass), instantiatedRcvrType);
 501             }
 502             int argOffset = implMethodArgumentTypes.length - samArgumentTypes.length;
 503             for (int i = samReceiverLength; i < samArgumentTypes.length; i++) {
 504                 Type argType = samArgumentTypes[i];
 505                 Type targetType = implMethodArgumentTypes[argOffset + i];
 506                 Type instantiatedArgType = instantiatedArgumentTypes[i];
 507 
 508                 visitVarInsn(argType.getOpcode(ILOAD), lvIndex + 1);
 509                 lvIndex += argType.getSize();
 510                 convertType(argType, targetType, instantiatedArgType);
 511             }
 512         }
 513 
 514         private void convertType(Type argType, Type targetType, Type functionalType) {
 515             convertType(argType.getDescriptor(),
 516                         targetType.getDescriptor(),
 517                         functionalType.getDescriptor());
 518         }
 519 
 520         private int invocationOpcode() throws InternalError {
 521             switch (implKind) {
 522                 case MethodHandleInfo.REF_invokeStatic:
 523                     return INVOKESTATIC;
 524                 case MethodHandleInfo.REF_newInvokeSpecial:
 525                     return INVOKESPECIAL;
 526                  case MethodHandleInfo.REF_invokeVirtual:
 527                     return INVOKEVIRTUAL;
 528                 case MethodHandleInfo.REF_invokeInterface:
 529                     return INVOKEINTERFACE;
 530                 case MethodHandleInfo.REF_invokeSpecial:
 531                     return INVOKESPECIAL;
 532                 default:
 533                     throw new InternalError("Unexpected invocation kind: " + implKind);
 534             }
 535         }
 536     }
 537 }