Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.pkl.thirdparty.graalvm.polyglot.HostAccess Maven / Gradle / Ivy
Go to download
Shaded fat Jar for pkl-config-java, a Java config library based on the Pkl config language.
/*
* 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 extends Annotation> 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 extends Annotation> 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 extends Annotation> 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 extends Annotation> 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 extends Annotation> 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 extends Annotation> 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 extends Annotation> 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);
}
}
}