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
```