All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.pkl.thirdparty.graalvm.polyglot.HostAccess Maven / Gradle / Ivy

Go to download

Fat Jar containing pkl-cli, pkl-codegen-java, pkl-codegen-kotlin, pkl-config-java, pkl-core, pkl-doc, and their shaded third-party dependencies.

There is a newer version: 0.27.1
Show newest version
/*
 * Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * The Universal Permissive License (UPL), Version 1.0
 *
 * Subject to the condition set forth below, permission is hereby granted to any
 * person obtaining a copy of this software, associated documentation and/or
 * data (collectively the "Software"), free of charge and under any and all
 * copyright rights in the Software, and any and all patent rights owned or
 * freely licensable by each licensor hereunder covering either (i) the
 * unmodified Software as contributed to or provided by such licensor, or (ii)
 * the Larger Works (as defined below), to deal in both
 *
 * (a) the Software, and
 *
 * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
 * one is included with the Software each a "Larger Work" to which the Software
 * is contributed by such licensors),
 *
 * without restriction, including without limitation the rights to copy, create
 * derivative works of, display, perform, and distribute the Software and make,
 * use, sell, offer for sale, import, export, have made, and have sold the
 * Software and the Larger Work(s), and to sublicense the foregoing rights on
 * either these or other terms.
 *
 * This license is subject to the following condition:
 *
 * The above copyright notice and either this complete permission notice or at a
 * minimum a reference to the UPL must be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package org.pkl.thirdparty.graalvm.polyglot;

import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.Predicate;

import org.pkl.thirdparty.graalvm.collections.EconomicMap;
import org.pkl.thirdparty.graalvm.collections.EconomicSet;
import org.pkl.thirdparty.graalvm.collections.Equivalence;
import org.pkl.thirdparty.graalvm.collections.MapCursor;

/**
 * Represents the host access policy of a polyglot context. The host access policy specifies which
 * methods and fields are accessible to the guest application, whenever Java host objects are
 * accessed.
 * 

* There are three predefined instances of host access policies: *

    *
  • {@link #EXPLICIT} - Java host methods or fields, must be public and be annotated with * {@link Export @Export} to make them accessible to the guest language. *
  • {@link #SCOPED} - Java host methods or fields, must be public and be annotated with * {@link Export @Export} to make them accessible to the guest language. Guest-to-host callback * parameter validity is scoped to the duration of the callback by default. *
  • {@link #NONE} - Does not allow any access to methods or fields of host objects. Java host * objects may still be passed into a context, but they cannot be accessed. *
  • {@link #ALL} - Does allow full unrestricted access to public methods or fields of host * objects. Note that this policy allows unrestricted access to reflection. It is highly discouraged * from using this policy in environments where the guest application is not fully trusted. *
  • {@link #CONSTRAINED} host access policy suitable for a context with * {@link SandboxPolicy#CONSTRAINED CONSTRAINED} sandbox policy. *
  • {@link #ISOLATED} host access policy suitable for a context with * {@link SandboxPolicy#ISOLATED ISOLATED} sandbox policy. *
  • {@link #UNTRUSTED} host access policy suitable for a context with * {@link SandboxPolicy#UNTRUSTED UNTRUSTED} sandbox policy. *
* Custom host access policies can be created using {@link #newBuilder()}. The builder allows to * specify a custom export annotation and allowed and denied methods or fields. * * @since 19.0 */ public final class HostAccess { private final String name; private final EconomicSet> accessAnnotations; final EconomicSet> implementableAnnotations; private final EconomicMap, Boolean> excludeTypes; private final EconomicSet members; private final EconomicSet> implementableTypes; private final List targetMappings; final boolean allowPublic; final boolean allowAllInterfaceImplementations; final boolean allowAllClassImplementations; final boolean allowArrayAccess; final boolean allowListAccess; final boolean allowBufferAccess; final boolean allowIterableAccess; final boolean allowIteratorAccess; final boolean allowMapAccess; final boolean allowBigIntegerNumberAccess; final boolean allowAccessInheritance; private final boolean methodScopingDefault; private final MutableTargetMapping[] allowMutableTargetMappings; private final EconomicSet> disableMethodScopingAnnotations; private final EconomicSet disableMethodScoping; volatile Object impl; private static final HostAccess EMPTY = new HostAccess(null, null, null, null, null, null, null, false, false, false, false, false, false, false, false, false, false, false, null, false, null, null); /** * Predefined host access policy that allows access to public host methods or fields that were * annotated with {@linkplain Export @Export} and were declared in public class. This is the * default configuration if {@link Context.Builder#allowAllAccess(boolean)} is * false. *

* Equivalent of using the following builder configuration: * *

     * 
     * HostAccess.newBuilder().allowAccessAnnotatedBy(HostAccess.Export.class).
     *                allowImplementationsAnnotatedBy(HostAccess.Implementable.class).
     *                allowImplementationsAnnotatedBy(FunctionalInterface.class).
     *                .build();
     * 
     * 
* * @since 19.0 */ public static final HostAccess EXPLICIT = newBuilder().// allowAccessAnnotatedBy(HostAccess.Export.class).// allowImplementationsAnnotatedBy(HostAccess.Implementable.class).// allowImplementationsAnnotatedBy(FunctionalInterface.class).// name("HostAccess.EXPLICIT").build(); /** * Predefined host access policy that is the same as EXPLICIT and enables method scoping of * callback parameter values on top. Methods that are annotated with * {@link HostAccess.DisableMethodScoping} are exempt from scoping. *

* Equivalent of using the following builder configuration: * *

     * 
     * HostAccess.newBuilder().allowAccessAnnotatedBy(HostAccess.Export.class).
     *                 allowImplementationsAnnotatedBy(HostAccess.Implementable.class).
     *                 allowImplementationsAnnotatedBy(FunctionalInterface.class).
     *                 methodScoping(true).
     *                 disableMethodScopingAnnotatedBy(HostAccess.DisableMethodScoping.class)
     *                 .build();
     * 
     * 
* * @since 21.3 */ public static final HostAccess SCOPED = newBuilder().// allowAccessAnnotatedBy(HostAccess.Export.class).// allowImplementationsAnnotatedBy(HostAccess.Implementable.class).// allowImplementationsAnnotatedBy(FunctionalInterface.class).// methodScoping(true).// disableMethodScopingAnnotatedBy(HostAccess.DisableMethodScoping.class).// name("HostAccess.SCOPED").build(); /** * Predefined host access policy that allows full unrestricted access to public methods or * fields of public host classes. Note that this policy allows unrestricted access to * reflection. It is highly discouraged from using this policy in environments where the guest * application is not fully trusted. This is the default configuration if * {@link Context.Builder#allowAllAccess(boolean)} is true. *

* Equivalent of using the following builder configuration: * *

     * 
     * HostAccess.newBuilder()
     *           allowPublicAccess(true).
     *           allowAllImplementations(true).
     *           allowAllClassImplementations(true).
     *           allowArrayAccess(true).
     *           allowListAccess(true).
     *           allowBufferAccess(true).
     *           allowIterableAccess(true).
     *           allowIteratorAccess(true).
     *           allowMapAccess(true).
     *           allowAccessInheritance(true).
     *           .build();
     * 
     * 
* * @since 19.0 */ public static final HostAccess ALL = newBuilder().// allowPublicAccess(true).// allowAllImplementations(true).// allowAllClassImplementations(true).// allowArrayAccess(true).allowListAccess(true).allowBufferAccess(true).// allowIterableAccess(true).allowIteratorAccess(true).allowMapAccess(true).// allowAccessInheritance(true).// name("HostAccess.ALL").build(); /** * Predefined host access policy that disallows any access to public host methods or fields. *

* Equivalent of using the following builder configuration: * *

     * HostAccess.newBuilder().build();
     * 
* * @since 19.0 */ public static final HostAccess NONE = newBuilder().name("HostAccess.NONE").build(); /** * Predefined host access policy used by Context with a {@link SandboxPolicy#CONSTRAINED} * sandbox policy when the host access policy is not explicitly specified by the embedder. *

* Equivalent of using the following builder configuration: * *

     * 
     * HostAccess.newBuilder().
     *           allowAccessAnnotatedBy(Export.class).
     *           allowImplementationsAnnotatedBy(Implementable.class).
     *           allowMutableTargetMappings().build();
     * 
     * 
* * @since 23.0 */ public static final HostAccess CONSTRAINED = HostAccess.newBuilder().// allowAccessAnnotatedBy(Export.class).// allowImplementationsAnnotatedBy(Implementable.class).// allowMutableTargetMappings().name("HostAccess.CONSTRAINED").build(); /** * Predefined host access policy used by Context with an {@link SandboxPolicy#ISOLATED} sandbox * policy when the host access policy is not explicitly specified by the embedder. *

* Equivalent of using the following builder configuration: * *

     * 
     * HostAccess.newBuilder(CONSTRAINED).
     *           methodScoping(true).build();
     * 
     * 
* * @since 23.0 */ public static final HostAccess ISOLATED = HostAccess.newBuilder(CONSTRAINED).// methodScoping(true).// name("HostAccess.ISOLATED").build(); /** * Predefined host access policy used by Context with an {@link SandboxPolicy#UNTRUSTED} sandbox * policy when the host access policy is not explicitly specified by the embedder. *

* Equivalent of using the following builder configuration: * *

     * 
     * HostAccess.newBuilder().
     *           allowAccessAnnotatedBy(Export.class).
     *           allowMutableTargetMappings().
     *           methodScoping(true).build();
     * 
     * 
* * @since 23.0 */ public static final HostAccess UNTRUSTED = HostAccess.newBuilder().// allowAccessAnnotatedBy(Export.class).// allowMutableTargetMappings().// methodScoping(true).// name("HostAccess.UNTRUSTED").build(); /** * List of default host object mappings of mutable target types available in * {@link Value#as(Class)}. The mappings map guest object traits to host object types. * * @since 23.0 */ public enum MutableTargetMapping { /** * Enables default mapping of guest object arrays to host object {@link java.util.List}. */ ARRAY_TO_JAVA_LIST, /** * Enables default mapping of guest object iterators to host object * {@link java.util.Iterator}. */ ITERATOR_TO_JAVA_ITERATOR, /** * Enables default mapping of guest object iterables to host object * {@link java.lang.Iterable}. */ ITERABLE_TO_JAVA_ITERABLE, /** * Enables default mapping of guest object hashes to host object {@link java.util.Map}. */ HASH_TO_JAVA_MAP, /** * Enables default mapping of guest objects with members to {@link java.util.Map}. */ MEMBERS_TO_JAVA_MAP, /** * Enables default mapping of guest objects with members to a Java interface. */ MEMBERS_TO_JAVA_INTERFACE, /** * Enables default mapping of executable guest objects to a * {@link java.lang.FunctionalInterface}. */ EXECUTABLE_TO_JAVA_INTERFACE, } HostAccess(EconomicSet> annotations, EconomicMap, Boolean> excludeTypes, EconomicSet members, EconomicSet> implementableAnnotations, EconomicSet> implementableTypes, List targetMappings, String name, boolean allowPublic, boolean allowAllImplementations, boolean allowAllClassImplementations, boolean allowArrayAccess, boolean allowListAccess, boolean allowBufferAccess, boolean allowIterableAccess, boolean allowIteratorAccess, boolean allowMapAccess, boolean allowBigIntegerNumberAccess, boolean allowAccessInheritance, MutableTargetMapping[] allowMutableTargetMappings, boolean methodScopingDefault, EconomicSet> disableMethodScopingAnnotations, EconomicSet disableMethodScoping) { // create defensive copies this.accessAnnotations = copySet(annotations, Equivalence.IDENTITY); this.excludeTypes = copyMap(excludeTypes, Equivalence.IDENTITY); this.members = copySet(members, Equivalence.DEFAULT); this.implementableAnnotations = copySet(implementableAnnotations, Equivalence.IDENTITY); this.implementableTypes = copySet(implementableTypes, Equivalence.IDENTITY); this.targetMappings = targetMappings != null ? new ArrayList<>(targetMappings) : null; this.name = name; this.allowPublic = allowPublic; this.allowAllInterfaceImplementations = allowAllImplementations; this.allowAllClassImplementations = allowAllClassImplementations; this.allowArrayAccess = allowArrayAccess; this.allowListAccess = allowListAccess; this.allowBufferAccess = allowBufferAccess; this.allowIterableAccess = allowListAccess || allowIterableAccess; this.allowMapAccess = allowMapAccess; this.allowBigIntegerNumberAccess = allowBigIntegerNumberAccess; this.allowIteratorAccess = allowListAccess || allowIterableAccess || allowMapAccess || allowIteratorAccess; this.allowAccessInheritance = allowAccessInheritance; this.allowMutableTargetMappings = allowMutableTargetMappings; this.methodScopingDefault = methodScopingDefault; this.disableMethodScopingAnnotations = disableMethodScopingAnnotations; this.disableMethodScoping = disableMethodScoping; } /** * {@inheritDoc} * * @since 20.3 */ @Override public boolean equals(Object obj) { if (!(obj instanceof HostAccess)) { return false; } HostAccess other = (HostAccess) obj; return allowPublic == other.allowPublic// && allowAllInterfaceImplementations == other.allowAllInterfaceImplementations// && allowAllClassImplementations == other.allowAllClassImplementations// && allowArrayAccess == other.allowArrayAccess// && allowListAccess == other.allowListAccess// && allowIterableAccess == other.allowIterableAccess// && allowIteratorAccess == other.allowIteratorAccess// && allowMapAccess == other.allowMapAccess// && allowBigIntegerNumberAccess == other.allowBigIntegerNumberAccess// && equalsMap(excludeTypes, other.excludeTypes)// && equalsSet(members, other.members)// && equalsSet(implementableAnnotations, other.implementableAnnotations)// && equalsSet(implementableTypes, other.implementableTypes)// && Objects.equals(targetMappings, other.targetMappings)// && equalsSet(accessAnnotations, other.accessAnnotations)// && Arrays.equals(allowMutableTargetMappings, other.allowMutableTargetMappings); } /** * {@inheritDoc} * * @since 20.3 */ @Override public int hashCode() { return Objects.hash(allowPublic, allowAllInterfaceImplementations, allowAllClassImplementations, allowArrayAccess, allowListAccess, allowIterableAccess, allowIteratorAccess, allowMapAccess, allowBigIntegerNumberAccess, hashMap(excludeTypes), hashSet(members), hashSet(implementableAnnotations), hashSet(implementableTypes), hashSet(members), targetMappings, hashSet(accessAnnotations)); } private static int hashMap(EconomicMap map) { int h = 0; if (map != null) { MapCursor cursor = map.getEntries(); while (cursor.advance()) { h += Objects.hashCode(cursor.getKey()) ^ Objects.hashCode(cursor.getValue()); } } return h; } private static int hashSet(EconomicSet set) { int h = 0; if (set != null) { for (V v : set) { if (v != null) { h += v.hashCode(); } } } return h; } private static boolean equalsMap(EconomicMap map0, EconomicMap map1) { if (Objects.equals(map0, map1)) { return true; } else if (map0 == null) { return false; } else if (map0.size() != map1.size()) { return false; } MapCursor cursor = map0.getEntries(); while (cursor.advance()) { if (!map1.containsKey(cursor.getKey())) { return false; } V v0 = cursor.getValue(); V v1 = map1.get(cursor.getKey()); if (!Objects.equals(v0, v1)) { return false; } } return true; } private static boolean equalsSet(EconomicSet set0, EconomicSet set1) { if (Objects.equals(set0, set1)) { return true; } else if (set0 == null) { return false; } else if (set0.size() != set1.size()) { return false; } for (T v : set0) { if (!set1.contains(v)) { return false; } } return true; } private static EconomicSet copySet(EconomicSet values, Equivalence equivalence) { if (values == null) { return null; } return EconomicSet.create(equivalence, values); } private static EconomicMap copyMap(EconomicMap values, Equivalence equivalence) { if (values == null) { return null; } return EconomicMap.create(equivalence, values); } /** * Creates a new builder that allows to create a custom host access policy. The builder * configuration needs to be completed using the {@link Builder#build() method}. * * @since 19.0 */ public static Builder newBuilder() { return EMPTY.new Builder(); } /** * Creates a new builder that allows to create a custom host access policy based of a preset * configuration. The preset configuration is copied and used as a template for the returned * buidler. The builder configuration needs to be completed using the {@link Builder#build() * method}. * * @since 19.0 */ public static Builder newBuilder(HostAccess conf) { Objects.requireNonNull(conf); return EMPTY.new Builder(conf); } List getTargetMappings() { return targetMappings; } boolean allowsImplementation(Class type) { if (allowAllInterfaceImplementations && type.isInterface()) { return true; } else if (allowAllClassImplementations && !type.isInterface()) { return true; } if (implementableTypes != null && implementableTypes.contains(type)) { return true; } if (implementableAnnotations != null) { for (Class ann : implementableAnnotations) { if (type.getAnnotation(ann) != null) { return true; } } } return false; } boolean allowsAccess(AnnotatedElement member) { if (excludeTypes != null) { Class owner = getDeclaringClass(member); MapCursor, Boolean> cursor = excludeTypes.getEntries(); while (cursor.advance()) { Class ban = cursor.getKey(); if (cursor.getValue()) { // include subclasses if (ban.isAssignableFrom(owner)) { return false; } } else { if (ban == owner) { return false; } } } } if (allowPublic) { return true; } if (members != null && members.contains(member)) { return true; } if (accessAnnotations != null) { for (Class ann : accessAnnotations) { if (hasAnnotation(member, ann)) { return true; } } } return false; } boolean isMethodScoped(Executable e) { if (!isMethodScopingEnabled()) { return false; } if (disableMethodScoping != null) { if (disableMethodScoping.contains(e)) { return false; } } if (disableMethodScopingAnnotations != null) { for (Class ann : disableMethodScopingAnnotations) { if (e.getAnnotation(ann) != null) { return false; } } } return true; } boolean isMethodScopingEnabled() { return methodScopingDefault; } MutableTargetMapping[] getMutableTargetMappings() { return allowMutableTargetMappings; } /** * {@inheritDoc} * * @since 19.0 */ @Override public String toString() { return name == null ? super.toString() : name; } private static boolean hasAnnotation(AnnotatedElement member, Class annotationType) { if (member instanceof Field) { Field f = (Field) member; return f.getAnnotation(annotationType) != null; } if (member instanceof Method) { Method m = (Method) member; return m.getAnnotation(annotationType) != null; } if (member instanceof Constructor) { Constructor c = (Constructor) member; return c.getAnnotation(annotationType) != null; } return false; } private static Class getDeclaringClass(AnnotatedElement member) { if (member instanceof Field) { Field f = (Field) member; return f.getDeclaringClass(); } if (member instanceof Method) { Method m = (Method) member; return m.getDeclaringClass(); } if (member instanceof Constructor) { Constructor c = (Constructor) member; return c.getDeclaringClass(); } return Object.class; } /** * Annotation used by the predefined {@link #EXPLICIT} access policy to mark public * constructors, methods and fields in public classes that should be accessible by the guest * application. *

* Example using a Java object from JavaScript: * *

     * public class JavaRecord {
     *     @HostAccess.Export public int x;
     *
     *     @HostAccess.Export
     *     public String name() {
     *         return "foo";
     *     }
     * }
     * try (Context context = Context.create()) {
     *     JavaRecord record = new JavaRecord();
     *     context.getBindings("js").putMember("javaRecord", record);
     *     context.eval("js", "javaRecord.x = 42");
     *     context.eval("js", "javaRecord.name()").asString().equals("foo");
     * }
     * 
* * @see Context.Builder#allowHostAccess(HostAccess) * @see HostAccess#EXPLICIT * @since 19.0 */ @Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface Export { } /** * Allows guest language to implement a Java type. Implementable is required if the * {@link HostAccess#EXPLICIT explicit} host access policy is set. The annotation to use for * this purpose can be customized with {@link Builder#allowImplementationsAnnotatedBy(Class)}. * Allowing implementations for all Java interfaces can be enabled with * {@link Builder#allowAllImplementations(boolean)}. * * @see Context.Builder#allowHostAccess(HostAccess) * @see Value#as(Class) * @see HostAccess#EXPLICIT * @since 19.0 */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) public @interface Implementable { } /** * If {@link HostAccess#SCOPED} is used, placing this annotation on an exported host function * excludes it from parameter scoping, i.e. parameters will not be released after invocation of * a callback. * * @see HostAccess#SCOPED * @since 21.3 * */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.CONSTRUCTOR, ElementType.METHOD}) public @interface DisableMethodScoping { } /** * Represents the precedence of a target type mapping. The precedence influences target type * mappings in two ways: *
    *
  • The conversion order in which target mappings are performed. {@link #HIGHEST Highest} and * {@link #HIGH high} precedences are invoked before all {@link Value#as(Class) default * mappings}. {@link #LOW Low} after all loss less conversions and {@link #LOWEST lowest} after * all other default mappings. *
  • To disambiguate multiple selected overloads on method invocation. The overload precedence * defines which method has precedence over other applicable methods. {@link #HIGHEST Highest} * have higher and {@link #HIGH high} have the same precedence as the default loss-less mapping. * The precedence {@link #LOW low} declares equal precedence than all lossy coercions and * {@link #LOWEST lowest} defines precedence lower than all default mappings. *
* * @see Value#as(Class) for detailed information on conversion order. * @since 20.3 */ public enum TargetMappingPrecedence { /** * Defines higher precedence and conversion order as all default mappings and target type * mappings with lower precedence. * * @since 20.3 */ HIGHEST, /** * Defines high or default precedence and conversion order for a target type mapping. This * precedence makes mappings be used before all other default mappings and treated with * equal overload precedence as default loss less mappings like primitive coercions. * * @since 20.3 */ HIGH, /** * Defines low precedence and conversion order for a target type mapping. This precedence * makes mappings be used before all other default lossy mappings and treated with equal * overload precedence as default lossy mappings, like mappings to Map. * * @since 20.3 */ LOW, /** * Defines lowest precedence and conversion order for a target type mapping. This precedence * makes mappings be used after all other default mappings and treated with lower overload * precedence as all default mappings or other target type mappings. * * @since 20.3 */ LOWEST } /** * Builder to create a custom {@link HostAccess host access policy}. * * @since 19.0 */ public final class Builder { private EconomicSet> accessAnnotations; private EconomicSet> implementationAnnotations; private EconomicMap, Boolean> excludeTypes; private EconomicSet> implementableTypes; private EconomicSet members; private List targetMappings; private boolean allowPublic; private boolean allowArrayAccess; private boolean allowListAccess; private boolean allowBufferAccess; private boolean allowIterableAccess; private boolean allowIteratorAccess; private boolean allowMapAccess; private boolean allowBigIntegerNumberAccess = true; private boolean allowAllImplementations; private boolean allowAllClassImplementations; private boolean allowAccessInheritance; private MutableTargetMapping[] allowMutableTargetMappings = MutableTargetMapping.values(); private boolean methodScopingDefault; private EconomicSet> disableMethodScopingAnnotations; private EconomicSet disableMethodScoping; private String name; Builder() { } Builder(HostAccess access) { this.accessAnnotations = copySet(access.accessAnnotations, Equivalence.IDENTITY); this.excludeTypes = copyMap(access.excludeTypes, Equivalence.IDENTITY); this.members = copySet(access.members, Equivalence.DEFAULT); this.implementationAnnotations = copySet(access.implementableAnnotations, Equivalence.IDENTITY); this.implementableTypes = copySet(access.implementableTypes, Equivalence.IDENTITY); this.targetMappings = access.targetMappings != null ? new ArrayList<>(access.targetMappings) : null; this.excludeTypes = access.excludeTypes; this.allowPublic = access.allowPublic; this.allowListAccess = access.allowListAccess; this.allowArrayAccess = access.allowArrayAccess; this.allowBufferAccess = access.allowBufferAccess; this.allowIterableAccess = access.allowIterableAccess; this.allowIteratorAccess = access.allowIteratorAccess; this.allowMapAccess = access.allowMapAccess; this.allowBigIntegerNumberAccess = access.allowBigIntegerNumberAccess; this.allowAllImplementations = access.allowAllInterfaceImplementations; this.allowAllClassImplementations = access.allowAllClassImplementations; this.allowAccessInheritance = access.allowAccessInheritance; this.allowMutableTargetMappings = access.allowMutableTargetMappings; this.methodScopingDefault = access.methodScopingDefault; this.disableMethodScopingAnnotations = copySet(access.disableMethodScopingAnnotations, Equivalence.IDENTITY); this.disableMethodScoping = copySet(access.disableMethodScoping, Equivalence.IDENTITY); } /** * Allows access to public constructors, methods or fields of public classes that were * annotated by the given annotation class. * * @since 19.0 */ public Builder allowAccessAnnotatedBy(Class annotation) { Objects.requireNonNull(annotation); if (accessAnnotations == null) { accessAnnotations = EconomicSet.create(Equivalence.IDENTITY); } accessAnnotations.add(annotation); return this; } /** * Allows unrestricted access to all public constructors, methods or fields of public * classes. Note that this policy allows unrestricted access to reflection. It is highly * discouraged from using this option in environments where the guest application is not * fully trusted. * * @since 19.0 */ public Builder allowPublicAccess(boolean allow) { allowPublic = allow; return this; } /** * Allows access to a given constructor or method. Note that the method or constructor must * be public in order to have any effect. * * @since 19.0 */ public Builder allowAccess(Executable element) { Objects.requireNonNull(element); if (members == null) { members = EconomicSet.create(); } members.add(element); return this; } /** * Allows access to a given field. Note that the field must be public in order to have any * effect. * * @since 19.0 */ public Builder allowAccess(Field element) { Objects.requireNonNull(element); if (members == null) { members = EconomicSet.create(); } members.add(element); return this; } /** * Prevents access to members of given class and its subclasses. * * @param clazz the class to deny access to * @return this builder * @since 19.0 */ public Builder denyAccess(Class clazz) { return denyAccess(clazz, true); } /** * Prevents access to members of given class. * * @param clazz the class to deny access to * @param includeSubclasses should subclasses be excuded as well? * @return this builder * @since 19.0 */ public Builder denyAccess(Class clazz, boolean includeSubclasses) { Objects.requireNonNull(clazz); if (excludeTypes == null) { excludeTypes = EconomicMap.create(Equivalence.IDENTITY); } excludeTypes.put(clazz, includeSubclasses); return this; } /** * Allow guest languages to implement any Java interface. *

* Note that implementations implicitly export all their methods, i.e., allowing * implementations of a type implies allowing access its methods via its implementations, * regardless of whether the methods have been explicitly exported. * * @see HostAccess#ALL * @see #allowImplementations(Class) * @see #allowImplementationsAnnotatedBy(Class) * @since 19.0 */ public Builder allowAllImplementations(boolean allow) { this.allowAllImplementations = allow; return this; } /** * Allow guest languages to implement (extend) any Java class. Note that the default host * type mappings and {@link Value#as(Class)} only implement abstract classes. *

* Note that implementations implicitly export all their methods, i.e., allowing * implementations of a type implies allowing access its methods via its implementations, * regardless of whether the methods have been explicitly exported. * * @see HostAccess#ALL * @see #allowImplementations(Class) * @see #allowImplementationsAnnotatedBy(Class) * @see #allowAllImplementations(boolean) * @since 20.3.0 */ public Builder allowAllClassImplementations(boolean allow) { this.allowAllClassImplementations = allow; return this; } /** * Allow implementations of types annotated with the given annotation. For the * {@link HostAccess#EXPLICIT explicit} host access present the {@link Implementable} * annotation is configured for this purpose. Applies to interfaces and classes. *

* Note that implementations implicitly export all their methods, i.e., allowing * implementations of a type implies allowing access its methods via its implementations, * regardless of whether the methods have been explicitly exported. * * @see HostAccess.Implementable * @see #allowImplementations(Class) * @see Value#as(Class) * @since 19.0 */ public Builder allowImplementationsAnnotatedBy(Class annotation) { Objects.requireNonNull(annotation); if (implementationAnnotations == null) { implementationAnnotations = EconomicSet.create(Equivalence.IDENTITY); } implementationAnnotations.add(annotation); return this; } /** * Allow implementations of this type by the guest language. *

* Note that implementations implicitly export all their methods, i.e., allowing * implementations of a type implies allowing access its methods via its implementations, * regardless of whether the methods have been explicitly exported. * * @param type an interface that may be implemented or a class that may be extended. * @see #allowImplementationsAnnotatedBy(Class) * @see Value#as(Class) * @since 19.0 */ public Builder allowImplementations(Class type) { Objects.requireNonNull(type); if (implementableTypes == null) { implementableTypes = EconomicSet.create(Equivalence.IDENTITY); } implementableTypes.add(type); return this; } /** * Allows the guest application to access arrays as values with * {@link Value#hasArrayElements() array elements}. By default no array access is allowed. * * @see Value#hasArrayElements() * @since 19.0 */ public Builder allowArrayAccess(boolean arrayAccess) { this.allowArrayAccess = arrayAccess; return this; } /** * Allows the guest application to access lists as values with * {@link Value#hasArrayElements() array elements} and {@link Value#hasIterator() * iterators}. By default no array access is allowed. Allowing list access implies also * allowing of {@link #allowIterableAccess(boolean) iterables} and * {@link #allowIteratorAccess(boolean) iterators}. * * @see Value#hasArrayElements() * @see Value#hasIterator() * @since 19.0 */ public Builder allowListAccess(boolean listAccess) { this.allowListAccess = listAccess; return this; } /** * Allows the guest application to access {@link Iterable iterables} as values with * {@link Value#hasIterator() iterators}. By default no iterable access is allowed. Allowing * iterable access implies also allowing of {@link #allowIteratorAccess(boolean) iterators}. * * @see Value#hasIterator() * @since 21.1 */ public Builder allowIterableAccess(boolean iterableAccess) { this.allowIterableAccess = iterableAccess; return this; } /** * Allows the guest application to access {@link Iterator iterators} as * {@link Value#isIterator() iterator} values. By default no iterator access is allowed. * * @see Value#isIterator() * @see Value#hasIteratorNextElement() * @see Value#getIteratorNextElement() * @since 21.1 */ public Builder allowIteratorAccess(boolean iteratorAccess) { this.allowIteratorAccess = iteratorAccess; return this; } /** * Allows the guest application to access {@link Map map} as {@link Value#hasHashEntries() * hash} values. By default no map access is allowed. Allowing map access implies also * allowing of {@link #allowIteratorAccess(boolean) iterators}. * * @see Value#hasHashEntries() * @since 21.1 */ public Builder allowMapAccess(boolean mapAccess) { this.allowMapAccess = mapAccess; return this; } /** * Allows the guest application to access {@link java.math.BigInteger BigInteger} as a * {@link Value#isNumber() number}. By default BigInteger number access is allowed. * * @see Value#isNumber() * @since 23.0 */ public Builder allowBigIntegerNumberAccess(boolean bigIntegerNumberAccess) { this.allowBigIntegerNumberAccess = bigIntegerNumberAccess; return this; } /** * Allows the guest application to access {@link java.nio.ByteBuffer}s as values with * {@link Value#hasBufferElements() buffer elements}. By default no buffer access is * allowed. * * @see Value#hasBufferElements() * @since 21.1 */ public Builder allowBufferAccess(boolean bufferAccess) { this.allowBufferAccess = bufferAccess; return this; } /** * Allows the guest application to inherit access to allowed methods, i.e. implementations * of allowed abstract and interface methods and overrides of allowed concrete methods. * * If access inheritance is disabled, all method implementations need to be explicitly * allowed (either by {@linkplain #allowAccessAnnotatedBy(Class) an annotation} or * {@linkplain #allowAccess(Executable) using reflection}) to be available. Consequently, * attempting to allow abstract methods has no effect in this access mode. * * Rationale: * * Requiring explicit vetting of all method implementations prevents unintentional exporting * of newly added or overridden methods. * * When a user annotates an abstract (or concrete) method as exported, they might still know * what that means when they write the code and know of all its implementations; but they * might forget it later and other contributors to the codebase might not be aware of what * is exported through an interface or superclass. So when someone introduces a new * implementation or overrides the method in a subclass, perhaps a few levels down, they * could be accidentally exporting it to the guest application. * * @see #allowAccessAnnotatedBy(Class) * @see #allowAccess(Executable) * @since 22.2 */ public Builder allowAccessInheritance(boolean inheritAccess) { this.allowAccessInheritance = inheritAccess; return this; } /** * Allows host object mappings of mutable target types, such as {@link java.util.List}, * {@link java.util.Map}, {@link java.util.Iterator} and {@link java.lang.Iterable} based on * the {@link MutableTargetMapping}. * * Mapping guest objects to well-known host object types such as {@link java.util.Map} is * convenient on one hand. On the other hand it can lead to bugs as the guest code is free * to provide an arbitrary backing implementation of such objects, which may not behave as * expected of a {@link java.util.Map}. * * @since 23.0 */ public Builder allowMutableTargetMappings(MutableTargetMapping... mapping) { Objects.requireNonNull(mapping); this.allowMutableTargetMappings = mapping; return this; } /** * Adds a custom source to target type mapping for Java host calls, host field assignments * and {@link Value#as(Class) explicit value conversions}. Method is equivalent to calling * the targetTypeMapping method with precedence {@link TargetMappingPrecedence#HIGH}. * * @since 19.0 */ public Builder targetTypeMapping(Class sourceType, Class targetType, Predicate accepts, Function converter) { return targetTypeMapping(sourceType, targetType, accepts, converter, TargetMappingPrecedence.HIGH); } /** * Adds a custom source to target type mapping for Java host calls, host field assignments * and {@link Value#as(Class) explicit value conversions}. The source type specifies the * static source type for the conversion. The target type specifies the exact and static * target type of the mapping. Sub or base target types won't trigger the mapping. Custom * target type mappings always have precedence over default mappings specified in * {@link Value#as(Class)}, therefore allow to customize their behavior. The provided * converter takes a value of the source type and converts it to the target type. If the * mapping is only conditionally applicable then an accepts predicate may be specified. If * the mapping is applicable for all source values with the specified source type then a * null accepts predicate should be specified. The converter may throw a * {@link ClassCastException} if the mapping is not applicable. It is recommended to return * false in the accepts predicate if the mapping is not applicable instead of * throwing an exception. Implementing the accepts predicate instead of throwing an * exception also allows the implementation to perform better overload selection when a * method with multiple overloads is invoked. *

* All type mappings are applied recursively to generic types. A type mapping with the * target type String.class will also be applied to the elements of a * List mapping. This works for lists, maps, arrays and varargs * parameters. *

* The source type uses the semantics of {@link Value#as(Class)} to convert to the source * value. Custom type mappings are not applied there. If the source type is not applicable * to a value then the mapping will not be applied. For conversions that may accept any * value the {@link Value} should be used as source type. *

* Multiple mappings may be added for a source or target class. Multiple mappings are * applied in the order they were added, grouped by the {@link TargetMappingPrecedence * priority} where the highest priority group is applied first. See {@link Value#as(Class)} * for a detailed ordered list of the conversion order used. The first mapping that accepts * the source value will be used. If the {@link TargetMappingPrecedence#HIGH default * priority} is used then all custom target type mappings use the same precedence when an * overloaded method is selected. This means that if two methods with a custom target type * mapping are applicable for a set of arguments, an {@link IllegalArgumentException} is * thrown at runtime. Using a non-default priority for the mapping allows to configure * whether the method will be prioritized or deprioritized depending on the precedence. *

* For example take a configured target mapping from String to int * and two overloaded methods that takes an int or a {@link String} parameter. If this * method is invoked with a String value then there are three possible outcomes * depending on the precedence that was used for the custom mapping: *

    *
  • {@link TargetMappingPrecedence#HIGHEST}: The int method overload will be selected and * invoked as the target mapping has a higher precedence than default. *
  • {@link TargetMappingPrecedence#HIGH}: The execution fails with an error as all * overloads have equivalent precedence. *
  • {@link TargetMappingPrecedence#LOW} or {@link TargetMappingPrecedence#LOWEST}: The * String method overload will be selected and invoked as the target mapping has a lower * precedence than default. *
  • In this example the outcome of low and lowest are equivalent. There are differences * between low and lowest. See {@link TargetMappingPrecedence} for details. *
*

* Primitive boxed target types will be applied to the primitive and boxed values. It is * therefore enough to specify a target mapping to {@link Integer} to also map to the target * type int.class. Primitive target types can not be used as target types. They * throw an {@link IllegalArgumentException} if used. *

* If the converter function or the accepts predicate calls {@link Value#as(Class)} * recursively then custom target mappings are applied. Special care must be taken in order * to not trigger stack overflow errors. It is recommended to use a restricted source type * instead of {@link Value#as(Class)} where possible. It is strongly discouraged that accept * predicates or converter cause any side-effects or escape values for permanent storage. *

* * Usage example: * *

         * public static class MyClass {
         *
         *     @HostAccess.Export
         *     public void json(JsonObject c) {
         *     }
         *
         *     @HostAccess.Export
         *     public String intToString(String c) {
         *         return c;
         *     }
         * }
         *
         * public static class JsonObject {
         *     JsonObject(Value v) {
         *     }
         * }
         *
         * public static void main(String[] args) {
         *     HostAccess.Builder builder = HostAccess.newBuilder();
         *     builder.allowAccessAnnotatedBy(HostAccess.Export.class);
         *     builder.targetTypeMapping(Value.class, JsonObject.class,
         *                     (v) -> v.hasMembers() || v.hasArrayElements(),
         *                     (v) -> new JsonObject(v)).build();
         *
         *     builder.targetTypeMapping(Integer.class, String.class, null,
         *                     (v) -> v.toString());
         *
         *     HostAccess access = builder.build();
         *     try (Context c = Context.newBuilder().allowHostAccess(access).build()) {
         *         c.getBindings("js").putMember("javaObject", new MyClass());
         *         c.eval("js", "javaObject.json({})"); // works!
         *         c.eval("js", "javaObject.json([])"); // works!
         *         try {
         *             c.eval("js", "javaObject.json(42)"); // fails!
         *         } catch (PolyglotException e) {
         *         }
         *
         *         c.eval("js", "javaObject.intToString(42)"); // returns "42"
         *     }
         * }
         * 
* * @param sourceType the static source type to convert from with this mapping. The source * type must be applicable for a mapping to be accepted. * @param targetType the exact and static target type to convert to with this mapping. * @param accepts the predicate to check whether a mapping is applicable. Returns * true if the mapping is applicable else false. If set to * null then all values of a given source type are applicable. * @param converter a function that produces the converted value of the mapping. May return * null. May throw {@link ClassCastException} if the source value is * not convertible. * @param precedence the precedence of the defined mapping which influences conversion order * and precedence with default mappings and other target type mappings. * @throws IllegalArgumentException for primitive target types. * @since 20.3 */ public Builder targetTypeMapping(Class sourceType, Class targetType, Predicate accepts, Function converter, TargetMappingPrecedence precedence) { Objects.requireNonNull(sourceType); Objects.requireNonNull(targetType); Objects.requireNonNull(converter); Objects.requireNonNull(precedence); if (targetType.isPrimitive()) { throw new IllegalArgumentException("Primitive target type is not supported as target mapping. Use boxed primitives instead."); } if (targetMappings == null) { targetMappings = new ArrayList<>(); } targetMappings.add(Engine.getImpl().newTargetTypeMapping(sourceType, targetType, accepts, converter, precedence)); return this; } HostAccess.Builder name(String givenName) { this.name = givenName; return this; } /** * Sets the default scoping of callback function parameters. Parameters escape from the * scope of a function, if a reference to them is kept after the function returns. To use a * value beyond the method scope {@link Value#pin()} may be used. * * @see Value#pin() * @since 21.3 */ public Builder methodScoping(boolean scopingDefault) { methodScopingDefault = scopingDefault; return this; } /** * Function parameters of a method annotated with the specified annotation are not scoped. * * @since 21.3 */ public Builder disableMethodScopingAnnotatedBy(Class annotation) { Objects.requireNonNull(annotation); if (disableMethodScopingAnnotations == null) { disableMethodScopingAnnotations = EconomicSet.create(Equivalence.IDENTITY); } disableMethodScopingAnnotations.add(annotation); return this; } /** * Function parameters of the specified executable escape the executable's scope. * * @since 21.3 */ public Builder disableMethodScoping(Executable e) { Objects.requireNonNull(e); if (disableMethodScoping == null) { disableMethodScoping = EconomicSet.create(Equivalence.IDENTITY); } disableMethodScoping.add(e); return this; } /** * Creates an instance of the custom host access configuration. * * @since 19.0 */ public HostAccess build() { return new HostAccess(accessAnnotations, excludeTypes, members, implementationAnnotations, implementableTypes, targetMappings, name, allowPublic, allowAllImplementations, allowAllClassImplementations, allowArrayAccess, allowListAccess, allowBufferAccess, allowIterableAccess, allowIteratorAccess, allowMapAccess, allowBigIntegerNumberAccess, allowAccessInheritance, allowMutableTargetMappings, methodScopingDefault, disableMethodScopingAnnotations, disableMethodScoping); } } }