or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

context-management.mdindex.mdio-filesystem.mdlanguage-execution.mdmonitoring-management.mdproxy-system.mdsecurity-access.mdvalue-interop.md

proxy-system.mddocs/

0

# Proxy System for Custom Objects

1

2

The proxy system enables host (Java) objects to mimic guest language objects with custom behavior. Proxy interfaces allow you to create objects that seamlessly integrate with guest languages by implementing specific behavioral contracts.

3

4

## Base Proxy Interface

5

6

All proxy implementations must extend the base Proxy interface.

7

8

```java { .api }

9

package org.graalvm.polyglot.proxy;

10

11

public interface Proxy {

12

// Marker interface - no methods

13

}

14

```

15

16

The Proxy interface serves as a marker interface that identifies objects as polyglot proxies. Host objects implementing Proxy interfaces can be exposed to guest languages and will behave according to the implemented proxy contracts.

17

18

## Object-Like Behavior

19

20

### ProxyObject Interface

21

22

ProxyObject enables host objects to behave like objects with named members (properties/fields).

23

24

```java { .api }

25

public interface ProxyObject extends Proxy {

26

/**

27

* Returns the value of the member.

28

* @param key the member identifier

29

* @return the member value, or null if the member does not exist

30

*/

31

Object getMember(String key);

32

33

/**

34

* Returns an array-like object containing all member keys.

35

* @return array-like object with member names, or null if not supported

36

*/

37

Object getMemberKeys();

38

39

/**

40

* Checks if a member exists.

41

* @param key the member identifier

42

* @return true if the member exists

43

*/

44

boolean hasMember(String key);

45

46

/**

47

* Sets the value of a member.

48

* @param key the member identifier

49

* @param value the new member value

50

*/

51

void putMember(String key, Value value);

52

53

/**

54

* Removes a member.

55

* @param key the member identifier

56

* @return true if the member was removed, false if it didn't exist

57

*/

58

boolean removeMember(String key);

59

}

60

```

61

62

**ProxyObject Implementation Example:**

63

64

```java

65

import org.graalvm.polyglot.proxy.ProxyObject;

66

import java.util.Map;

67

import java.util.HashMap;

68

import java.util.Set;

69

70

public class CustomObject implements ProxyObject {

71

private final Map<String, Object> properties = new HashMap<>();

72

73

public CustomObject() {

74

properties.put("name", "Custom Object");

75

properties.put("version", "1.0");

76

}

77

78

@Override

79

public Object getMember(String key) {

80

return properties.get(key);

81

}

82

83

@Override

84

public Object getMemberKeys() {

85

return properties.keySet().toArray(new String[0]);

86

}

87

88

@Override

89

public boolean hasMember(String key) {

90

return properties.containsKey(key);

91

}

92

93

@Override

94

public void putMember(String key, Value value) {

95

properties.put(key, value.as(Object.class));

96

}

97

98

@Override

99

public boolean removeMember(String key) {

100

return properties.remove(key) != null;

101

}

102

}

103

104

// Usage

105

Context context = Context.create("js");

106

CustomObject obj = new CustomObject();

107

context.getBindings("js").putMember("customObj", obj);

108

109

context.eval("js", """

110

console.log(customObj.name); // "Custom Object"

111

customObj.description = "A proxy object";

112

console.log(customObj.description); // "A proxy object"

113

delete customObj.version;

114

console.log(customObj.version); // undefined

115

""");

116

```

117

118

## Array-Like Behavior

119

120

### ProxyArray Interface

121

122

ProxyArray enables host objects to behave like arrays with indexed access.

123

124

```java { .api }

125

public interface ProxyArray extends ProxyIterable {

126

/**

127

* Returns the element at the given index.

128

* @param index the element index

129

* @return the element at the index

130

*/

131

Object get(long index);

132

133

/**

134

* Sets the element at the given index.

135

* @param index the element index

136

* @param value the new element value

137

*/

138

void set(long index, Value value);

139

140

/**

141

* Removes the element at the given index.

142

* @param index the element index

143

* @return true if the element was removed

144

*/

145

boolean remove(long index);

146

147

/**

148

* Returns the array size.

149

* @return the array size

150

*/

151

long getSize();

152

}

153

```

154

155

**ProxyArray Implementation Example:**

156

157

