
io.reactiverse.es4x.ECMAEngine Maven / Gradle / Ivy
/*
* Copyright 2018 Red Hat, Inc.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.reactiverse.es4x;
import io.netty.buffer.Unpooled;
import io.reactiverse.es4x.impl.JSObjectMessageCodec;
import io.reactiverse.es4x.impl.SetContainer;
import io.reactiverse.es4x.jul.ANSIFormatter;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.impl.VertxInternal;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import org.graalvm.polyglot.*;
import org.graalvm.polyglot.io.FileSystem;
import org.graalvm.polyglot.proxy.Proxy;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.logging.ConsoleHandler;
import java.util.logging.Handler;
import java.util.regex.Pattern;
import static io.reactiverse.es4x.impl.AsyncError.parseStrackTraceElement;
public final class ECMAEngine {
private static Pattern[] allowedHostClassFilters() {
String hostClassFilter = System.getProperty("es4x.host.class.filter", System.getenv("ES4XHOSTCLASSFILTER"));
if (hostClassFilter == null || hostClassFilter.length() == 0) {
return null;
}
String[] glob = hostClassFilter.split(",");
Pattern[] patterns = new Pattern[glob.length];
for (int i = 0; i < patterns.length; i++) {
boolean negate = false;
String regex = glob[i];
if (glob[i].charAt(0) == '!') {
negate = true;
regex = glob[i].substring(1);
}
patterns[i] = Pattern.compile(
(negate ? "^(?!" : "") +
regex
// replace dots
.replace(".", "\\.")
// replace stars
.replace("*", "[^\\.]+")
// replace double stars
.replace("[^\\.]+[^\\.]+", ".*")
// replace ?
.replace("?", "\\w") +
(negate ? "$).*$" : "")
);
}
return patterns;
}
private final Vertx vertx;
private final Engine engine;
private final HostAccess hostAccess;
private final PolyglotAccess polyglotAccess;
private final Source objLookup;
private final Source arrLookup;
// lazy install the codec
private final AtomicBoolean codecInstalled = new AtomicBoolean(false);
public ECMAEngine(Vertx vertx) {
this.vertx = vertx;
final Handler logHandler = new ConsoleHandler();
// customize the formatter
logHandler.setFormatter(new ANSIFormatter());
// build it
this.engine = Engine.newBuilder()
.logHandler(logHandler)
.build();
if (!engine.getLanguages().containsKey("js")) {
throw new IllegalStateException("A language with id 'js' is not installed");
}
// enable or disable the polyglot access
polyglotAccess = Boolean.getBoolean("es4x.no-polyglot-access") ? PolyglotAccess.NONE : PolyglotAccess.ALL;
// source caches
objLookup = Source.newBuilder("js", "(function (fn) { fn({}); })", "")
.cached(true)
.internal(true)
.buildLiteral();
arrLookup = Source.newBuilder("js", "(function (fn) { fn([]); })", "")
.cached(true)
.internal(true)
.buildLiteral();
hostAccess = HostAccess.newBuilder(HostAccess.ALL)
/// Highest Precedence
/// accepts is null, so we can quickly assert the type
// Goal: [] -> Object
// When the target type is "exactly" and the source is a array like object (json array for
// example) then we want it to be a , otherwise the default behavior of graal is
.targetTypeMapping(
List.class,
Object.class,
Objects::nonNull,
v -> v,
HostAccess.TargetMappingPrecedence.HIGHEST)
// Goal: number -> Byte
// Graal already converts numeric types to byte, however most JS APIs assume bytes to be treated as unsigned
// values, which is not the default behaviour of the JVM. Here we override the default and "explicitly" declare
// that if the target is "java.lang.Byte" always assume the source to be "unsigned"
.targetTypeMapping(
Number.class,
Byte.class,
Objects::nonNull,
Number::byteValue,
HostAccess.TargetMappingPrecedence.HIGHEST)
/// High Precedence (default precedence)
/// Most usual types
// Goal: [...] -> JsonArray
// Convert array like object to and unwrap if the source is a proxy that extends
// the JsonArray class.
.targetTypeMapping(
Value.class,
JsonArray.class,
Value::hasArrayElements,
v -> {
if (v.isProxyObject()) {
final Proxy p = v.asProxyObject();
// special case, if the value is a proxy and JsonArray, just unwrap
if (p instanceof JsonArray) {
return (JsonArray) p;
}
}
return new JsonArray(v.as(List.class));
},
HostAccess.TargetMappingPrecedence.HIGH)
// Goal: Thenable -> Future
// This shall be heavily used. When the source object is a "Thenable" object and the target a
// wrap it to match the desired target.
.targetTypeMapping(
Map.class,
Future.class,
v -> v.get("then") instanceof Function,
v -> {
final Promise
© 2015 - 2025 Weber Informatics LLC | Privacy Policy