CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-graalvm-polyglot--polyglot

GraalVM Polyglot API for embedding multiple programming languages in Java applications with secure language interoperability

Pending
Overview
Eval results
Files

proxy-system.mddocs/

Proxy System

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.

Capabilities

Base Proxy Interface

All proxy types implement the base Proxy interface.

public interface Proxy {
    // Marker interface - no methods
}

Object Proxies

Enable Java objects to behave like guest language objects with named members.

public interface ProxyObject extends Proxy {
    Object getMember(String key);
    Object[] getMemberKeys();
    boolean hasMember(String key);
    void putMember(String key, Value value);
    boolean removeMember(String key);
}

Usage:

public class PersonProxy implements ProxyObject {
    private final Map<String, Object> properties = new HashMap<>();
    
    public PersonProxy(String name, int age) {
        properties.put("name", name);
        properties.put("age", age);
    }
    
    @Override
    public Object getMember(String key) {
        return properties.get(key);
    }
    
    @Override
    public Object[] getMemberKeys() {
        return properties.keySet().toArray();
    }
    
    @Override
    public boolean hasMember(String key) {
        return properties.containsKey(key);
    }
    
    @Override
    public void putMember(String key, Value value) {
        properties.put(key, value.isString() ? value.asString() : value);
    }
    
    @Override
    public boolean removeMember(String key) {
        return properties.remove(key) != null;
    }
}

// Usage in context
try (Context context = Context.create("js")) {
    PersonProxy person = new PersonProxy("Alice", 30);
    context.getBindings("js").putMember("person", person);
    
    // JavaScript can access as object
    context.eval("js", "console.log(person.name)");      // "Alice"
    context.eval("js", "person.age = 31");               // Modify age
    context.eval("js", "person.city = 'New York'");      // Add new property
    context.eval("js", "console.log(person.city)");      // "New York"
}

Array Proxies

Enable Java objects to behave like guest language arrays.

public interface ProxyArray extends ProxyIterable {
    Object get(long index);
    void set(long index, Value value);
    long getSize();
    boolean remove(long index);
}

Usage:

public class ListProxy implements ProxyArray {
    private final List<Object> list;
    
    public ListProxy(List<Object> list) {
        this.list = list;
    }
    
    @Override
    public Object get(long index) {
        return list.get((int) index);
    }
    
    @Override
    public void set(long index, Value value) {
        list.set((int) index, value.isString() ? value.asString() : value);
    }
    
    @Override
    public long getSize() {
        return list.size();
    }
    
    @Override
    public boolean remove(long index) {
        try {
            list.remove((int) index);
            return true;
        } catch (IndexOutOfBoundsException e) {
            return false;
        }
    }
    
    @Override
    public Object getIterator() {
        return new IteratorProxy(list.iterator());
    }
}

// Usage
try (Context context = Context.create("js")) {
    ListProxy array = new ListProxy(new ArrayList<>(Arrays.asList("a", "b", "c")));
    context.getBindings("js").putMember("array", array);
    
    // JavaScript can access as array
    context.eval("js", "console.log(array[0])");         // "a"
    context.eval("js", "array[1] = 'modified'");         // Modify element
    context.eval("js", "console.log(array.length)");     // Array size
}

Executable Proxies

Enable Java objects to be called as functions.

public interface ProxyExecutable extends Proxy {
    Object execute(Value... arguments);
}

Usage:

public class MathFunction implements ProxyExecutable {
    private final String operation;
    
    public MathFunction(String operation) {
        this.operation = operation;
    }
    
    @Override
    public Object execute(Value... arguments) {
        if (arguments.length != 2) {
            throw new IllegalArgumentException("Expected 2 arguments");
        }
        
        double a = arguments[0].asDouble();
        double b = arguments[1].asDouble();
        
        switch (operation) {
            case "add": return a + b;
            case "multiply": return a * b;
            case "divide": return b != 0 ? a / b : Double.POSITIVE_INFINITY;
            default: throw new IllegalArgumentException("Unknown operation: " + operation);
        }
    }
}

// Usage
try (Context context = Context.create("js")) {
    MathFunction addFunc = new MathFunction("add");
    context.getBindings("js").putMember("add", addFunc);
    
    // JavaScript can call as function
    Value result = context.eval("js", "add(10, 20)");
    System.out.println("Result: " + result.asDouble()); // 30.0
}

Instantiable Proxies