```java

158

import org.graalvm.polyglot.proxy.ProxyArray;

159

import java.util.List;

160

import java.util.ArrayList;

161

162

public class DynamicArray implements ProxyArray {

163

private final List<Object> elements = new ArrayList<>();

164

165

@Override

166

public Object get(long index) {

167

if (index < 0 || index >= elements.size()) {

168

return null;

169

}

170

return elements.get((int) index);

171

}

172

173

@Override

174

public void set(long index, Value value) {

175

int idx = (int) index;

176

// Extend list if necessary

177

while (elements.size() <= idx) {

178

elements.add(null);

179

}

180

elements.set(idx, value.as(Object.class));

181

}

182

183

@Override

184

public boolean remove(long index) {

185

if (index < 0 || index >= elements.size()) {

186

return false;

187

}

188

elements.remove((int) index);

189

return true;

190

}

191

192

@Override

193

public long getSize() {

194

return elements.size();

195

}

196

197

@Override

198

public Object getIterator() {

199

return new ArrayIterator(elements);

200

}

201

}

202

203

// Usage

204

Context context = Context.create("js");

205

DynamicArray arr = new DynamicArray();

206

context.getBindings("js").putMember("dynamicArray", arr);

207

208

context.eval("js", """

209

dynamicArray[0] = "first";

210

dynamicArray[1] = 42;

211

dynamicArray[2] = true;

212

213

console.log(dynamicArray.length); // 3

214

console.log(dynamicArray[1]); // 42

215

216

for (let item of dynamicArray) {

217

console.log(item); // "first", 42, true

218

}

219

""");

220

```

221

222

## Function-Like Behavior

223

224

### ProxyExecutable Interface

225

226

ProxyExecutable enables host objects to behave like functions that can be called.

227

228

```java { .api }

229

public interface ProxyExecutable extends Proxy {

230

/**

231

* Executes the proxy as a function.

232

* @param arguments the function arguments

233

* @return the execution result

234

*/

235

Object execute(Value... arguments);

236

}

237

```

238

239

**ProxyExecutable Implementation Example:**

240

241

```java

242

import org.graalvm.polyglot.proxy.ProxyExecutable;

243

244

public class MathFunction implements ProxyExecutable {

245

private final String operation;

246

247

public MathFunction(String operation) {

248

this.operation = operation;

249

}

250

251

@Override

252

public Object execute(Value... arguments) {

253

if (arguments.length != 2) {

254

throw new IllegalArgumentException("Expected exactly 2 arguments");

255

}

256

257

double a = arguments[0].asDouble();

258

double b = arguments[1].asDouble();

259

260

return switch (operation) {

261

case "add" -> a + b;

262

case "subtract" -> a - b;

263

case "multiply" -> a * b;

264

case "divide" -> b != 0 ? a / b : Double.NaN;

265

case "power" -> Math.pow(a, b);

266

default -> throw new IllegalArgumentException("Unknown operation: " + operation);

267

};

268

}

269

}

270

271

// Usage

272

Context context = Context.create("js");

273

context.getBindings("js").putMember("add", new MathFunction("add"));

274

context.getBindings("js").putMember("multiply", new MathFunction("multiply"));

275

276

Value result = context.eval("js", "add(multiply(3, 4), 5)"); // (3 * 4) + 5 = 17

277

System.out.println(result.asDouble()); // 17.0

278

```

279

280

### ProxyInstantiable Interface

281

282

ProxyInstantiable enables host objects to behave like constructors that can create new instances.

283

284

```java { .api }

285

public interface ProxyInstantiable extends Proxy {

286

/**

287

* Creates a new instance using this proxy as a constructor.

288

* @param arguments the constructor arguments

289

* @return the new instance

290

*/

291

Object newInstance(Value... arguments);

292

}

293

```

294

295

**ProxyInstantiable Implementation Example:**

296

297

