0
# Proxy System
1
2
The proxy system enables Java objects to be seamlessly exposed to guest languages with customizable behaviors. Proxy interfaces allow Java objects to participate in polyglot interactions by implementing language-specific protocols for object access, function execution, and data manipulation.
3
4
## Capabilities
5
6
### Base Proxy Interface
7
8
All proxy types implement the base Proxy interface.
9
10
```java { .api }
11
public interface Proxy {
12
// Marker interface - no methods
13
}
14
```
15
16
### Object Proxies
17
18
Enable Java objects to behave like guest language objects with named members.
19
20
```java { .api }
21
public interface ProxyObject extends Proxy {
22
Object getMember(String key);
23
Object[] getMemberKeys();
24
boolean hasMember(String key);
25
void putMember(String key, Value value);
26
boolean removeMember(String key);
27
}
28
```
29
30
**Usage:**
31
32
```java
33
public class PersonProxy implements ProxyObject {
34
private final Map<String, Object> properties = new HashMap<>();
35
36
public PersonProxy(String name, int age) {
37
properties.put("name", name);
38
properties.put("age", age);
39
}
40
41
@Override
42
public Object getMember(String key) {
43
return properties.get(key);
44
}
45
46
@Override
47
public Object[] getMemberKeys() {
48
return properties.keySet().toArray();
49
}
50
51
@Override
52
public boolean hasMember(String key) {
53
return properties.containsKey(key);
54
}
55
56
@Override
57
public void putMember(String key, Value value) {
58
properties.put(key, value.isString() ? value.asString() : value);
59
}
60
61
@Override
62
public boolean removeMember(String key) {
63
return properties.remove(key) != null;
64
}
65
}
66
67
// Usage in context
68
try (Context context = Context.create("js")) {
69
PersonProxy person = new PersonProxy("Alice", 30);
70
context.getBindings("js").putMember("person", person);
71
72
// JavaScript can access as object
73
context.eval("js", "console.log(person.name)"); // "Alice"
74
context.eval("js", "person.age = 31"); // Modify age
75
context.eval("js", "person.city = 'New York'"); // Add new property
76
context.eval("js", "console.log(person.city)"); // "New York"
77
}
78
```
79
80
### Array Proxies
81
82
Enable Java objects to behave like guest language arrays.
83
84
```java { .api }
85
public interface ProxyArray extends ProxyIterable {
86
Object get(long index);
87
void set(long index, Value value);
88
long getSize();
89
boolean remove(long index);
90
}
91
```
92
93
**Usage:**
94
95
```java
96
public class ListProxy implements ProxyArray {
97
private final List<Object> list;
98
99
public ListProxy(List<Object> list) {
100
this.list = list;
101
}
102
103
@Override
104
public Object get(long index) {
105
return list.get((int) index);
106
}
107
108
@Override
109
public void set(long index, Value value) {
110
list.set((int) index, value.isString() ? value.asString() : value);
111
}
112
113
@Override
114
public long getSize() {
115
return list.size();
116
}
117
118
@Override
119
public boolean remove(long index) {
120
try {
121
list.remove((int) index);
122
return true;
123
} catch (IndexOutOfBoundsException e) {
124
return false;
125
}
126
}
127
128
@Override
129
public Object getIterator() {
130
return new IteratorProxy(list.iterator());
131
}
132
}
133
134
// Usage
135
try (Context context = Context.create("js")) {
136
ListProxy array = new ListProxy(new ArrayList<>(Arrays.asList("a", "b", "c")));
137
context.getBindings("js").putMember("array", array);
138
139
// JavaScript can access as array
140
context.eval("js", "console.log(array[0])"); // "a"
141
context.eval("js", "array[1] = 'modified'"); // Modify element
142
context.eval("js", "console.log(array.length)"); // Array size
143
}
144
```
145
146
### Executable Proxies
147
148
Enable Java objects to be called as functions.
149
150
```java { .api }
151
public interface ProxyExecutable extends Proxy {
152
Object execute(Value... arguments);
153
}
154
```
155
156
**Usage:**
157
158
```java
159
public class MathFunction implements ProxyExecutable {
160
private final String operation;
161
162
public MathFunction(String operation) {
163
this.operation = operation;
164
}
165
166
@Override
167
public Object execute(Value... arguments) {
168
if (arguments.length != 2) {
169
throw new IllegalArgumentException("Expected 2 arguments");
170
}
171
172
double a = arguments[0].asDouble();
173
double b = arguments[1].asDouble();
174
175
switch (operation) {
176
case "add": return a + b;
177
case "multiply": return a * b;
178
case "divide": return b != 0 ? a / b : Double.POSITIVE_INFINITY;
179
default: throw new IllegalArgumentException("Unknown operation: " + operation);
180
}
181
}
182
}
183
184
// Usage
185
try (Context context = Context.create("js")) {
186
MathFunction addFunc = new MathFunction("add");
187
context.getBindings("js").putMember("add", addFunc);
188
189
// JavaScript can call as function
190
Value result = context.eval("js", "add(10, 20)");
191
System.out.println("Result: " + result.asDouble()); // 30.0
192
}
193
```
194
195
### Instantiable Proxies
196
197
Enable Java objects to be used as constructors.
198
199
```java { .api }
200
public interface ProxyInstantiable extends Proxy {
201
Object newInstance(Value... arguments);
202
}
203
```
204
205
**Usage:**
206
207
```java
208
public class PersonConstructor implements ProxyInstantiable {
209
@Override
210
public Object newInstance(Value... arguments) {
211
if (arguments.length < 2) {
212
throw new IllegalArgumentException("Person requires name and age");
213
}
214
215
String name = arguments[0].asString();
216
int age = arguments[1].asInt();
217
218
return new PersonProxy(name, age);
219
}
220
}
221
222
// Usage
223
try (Context context = Context.create("js")) {
224
PersonConstructor PersonClass = new PersonConstructor();
225
context.getBindings("js").putMember("Person", PersonClass);
226
227
// JavaScript can use as constructor
228
context.eval("js", "const alice = new Person('Alice', 30)");
229
context.eval("js", "console.log(alice.name)"); // "Alice"
230
}
231
```
232
233
### Iterable and Iterator Proxies
234
235
Enable Java objects to support iteration protocols.
236
237
```java { .api }
238
public interface ProxyIterable extends Proxy {
239
Object getIterator();
240
}
241
242
public interface ProxyIterator extends Proxy {
243
boolean hasNext();
244
Object getNext();
245
}
246
```
247
248
**Usage:**
249
250
```java
251
public class IteratorProxy implements ProxyIterator {
252
private final Iterator<?> iterator;
253
254
public IteratorProxy(Iterator<?> iterator) {
255
this.iterator = iterator;
256
}
257
258
@Override
259
public boolean hasNext() {
260
return iterator.hasNext();
261
}
262
263
@Override
264
public Object getNext() {
265
return iterator.next();
266
}
267
}
268
269
public class RangeProxy implements ProxyIterable {
270
private final int start, end;
271
272
public RangeProxy(int start, int end) {
273
this.start = start;
274
this.end = end;
275
}
276
277
@Override
278
public Object getIterator() {
279
return new IteratorProxy(IntStream.range(start, end).iterator());
280
}
281
}
282
283
// Usage
284
try (Context context = Context.create("js")) {
285
RangeProxy range = new RangeProxy(1, 5);
286
context.getBindings("js").putMember("range", range);
287
288
// JavaScript can iterate
289
context.eval("js", "for (const num of range) { console.log(num); }"); // 1, 2, 3, 4
290
}
291
```
292
293
### Hash Map Proxies
294
295
Enable Java objects to behave like guest language maps/dictionaries.
296
297
```java { .api }
298
public interface ProxyHashMap extends Proxy {
299
long getHashSize();
300
boolean hasHashEntry(Value key);
301
Value getHashValue(Value key);
302
void putHashEntry(Value key, Value value);
303
boolean removeHashEntry(Value key);
304
Value getHashEntriesIterator();
305
Value getHashKeysIterator();
306
Value getHashValuesIterator();
307
}
308
```
309
310
**Usage:**
311
312
```java
313
public class MapProxy implements ProxyHashMap {
314
private final Map<Object, Object> map = new HashMap<>();
315
316
@Override
317
public long getHashSize() {
318
return map.size();
319
}
320
321
@Override
322
public boolean hasHashEntry(Value key) {
323
return map.containsKey(convertKey(key));
324
}
325
326
@Override
327
public Value getHashValue(Value key) {
328
Object value = map.get(convertKey(key));
329
return value != null ? Value.asValue(value) : null;
330
}
331
332
@Override
333
public void putHashEntry(Value key, Value value) {
334
map.put(convertKey(key), convertValue(value));
335
}
336
337
@Override
338
public boolean removeHashEntry(Value key) {
339
return map.remove(convertKey(key)) != null;
340
}
341
342
// Helper methods
343
private Object convertKey(Value key) {
344
if (key.isString()) return key.asString();
345
if (key.isNumber()) return key.asInt();
346
return key;
347
}
348
349
private Object convertValue(Value value) {
350
if (value.isString()) return value.asString();
351
if (value.isNumber()) return value.asDouble();
352
if (value.isBoolean()) return value.asBoolean();
353
return value;
354
}
355
356
// Iterator implementations omitted for brevity
357
@Override
358
public Value getHashEntriesIterator() { /* ... */ }
359
@Override
360
public Value getHashKeysIterator() { /* ... */ }
361
@Override
362
public Value getHashValuesIterator() { /* ... */ }
363
}
364
```
365
366
### Date/Time Proxies
367
368
Enable Java objects to represent date and time values in guest languages.
369
370
```java { .api }
371
public interface ProxyDate extends Proxy {
372
LocalDate asDate();
373
}
374
375
public interface ProxyTime extends Proxy {
376
LocalTime asTime();
377
}
378
379
public interface ProxyTimeZone extends Proxy {
380
ZoneId asTimeZone();
381
}
382
383
public interface ProxyDuration extends Proxy {
384
Duration asDuration();
385
}
386
387
public interface ProxyInstant extends ProxyDate, ProxyTime, ProxyTimeZone {
388
Instant asInstant();
389
}
390
```
391
392
**Usage:**
393
394
```java
395
public class DateTimeProxy implements ProxyInstant {
396
private final ZonedDateTime dateTime;
397
398
public DateTimeProxy(ZonedDateTime dateTime) {
399
this.dateTime = dateTime;
400
}
401
402
@Override
403
public LocalDate asDate() {
404
return dateTime.toLocalDate();
405
}
406
407
@Override
408
public LocalTime asTime() {
409
return dateTime.toLocalTime();
410
}
411
412
@Override
413
public ZoneId asTimeZone() {
414
return dateTime.getZone();
415
}
416
417
@Override
418
public Instant asInstant() {
419
return dateTime.toInstant();
420
}
421
}
422
423
// Usage
424
try (Context context = Context.create("js")) {
425
DateTimeProxy now = new DateTimeProxy(ZonedDateTime.now());
426
context.getBindings("js").putMember("now", now);
427
428
// JavaScript can work with as date
429
context.eval("js", "console.log(new Date(now))");
430
}
431
```
432
433
## Advanced Proxy Patterns
434
435
### Composite Proxies
436
437
Implement multiple proxy interfaces for rich behavior:
438
439
```java
440
public class SmartArray implements ProxyArray, ProxyObject, ProxyExecutable {
441
private final List<Object> data = new ArrayList<>();
442
443
// ProxyArray implementation
444
@Override
445
public Object get(long index) { return data.get((int) index); }
446
@Override
447
public void set(long index, Value value) { /* ... */ }
448
@Override
449
public long getSize() { return data.size(); }
450
@Override
451
public boolean remove(long index) { /* ... */ }
452
@Override
453
public Object getIterator() { /* ... */ }
454
455
// ProxyObject implementation (for methods like push, pop, etc.)
456
@Override
457
public Object getMember(String key) {
458
switch (key) {
459
case "push": return new PushFunction();
460
case "pop": return new PopFunction();
461
case "length": return data.size();
462
default: return null;
463
}
464
}
465
// ... other ProxyObject methods
466
467
// ProxyExecutable implementation (for functional behavior)
468
@Override
469
public Object execute(Value... arguments) {
470
// Array can be called as function (e.g., for filtering)
471
if (arguments.length == 1 && arguments[0].canExecute()) {
472
return data.stream()
473
.filter(item -> arguments[0].execute(item).asBoolean())
474
.collect(Collectors.toList());
475
}
476
throw new UnsupportedOperationException();
477
}
478
}
479
```
480
481
### Dynamic Proxies
482
483
Create proxies that adapt behavior based on usage:
484
485
```java
486
public class DynamicProxy implements ProxyObject, ProxyExecutable {
487
private final Map<String, Object> properties = new HashMap<>();
488
private final Object target;
489
490
public DynamicProxy(Object target) {
491
this.target = target;
492
// Introspect target and populate properties
493
populateFromTarget();
494
}
495
496
@Override
497
public Object getMember(String key) {
498
// First check explicit properties
499
if (properties.containsKey(key)) {
500
return properties.get(key);
501
}
502
503
// Then try reflection on target
504
try {
505
Method method = target.getClass().getMethod(key);
506
return new MethodProxy(target, method);
507
} catch (NoSuchMethodException e) {
508
return null;
509
}
510
}
511
512
// ... implement other methods
513
}
514
515
class MethodProxy implements ProxyExecutable {
516
private final Object target;
517
private final Method method;
518
519
public MethodProxy(Object target, Method method) {
520
this.target = target;
521
this.method = method;
522
}
523
524
@Override
525
public Object execute(Value... arguments) {
526
try {
527
Object[] args = Arrays.stream(arguments)
528
.map(this::convertArgument)
529
.toArray();
530
return method.invoke(target, args);
531
} catch (Exception e) {
532
throw new RuntimeException(e);
533
}
534
}
535
536
private Object convertArgument(Value arg) {
537
// Convert Value to appropriate Java type
538
// ... implementation
539
}
540
}
541
```
542
543
## Performance Considerations
544
545
- **Interface Segregation**: Only implement proxy interfaces you actually need
546
- **Lazy Evaluation**: Defer expensive operations until actually called
547
- **Caching**: Cache computed values and method lookups when possible
548
- **Type Conversion**: Minimize unnecessary type conversions between Value and Java objects
549
- **Memory Management**: Be careful with object references in long-running contexts
550
551
## Error Handling
552
553
Proxy methods should handle errors gracefully:
554
555
```java
556
public class SafeProxy implements ProxyObject {
557
@Override
558
public Object getMember(String key) {
559
try {
560
return doGetMember(key);
561
} catch (Exception e) {
562
// Log error, return null, or throw PolyglotException
563
throw new PolyglotException("Error accessing member: " + key, e);
564
}
565
}
566
567
// ... other methods with similar error handling
568
}
569
```