Enable Java objects to be used as constructors.

public interface ProxyInstantiable extends Proxy {
    Object newInstance(Value... arguments);
}

Usage:

public class PersonConstructor implements ProxyInstantiable {
    @Override
    public Object newInstance(Value... arguments) {
        if (arguments.length < 2) {
            throw new IllegalArgumentException("Person requires name and age");
        }
        
        String name = arguments[0].asString();
        int age = arguments[1].asInt();
        
        return new PersonProxy(name, age);
    }
}

// Usage
try (Context context = Context.create("js")) {
    PersonConstructor PersonClass = new PersonConstructor();
    context.getBindings("js").putMember("Person", PersonClass);
    
    // JavaScript can use as constructor
    context.eval("js", "const alice = new Person('Alice', 30)");
    context.eval("js", "console.log(alice.name)"); // "Alice"
}

Iterable and Iterator Proxies

Enable Java objects to support iteration protocols.

public interface ProxyIterable extends Proxy {
    Object getIterator();
}

public interface ProxyIterator extends Proxy {
    boolean hasNext();
    Object getNext();
}

Usage:

public class IteratorProxy implements ProxyIterator {
    private final Iterator<?> iterator;
    
    public IteratorProxy(Iterator<?> iterator) {
        this.iterator = iterator;
    }
    
    @Override
    public boolean hasNext() {
        return iterator.hasNext();
    }
    
    @Override
    public Object getNext() {
        return iterator.next();
    }
}

public class RangeProxy implements ProxyIterable {
    private final int start, end;
    
    public RangeProxy(int start, int end) {
        this.start = start;
        this.end = end;
    }
    
    @Override
    public Object getIterator() {
        return new IteratorProxy(IntStream.range(start, end).iterator());
    }
}

// Usage
try (Context context = Context.create("js")) {
    RangeProxy range = new RangeProxy(1, 5);
    context.getBindings("js").putMember("range", range);
    
    // JavaScript can iterate
    context.eval("js", "for (const num of range) { console.log(num); }"); // 1, 2, 3, 4
}

Hash Map Proxies

Enable Java objects to behave like guest language maps/dictionaries.

public interface ProxyHashMap extends Proxy {
    long getHashSize();
    boolean hasHashEntry(Value key);
    Value getHashValue(Value key);
    void putHashEntry(Value key, Value value);
    boolean removeHashEntry(Value key);
    Value getHashEntriesIterator();
    Value getHashKeysIterator();
    Value getHashValuesIterator();
}

Usage:

public class MapProxy implements ProxyHashMap {
    private final Map<Object, Object> map = new HashMap<>();
    
    @Override
    public long getHashSize() {
        return map.size();
    }
    
    @Override
    public boolean hasHashEntry(Value key) {
        return map.containsKey(convertKey(key));
    }
    
    @Override
    public Value getHashValue(Value key) {
        Object value = map.get(convertKey(key));
        return value != null ? Value.asValue(value) : null;
    }
    
    @Override
    public void putHashEntry(Value key, Value value) {
        map.put(convertKey(key), convertValue(value));
    }
    
    @Override
    public boolean removeHashEntry(Value key) {
        return map.remove(convertKey(key)) != null;
    }
    
    // Helper methods
    private Object convertKey(Value key) {
        if (key.isString()) return key.asString();
        if (key.isNumber()) return key.asInt();
        return key;
    }
    
    private Object convertValue(Value value) {
        if (value.isString()) return value.asString();
        if (value.isNumber()) return value.asDouble();
        if (value.isBoolean()) return value.asBoolean();
        return value;
    }
    
    // Iterator implementations omitted for brevity
    @Override
    public Value getHashEntriesIterator() { /* ... */ }
    @Override 
    public Value getHashKeysIterator() { /* ... */ }
    @Override
    public Value getHashValuesIterator() { /* ... */ }
}

Date/Time Proxies

Enable Java objects to represent date and time values in guest languages.

public interface ProxyDate extends Proxy {
    LocalDate asDate();
}

public interface ProxyTime extends Proxy {
    LocalTime asTime();
}

public interface ProxyTimeZone extends Proxy {
    ZoneId asTimeZone();
}

public interface ProxyDuration extends Proxy {
    Duration asDuration();
}

public interface ProxyInstant extends ProxyDate, ProxyTime, ProxyTimeZone {
    Instant asInstant();
}

Usage:

public class DateTimeProxy implements ProxyInstant {
    private final ZonedDateTime dateTime;
    
    public DateTimeProxy(ZonedDateTime dateTime) {
        this.dateTime = dateTime;
    }
    
    @Override
    public LocalDate asDate() {
        return dateTime.toLocalDate();
    }
    
    @Override
    public LocalTime asTime() {
        return dateTime.toLocalTime();
    }
    
    @Override
    public ZoneId asTimeZone() {
        return dateTime.getZone();
    }
    
    @Override
    public Instant asInstant() {
        return dateTime.toInstant();
    }
}

// Usage
try (Context context = Context.create("js")) {
    DateTimeProxy now = new DateTimeProxy(ZonedDateTime.now());
    context.getBindings("js").putMember("now", now);
    
    // JavaScript can work with as date
    context.eval("js", "console.log(new Date(now))");
}

Advanced Proxy Patterns

Composite Proxies

Implement multiple proxy interfaces for rich behavior:

public class SmartArray implements ProxyArray, ProxyObject, ProxyExecutable {
    private final List<Object> data = new ArrayList<>();
    
    // ProxyArray implementation
    @Override
    public Object get(long index) { return data.get((int) index); }
    @Override
    public void set(long index, Value value) { /* ... */ }
    @Override
    public long getSize() { return data.size(); }
    @Override
    public boolean remove(long index) { /* ... */ }
    @Override
    public Object getIterator() { /* ... */ }
    
    // ProxyObject implementation (for methods like push, pop, etc.)
    @Override
    public Object getMember(String key) {
        switch (key) {
            case "push": return new PushFunction();
            case "pop": return new PopFunction();
            case "length": return data.size();
            default: return null;
        }
    }
    // ... other ProxyObject methods
    
    // ProxyExecutable implementation (for functional behavior)
    @Override
    public Object execute(Value... arguments) {
        // Array can be called as function (e.g., for filtering)
        if (arguments.length == 1 && arguments[0].canExecute()) {
            return data.stream()
                .filter(item -> arguments[0].execute(item).asBoolean())
                .collect(Collectors.toList());
        }
        throw new UnsupportedOperationException();
    }
}

Dynamic Proxies

Create proxies that adapt behavior based on usage:

public class DynamicProxy implements ProxyObject, ProxyExecutable {
    private final Map<String, Object> properties = new HashMap<>();
    private final Object target;
    
    public DynamicProxy(Object target) {
        this.target = target;
        // Introspect target and populate properties
        populateFromTarget();
    }
    
    @Override
    public Object getMember(String key) {
        // First check explicit properties
        if (properties.containsKey(key)) {
            return properties.get(key);
        }
        
        // Then try reflection on target
        try {
            Method method = target.getClass().getMethod(key);
            return new MethodProxy(target, method);
        } catch (NoSuchMethodException e) {
            return null;
        }
    }
    
    // ... implement other methods
}

class MethodProxy implements ProxyExecutable {
    private final Object target;
    private final Method method;
    
    public MethodProxy(Object target, Method method) {
        this.target = target;
        this.method = method;
    }
    
    @Override
    public Object execute(Value... arguments) {
        try {
            Object[] args = Arrays.stream(arguments)
                .map(this::convertArgument)
                .toArray();
            return method.invoke(target, args);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    private Object convertArgument(Value arg) {
        // Convert Value to appropriate Java type
        // ... implementation
    }
}

Performance Considerations

  • Interface Segregation: Only implement proxy interfaces you actually need
  • Lazy Evaluation: Defer expensive operations until actually called
  • Caching: Cache computed values and method lookups when possible
  • Type Conversion: Minimize unnecessary type conversions between Value and Java objects
  • Memory Management: Be careful with object references in long-running contexts

Error Handling

Proxy methods should handle errors gracefully:

public class SafeProxy implements ProxyObject {
    @Override
    public Object getMember(String key) {
        try {
            return doGetMember(key);
        } catch (Exception e) {
            // Log error, return null, or throw PolyglotException
            throw new PolyglotException("Error accessing member: " + key, e);
        }
    }
    
    // ... other methods with similar error handling
}

Install with Tessl CLI

npx tessl i tessl/maven-org-graalvm-polyglot--polyglot

docs

context-management.md

engine-management.md

execution-monitoring.md

index.md

io-abstractions.md

proxy-system.md

security-configuration.md

source-management.md

value-operations.md

tile.json