```java

298

import org.graalvm.polyglot.proxy.ProxyInstantiable;

299

import org.graalvm.polyglot.proxy.ProxyObject;

300

301

public class PersonConstructor implements ProxyInstantiable {

302

303

@Override

304

public Object newInstance(Value... arguments) {

305

if (arguments.length < 2) {

306

throw new IllegalArgumentException("Person requires name and age");

307

}

308

309

String name = arguments[0].asString();

310

int age = arguments[1].asInt();

311

312

return new PersonInstance(name, age);

313

}

314

315

// Inner class representing a Person instance

316

public static class PersonInstance implements ProxyObject {

317

private final Map<String, Object> properties = new HashMap<>();

318

319

public PersonInstance(String name, int age) {

320

properties.put("name", name);

321

properties.put("age", age);

322

}

323

324

@Override

325

public Object getMember(String key) {

326

if ("greet".equals(key)) {

327

return new ProxyExecutable() {

328

@Override

329

public Object execute(Value... arguments) {

330

return "Hello, I'm " + properties.get("name") +

331

" and I'm " + properties.get("age") + " years old.";

332

}

333

};

334

}

335

return properties.get(key);

336

}

337

338

@Override

339

public Object getMemberKeys() {

340

Set<String> keys = new HashSet<>(properties.keySet());

341

keys.add("greet");

342

return keys.toArray(new String[0]);

343

}

344

345

@Override

346

public boolean hasMember(String key) {

347

return properties.containsKey(key) || "greet".equals(key);

348

}

349

350

@Override

351

public void putMember(String key, Value value) {

352

if (!"greet".equals(key)) {

353

properties.put(key, value.as(Object.class));

354

}

355

}

356

357

@Override

358

public boolean removeMember(String key) {

359

return !"greet".equals(key) && properties.remove(key) != null;

360

}

361

}

362

}

363

364

// Usage

365

Context context = Context.create("js");

366

context.getBindings("js").putMember("Person", new PersonConstructor());

367

368

context.eval("js", """

369

let alice = new Person("Alice", 30);

370

console.log(alice.name); // "Alice"

371

console.log(alice.greet()); // "Hello, I'm Alice and I'm 30 years old."

372

373

let bob = new Person("Bob", 25);

374

bob.occupation = "Developer";

375

console.log(bob.occupation); // "Developer"

376

""");

377

```

378

379

## Iteration Support

380

381

### ProxyIterable Interface

382

383

ProxyIterable enables host objects to be iterated in guest languages.

384

385

```java { .api }

386

public interface ProxyIterable extends Proxy {

387

/**

388

* Returns an iterator for this object.

389

* @return an iterator (typically a ProxyIterator)

390

*/

391

Object getIterator();

392

}

393

```

394

395

### ProxyIterator Interface

396

397

ProxyIterator represents iterator objects that can be used in for-loops and other iteration constructs.

398

399

```java { .api }

400

public interface ProxyIterator extends Proxy {

401

/**

402

* Checks if the iterator has more elements.

403

* @return true if there are more elements

404

*/

405

boolean hasNext();

406

407

/**

408

* Returns the next element and advances the iterator.

409

* @return the next element

410

*/

411

Object getNext();

412

}

413

```

414

415

**Iterator Implementation Example:**

416

417

```java

418

import org.graalvm.polyglot.proxy.ProxyIterable;

419

import org.graalvm.polyglot.proxy.ProxyIterator;

420

421

public class Range implements ProxyIterable {

422

private final int start;

423

private final int end;

424

private final int step;

425

426

public Range(int start, int end, int step) {

427

this.start = start;

428

this.end = end;

429

this.step = step;

430

}

431

432

@Override

433

public Object getIterator() {

434

return new RangeIterator(start, end, step);

435

}

436

437

private static class RangeIterator implements ProxyIterator {

438

private int current;

439

private final int end;

440

private final int step;

441

442

public RangeIterator(int start, int end, int step) {

443

this.current = start;

444

this.end = end;

445

this.step = step;

446

}

447

448

@Override

449

public boolean hasNext() {

450

return step > 0 ? current < end : current > end;

451

}

452

453

@Override

454

public Object getNext() {

455

if (!hasNext()) {

456

throw new RuntimeException("No more elements");

457

}

458

int value = current;

459

current += step;

460

return value;

461

}

462

}

463

}

464

465

// Usage

466

Context context = Context.create("js");

467

context.getBindings("js").putMember("range", new Range(0, 10, 2));

468

469

context.eval("js", """

470

for (let num of range) {

471

console.log(num); // 0, 2, 4, 6, 8

472

}

473

474

// Convert to array

475

let arr = Array.from(range);

476

console.log(arr); // [0, 2, 4, 6, 8]

477

""");

478

```

479

480

## Map-Like Behavior

481

482

### ProxyHashMap Interface

483

484

ProxyHashMap enables host objects to behave like hash maps or dictionaries with key-value operations.

485

486

```java { .api }

487

public interface ProxyHashMap extends Proxy {

488

/**

489

* Returns the value for the given key.

490

* @param key the key

491

* @return the value, or null if not found

492

*/

493

Object getHashValue(Object key);

494

495

/**

496

* Sets a key-value pair.

497

* @param key the key

498

* @param value the value

499

*/

500

void putHashEntry(Object key, Value value);

501

502

/**

503

* Removes a key-value pair.

504

* @param key the key to remove

505

* @return true if the key was removed

506

*/

507

boolean removeHashEntry(Object key);

508

509

/**

510

* Returns the number of entries.

511

* @return the size

512

*/

513

long getHashSize();

514

515

/**

516

* Checks if a key exists.

517

* @param key the key

518

* @return true if the key exists

519

*/

520

boolean hasHashEntry(Object key);

521

522

/**

523

* Returns an iterator over all entries.

524

* @return iterator yielding [key, value] pairs

525

*/

526

Object getHashEntriesIterator();

527

528

/**

529

* Returns an iterator over all keys.

530

* @return iterator yielding keys

531

*/

532

Object getHashKeysIterator();

533

534

/**

535

* Returns an iterator over all values.

536

* @return iterator yielding values

537

*/

538

Object getHashValuesIterator();

539

}

540

```

541

542

**ProxyHashMap Implementation Example:**

543

544

```java

545

import org.graalvm.polyglot.proxy.ProxyHashMap;

546

import java.util.concurrent.ConcurrentHashMap;

547

import java.util.Map;

548

549

public class ConcurrentMap implements ProxyHashMap {

550

private final Map<Object, Object> map = new ConcurrentHashMap<>();

551

552

@Override

553

public Object getHashValue(Object key) {

554

return map.get(key);

555

}

556

557

@Override

558

public void putHashEntry(Object key, Value value) {

559

map.put(key, value.as(Object.class));

560

}

561

562

@Override

563

public boolean removeHashEntry(Object key) {

564

return map.remove(key) != null;

565

}

566

567

@Override

568

public long getHashSize() {

569

return map.size();

570

}

571

572

@Override

573

public boolean hasHashEntry(Object key) {

574

return map.containsKey(key);

575

}

576

577

@Override

578

public Object getHashEntriesIterator() {

579

return new MapEntriesIterator(map.entrySet().iterator());

580

}

581

582

@Override

583

public Object getHashKeysIterator() {

584

return new SimpleIterator(map.keySet().iterator());

585

}

586

587

@Override

588

public Object getHashValuesIterator() {

589

return new SimpleIterator(map.values().iterator());

590

}

591

}

592

593

// Usage

594

Context context = Context.create("js");

595

context.getBindings("js").putMember("concurrentMap", new ConcurrentMap());

596

597

context.eval("js", """

598

concurrentMap.set("user:1", {name: "Alice", age: 30});

599

concurrentMap.set("user:2", {name: "Bob", age: 25});

600

601

console.log(concurrentMap.size); // 2

602

console.log(concurrentMap.get("user:1").name); // "Alice"

603

604

for (let [key, value] of concurrentMap.entries()) {

605

console.log(`${key}: ${value.name}`);

606

}

607

""");

608

```

609

610

## Temporal Proxy Interfaces

611

612

The polyglot API provides specialized proxy interfaces for temporal (date/time) values.

613

614

### ProxyDate Interface

615

616

```java { .api }

617

public interface ProxyDate extends Proxy {

618

/**

619

* Returns the date as a LocalDate.

620

* @return the date

621

*/

622

LocalDate asDate();

623

}

624

```

625

626

### ProxyTime Interface

627

628

```java { .api }

629

public interface ProxyTime extends Proxy {

630

/**

631

* Returns the time as a LocalTime.

632

* @return the time

633

*/

634

LocalTime asTime();

635

}

636

```

637

638

### ProxyTimeZone Interface

639

640

```java { .api }

641

public interface ProxyTimeZone extends Proxy {

642

/**

643

* Returns the timezone as a ZoneId.

644

* @return the timezone

645

*/

646

ZoneId asTimeZone();

647

}

648

```

649

650

### ProxyDuration Interface

651

652

```java { .api }

653

public interface ProxyDuration extends Proxy {

654

/**

655

* Returns the duration.

656

* @return the duration

657

*/

658

Duration asDuration();

659

}

660

```

661

662

### ProxyInstant Interface

663

664

ProxyInstant combines date, time, and timezone information.

665

666

```java { .api }

667

public interface ProxyInstant extends ProxyDate, ProxyTime, ProxyTimeZone {

668

// Inherits asDate(), asTime(), and asTimeZone() methods

669

}

670

```

671

672

**Temporal Proxy Example:**

673

674

```java

675

import org.graalvm.polyglot.proxy.ProxyInstant;

676

import java.time.*;

677

678

public class CustomDateTime implements ProxyInstant {

679

private final ZonedDateTime dateTime;

680

681

public CustomDateTime(ZonedDateTime dateTime) {

682

this.dateTime = dateTime;

683

}

684

685

@Override

686

public LocalDate asDate() {

687

return dateTime.toLocalDate();

688

}

689

690

@Override

691

public LocalTime asTime() {

692

return dateTime.toLocalTime();

693

}

694

695

@Override

696

public ZoneId asTimeZone() {

697

return dateTime.getZone();

698

}

699

}

700

701

// Usage

702

Context context = Context.create("js");

703

ZonedDateTime now = ZonedDateTime.now(ZoneId.of("America/New_York"));

704

context.getBindings("js").putMember("customTime", new CustomDateTime(now));

705

706

context.eval("js", """

707

console.log(customTime); // Shows as date/time object

708

let jsDate = new Date(customTime); // Converts to JavaScript Date

709

console.log(jsDate.getFullYear()); // Accesses year

710

""");

711

```

712

713

## Native Pointer Support

714

715

### ProxyNativeObject Interface

716

717

ProxyNativeObject represents objects that wrap native pointers.

718

719

```java { .api }

720

public interface ProxyNativeObject extends Proxy {

721

/**

722

* Returns the native pointer value.

723

* @return the pointer as a long value

724

*/

725

long asPointer();

726

}

727

```

728

729

**Native Pointer Example:**

730

731

```java

732

import org.graalvm.polyglot.proxy.ProxyNativeObject;

733

734

public class NativeBuffer implements ProxyNativeObject {

735

private final long nativePointer;

736

private final int size;

737

738

public NativeBuffer(long pointer, int size) {

739

this.nativePointer = pointer;

740

this.size = size;

741

}

742

743

@Override

744

public long asPointer() {

745

return nativePointer;

746

}

747

748

public int getSize() {

749

return size;

750

}

751

}

752

753

// Usage with languages that support native pointers

754

Context context = Context.create("llvm"); // Example with LLVM language

755

NativeBuffer buffer = new NativeBuffer(0x7fff12345678L, 1024);

756

context.getBindings("llvm").putMember("nativeBuffer", buffer);

757

```

758

759

## Composite Proxy Objects

760

761

You can implement multiple proxy interfaces to create objects with rich behavior.

762

763

### Multi-Interface Proxy Example

764

765

```java

766

import org.graalvm.polyglot.proxy.*;

767

import java.util.*;

768

769

public class SmartCollection implements ProxyArray, ProxyObject, ProxyIterable {

770

private final List<Object> data = new ArrayList<>();

771

private final Map<String, Object> metadata = new HashMap<>();

772

773

public SmartCollection() {

774

metadata.put("created", System.currentTimeMillis());

775

metadata.put("type", "SmartCollection");

776

}

777

778

// ProxyArray implementation

779

@Override

780

public Object get(long index) {

781

return index >= 0 && index < data.size() ? data.get((int) index) : null;

782

}

783

784

@Override

785

public void set(long index, Value value) {

786

int idx = (int) index;

787

while (data.size() <= idx) {

788

data.add(null);

789

}

790

data.set(idx, value.as(Object.class));

791

}

792

793

@Override

794

public boolean remove(long index) {

795

if (index >= 0 && index < data.size()) {

796

data.remove((int) index);

797

return true;

798

}

799

return false;

800

}

801

802

@Override

803

public long getSize() {

804

return data.size();

805

}

806

807

// ProxyObject implementation

808

@Override

809

public Object getMember(String key) {

810

switch (key) {

811

case "length": return data.size();

812

case "push": return new ProxyExecutable() {

813

@Override

814

public Object execute(Value... arguments) {

815

for (Value arg : arguments) {

816

data.add(arg.as(Object.class));

817

}

818

return data.size();

819

}

820

};

821

case "pop": return new ProxyExecutable() {

822

@Override

823

public Object execute(Value... arguments) {

824

return data.isEmpty() ? null : data.remove(data.size() - 1);

825

}

826

};

827

default: return metadata.get(key);

828

}

829

}

830

831

@Override

832

public Object getMemberKeys() {

833

Set<String> keys = new HashSet<>(metadata.keySet());

834

keys.addAll(Arrays.asList("length", "push", "pop"));

835

return keys.toArray(new String[0]);

836

}

837

838

@Override

839

public boolean hasMember(String key) {

840

return metadata.containsKey(key) ||

841

Arrays.asList("length", "push", "pop").contains(key);

842

}

843

844

@Override

845

public void putMember(String key, Value value) {

846

if (!Arrays.asList("length", "push", "pop").contains(key)) {

847

metadata.put(key, value.as(Object.class));

848

}

849

}

850

851

@Override

852

public boolean removeMember(String key) {

853

return metadata.remove(key) != null;

854

}

855

856

// ProxyIterable implementation

857

@Override

858

public Object getIterator() {

859

return new SimpleIterator(data.iterator());

860

}

861

}

862

863

// Usage

864

Context context = Context.create("js");

865

SmartCollection collection = new SmartCollection();

866

context.getBindings("js").putMember("smartArray", collection);

867

868

context.eval("js", """

869

// Use as array

870

smartArray[0] = "first";

871

smartArray[1] = "second";

872

console.log(smartArray.length); // 2

873

874

// Use array methods

875

smartArray.push("third", "fourth");

876

console.log(smartArray.length); // 4

877

878

// Use as object

879

smartArray.description = "A smart collection";

880

console.log(smartArray.description); // "A smart collection"

881

882

// Iterate

883

for (let item of smartArray) {

884

console.log(item); // "first", "second", "third", "fourth"

885

}

886

""");

887

```

888

889

## Error Handling in Proxies

890

891

### Exception Handling

892

893

Proxy methods can throw exceptions that will be properly propagated to guest languages:

894

895

```java

896

public class SafeCalculator implements ProxyExecutable {

897

898

@Override

899

public Object execute(Value... arguments) {

900

if (arguments.length != 2) {

901

throw new IllegalArgumentException("Calculator requires exactly 2 arguments");

902

}

903

904

try {

905

double a = arguments[0].asDouble();

906

double b = arguments[1].asDouble();

907

908

if (Double.isNaN(a) || Double.isNaN(b)) {

909

throw new ArithmeticException("Arguments cannot be NaN");

910

}

911

912

return a / b;

913

} catch (ClassCastException e) {

914

throw new IllegalArgumentException("Arguments must be numbers", e);

915

}

916

}

917

}

918

919

// Usage

920

Context context = Context.create("js");

921

context.getBindings("js").putMember("divide", new SafeCalculator());

922

923

try {

924

context.eval("js", "divide('not', 'numbers')");

925

} catch (PolyglotException e) {

926

if (e.isHostException()) {

927

Throwable hostException = e.asHostException();

928

System.out.println("Host exception: " + hostException.getMessage());

929

}

930

}

931

```

932

933

## Performance Best Practices

934

935

### Efficient Proxy Implementation

936

937

1. **Minimize Object Creation**: Reuse objects where possible

938

2. **Cache Computed Values**: Store expensive calculations

939

3. **Use Appropriate Data Structures**: Choose efficient backing collections

940

4. **Implement Only Needed Interfaces**: Don't implement unused proxy interfaces

941

942

```java

943

public class EfficientProxy implements ProxyObject, ProxyArray {

944

private final Map<String, Object> members = new HashMap<>();

945

private final List<Object> elements = new ArrayList<>();

946

947

// Cache frequently accessed members

948

private ProxyExecutable cachedMethod;

949

950

@Override

951

public Object getMember(String key) {

952

if ("expensiveMethod".equals(key)) {

953

if (cachedMethod == null) {

954

cachedMethod = new ExpensiveMethod();

955

}

956

return cachedMethod;

957

}

958

return members.get(key);

959

}

960

961

// ... other implementations

962

}

963

```