com.github.jlangch.venice.impl.javainterop.JavaInteropFunctions Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of venice Show documentation
Show all versions of venice Show documentation
Venice, a sandboxed Lisp implemented in Java.
/* __ __ _
* \ \ / /__ _ __ (_) ___ ___
* \ \/ / _ \ '_ \| |/ __/ _ \
* \ / __/ | | | | (_| __/
* \/ \___|_| |_|_|\___\___|
*
*
* Copyright 2017-2024 Venice
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.jlangch.venice.impl.javainterop;
import static com.github.jlangch.venice.impl.types.Constants.Nil;
import static com.github.jlangch.venice.impl.util.reflect.ReflectionAccessor.invokeInstanceMethod;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.stream.Collectors;
import com.github.jlangch.venice.VncException;
import com.github.jlangch.venice.impl.VeniceInterpreter;
import com.github.jlangch.venice.impl.functions.SystemFunctions;
import com.github.jlangch.venice.impl.namespaces.Namespaces;
import com.github.jlangch.venice.impl.types.Constants;
import com.github.jlangch.venice.impl.types.VncBoolean;
import com.github.jlangch.venice.impl.types.VncFunction;
import com.github.jlangch.venice.impl.types.VncJavaObject;
import com.github.jlangch.venice.impl.types.VncKeyword;
import com.github.jlangch.venice.impl.types.VncLong;
import com.github.jlangch.venice.impl.types.VncNumber;
import com.github.jlangch.venice.impl.types.VncString;
import com.github.jlangch.venice.impl.types.VncTunnelAsJavaObject;
import com.github.jlangch.venice.impl.types.VncVal;
import com.github.jlangch.venice.impl.types.collections.VncHashMap;
import com.github.jlangch.venice.impl.types.collections.VncList;
import com.github.jlangch.venice.impl.types.collections.VncMap;
import com.github.jlangch.venice.impl.types.collections.VncOrderedMap;
import com.github.jlangch.venice.impl.types.collections.VncSequence;
import com.github.jlangch.venice.impl.types.util.Coerce;
import com.github.jlangch.venice.impl.types.util.Types;
import com.github.jlangch.venice.impl.util.ArityExceptions;
import com.github.jlangch.venice.impl.util.StreamUtil;
import com.github.jlangch.venice.impl.util.SymbolMapBuilder;
import com.github.jlangch.venice.impl.util.callstack.CallFrame;
import com.github.jlangch.venice.impl.util.reflect.ReflectionUtil;
import com.github.jlangch.venice.javainterop.ReturnValue;
public class JavaInteropFunctions {
private static abstract class AbstractJavaFn extends VncFunction {
public AbstractJavaFn(final String name, final VncVal meta) {
super(name, meta);
}
@Override
public boolean isRedefinable() {
return false; // don't allow redefinition for security reasons
}
private static final long serialVersionUID = -1848883965231344442L;
}
public static class JavaFn extends AbstractJavaFn {
public JavaFn() {
super(
".",
VncFunction
.meta()
.arglists(
"(. classname :new args)",
"(. classname method-name args)",
"(. classname field-name)",
"(. classname :class)",
"(. object method-name args)",
"(. object field-name)",
"(. object :class)")
.doc(
"Java interop. Calls a constructor or an class/object method or accesses a " +
"class/instance field. The function is sandboxed.")
.examples(
";; invoke constructor \n(. :java.lang.Long :new 10)",
";; invoke static method \n(. :java.time.ZonedDateTime :now)",
";; invoke static method \n(. :java.lang.Math :min 10 20)",
";; access static field \n(. :java.lang.Math :PI)",
";; invoke method \n(. (. :java.lang.Long :new 10) :toString)",
";; get class name \n(. :java.lang.Math :class)",
";; get class name \n(. (. :java.io.File :new \"/temp\") :class)")
.seeAlso("import", "proxify", "java/as-runnable", "java/as-callable")
.build());
}
@Override
public VncVal apply(final VncList args) {
ArityExceptions.assertMinArity(this, args, 2);
sandboxFunctionCallValidation();
return JavaInteropUtil.applyJavaAccess(
args,
Namespaces.getCurrentNamespace().getJavaImports());
}
private static final long serialVersionUID = -1848883965231344442L;
}
public static class ProxifyFn extends AbstractJavaFn {
public ProxifyFn() {
super(
"proxify",
VncFunction
.meta()
.arglists("(proxify interface method-map)")
.doc(
"Proxifies a Java interface to be passed as a Callback object to " +
"Java functions. The interface's methods are implemented by Venice " +
"functions. \n\n" +
"The dynamic invocation handler takes care that the methods are " +
"called in the context of a Venice sandbox even if the Java method " +
"that invokes the callback methods is running in another thread.\n\n" +
"Supports default method implementations in the proxied Java interface. " +
"These Java interface methods can be either overriden by a Venice " +
"function or just be omitted. In the latter case the return value of " +
"methods default implementation will be handed back.\n\n" +
"In case a Java `FunctionalInterface` is required the proxy " +
"wrappers from the `:java` module are often simpler to use:\n\n" +
"* `java/as-runnable` \n" +
"* `java/as-callable` \n" +
"* `java/as-predicate` \n" +
"* `java/as-function` \n" +
"* `java/as-consumer` \n" +
"* `java/as-supplier` \n" +
"* `java/as-bipredicate` \n" +
"* `java/as-bifunction` \n" +
"* `java/as-biconsumer` \n" +
"* `java/as-binaryoperator` ")
.examples(
"(do \n" +
" (import :java.io.File :java.io.FilenameFilter) \n" +
" \n" +
" (def file-filter \n" +
" (fn [dir name] (str/ends-with? name \".xxx\"))) \n" +
" \n" +
" (let [dir (io/tmp-dir)] \n" +
" ;; create a dynamic proxy for the interface FilenameFilter \n" +
" ;; and implement its function 'accept' by 'file-filter' \n" +
" (. dir :list (proxify :FilenameFilter {:accept file-filter})))) ",
";; Instead of explicit proxies, functional interface wrappers are \n" +
";; often simpler to use \n" +
"(do \n" +
" (load-module :java) \n" +
" (import :java.util.stream.Collectors) \n" +
" \n" +
" (-> (. [1 2 3 4] :stream) \n" +
" (. :filter (java/as-predicate #(> % 2))) \n" +
" (. :map (java/as-function #(* % 10))) \n" +
" (. :collect (. :Collectors :toList)))) ")
.seeAlso(
"java/as-runnable",
"java/as-callable",
"java/as-predicate",
"java/as-function",
"java/as-consumer",
"java/as-supplier",
"java/as-bipredicate",
"java/as-bifunction",
"java/as-biconsumer",
"java/as-binaryoperator")
.build());
}
@Override
public VncVal apply(final VncList args) {
ArityExceptions.assertArity(this, args, 2);
sandboxFunctionCallValidation();
final Class> clazz = JavaInteropUtil.toClass(
args.first(),
Namespaces.getCurrentNamespace().getJavaImports());
return new VncJavaObject(
DynamicInvocationHandler.proxify(
new CallFrame("proxify(:" + clazz.getName() +")", args.getMeta()),
clazz,
Coerce.toVncMap(args.second())));
}
private static final long serialVersionUID = -1848883965231344442L;
}
public static class CastFn extends AbstractJavaFn {
public CastFn() {
super(
"cast",
VncFunction
.meta()
.arglists("(cast class object)")
.doc(
"Casts a Java object to a specific type\n\n" +
"Note: Casting a Java object will change the object's *formal type*. " +
"See the `formal-type` function for detailed information.")
.examples(
"(do \n" +
" (import :java.awt.Point) \n" +
" (import :java.awt.geom.Point2D) \n" +
" \n" +
" ;; upcasting :java.awt.Point to :java.awt.geom.Point2D \n" +
" ;; Point2D does not define the translate method! \n" +
" (let [p1 (. :Point :new 1.0 1.0) \n" +
" p2 (cast :Point2D p1)] \n" +
" (println \"p1 ->\" p1) \n" +
" (println \"p2 ->\" p2) \n" +
" (println \"Formal type p1 ->\" (formal-type p1)) \n" +
" (println \"Formal type p2 ->\" (formal-type p2)) \n" +
" (println \"p1' ->\" (doto p1 (. :translate 2.0 2.0))) \n" +
" ;; the translate method is not defined by Point2D \n" +
" ;; and will fail with a JavaMethodInvocationException! \n" +
" ;; (doto p2 (. :translate 2.0 2.0)) \n" +
"))")
.seeAlso("formal-type", "remove-formal-type", "class")
.build());
}
@Override
public VncVal apply(final VncList args) {
ArityExceptions.assertArity(this, args, 2);
if (args.second() == Constants.Nil) {
return Constants.Nil;
}
else if (Types.isVncJavaObject(args.second())) {
final Class> clazz = JavaInteropUtil.toClass(
args.first(),
Namespaces.getCurrentNamespace().getJavaImports());
return ((VncJavaObject)args.second()).castTo(clazz);
}
else {
throw new VncException(String.format(
"Function 'cast' does not allow casting a non Java object (%s)",
Types.getType(args.second())));
}
}
private static final long serialVersionUID = -1848883965231344442L;
}
public static class FormalTypeFn extends AbstractJavaFn {
public FormalTypeFn() {
super(
"formal-type",
VncFunction
.meta()
.arglists("(formal-type object)")
.doc(
"Returns the *formal type* of a Java object. " +
"\n\n" +
"The *formal type* of an object is defined as the explicit Java " +
"return type given by the function's definition. The " +
"*formal type* may differ from the real type of the returned " +
"Java object. A type cast will also change the object's formal type " +
"and set it to the cast type." +
"\n\n" +
"Venice must honor Java's static type system while interacting " +
"with Java objects. Therefore Venice adheres to *formal types* " +
"strictly when calling methods of Java objects. " +
"\n\n" +
"**Venice** \n" +
" \n" +
"``` \n" +
";; The Circle constructor returns an object of type Circle \n" +
"(let [c (. :Circle :new 1.5)] \n" +
" (. c :area) ;; OK Circle::area() \n" +
" (. c :radius)) ;; OK Circle::radius() \n" +
" \n" +
";; Builder::circle returns an object of the formal type Shape \n" +
"(let [c (. :Builder :circle 1.5)] \n" +
" (. c :area) ;; OK Shape::area() \n" +
" (. c :radius)) ;; FAIL Shape::radius(), undefined method \n" +
"``` \n" +
" \n" +
"**Java** \n" +
" \n" +
"``` \n" +
"public class Builder { \n" +
" public static Shape circle(double radius) { \n" +
" return new Circle(radius); \n" +
" } \n" +
"} \n" +
" \n" +
"public interface Shape { \n" +
" double area(); \n" +
"} \n" +
" \n" +
"public class Circle implements Shape { \n" +
" public Circle(double radius) {...} \n" +
" public double area() {...} \n" +
" public double radius() {...} \n" +
"} \n" +
"```")
.examples(
"(do \n" +
" (import :java.awt.Point) \n" +
" (import :java.awt.geom.Point2D) \n" +
" \n" +
" ;; upcasting :java.awt.Point to :java.awt.geom.Point2D \n" +
" ;; Point2D does not define the translate method! \n" +
" (let [p1 (. :Point :new 1.0 1.0) \n" +
" p2 (cast :Point2D p1)] \n" +
" (println \"p1 ->\" p1) \n" +
" (println \"p2 ->\" p2) \n" +
" (println \"Formal type p1 ->\" (formal-type p1)) \n" +
" (println \"Formal type p2 ->\" (formal-type p2)) \n" +
" (println \"p1' ->\" (doto p1 (. :translate 2.0 2.0))) \n" +
" ;; the translate method is not defined by Point2D \n" +
" ;; and will fail with a JavaMethodInvocationException! \n" +
" ;; (doto p2 (. :translate 2.0 2.0)) \n" +
"))")
.seeAlso("remove-formal-type", "cast", "class")
.build());
}
@Override
public VncVal apply(final VncList args) {
ArityExceptions.assertArity(this, args, 1);
if (Types.isVncJavaObject(args.first())) {
final VncJavaObject obj = (VncJavaObject)args.first();
return new VncKeyword(
obj.getDelegateFormalType() == null
? obj.getDelegate().getClass().getName()
: obj.getDelegateFormalType().getName());
}
else {
throw new VncException(String.format(
"Function 'formal-type' is not supported on non Java object (%s)",
Types.getType(args.first())));
}
}
private static final long serialVersionUID = -1848883965231344442L;
}
public static class RemoveFormalTypeFn extends AbstractJavaFn {
public RemoveFormalTypeFn() {
super(
"remove-formal-type",
VncFunction
.meta()
.arglists("(remove-formal-type object)")
.doc(
"Removes the *formal type* from a Java object.\n\n" +
"This is identical to casting an object back to its real type " +
"without knowing its real type.")
.examples(
"(do \n" +
" (let [p0 (. :java.awt.Point :new 0 0) \n" +
" p1 (cast :java.lang.Object p0) \n" +
" p2 (remove-formal-type p1)] \n" +
" (println \"p0 ->\" (formal-type p0)) \n" +
" (println \"p1 ->\" (formal-type p1)) \n" +
" (println \"p2 ->\" (formal-type p2)))) ")
.seeAlso("formal-type", "cast", "class")
.build());
}
@Override
public VncVal apply(final VncList args) {
ArityExceptions.assertArity(this, args, 1);
if (Types.isVncJavaObject(args.first())) {
final VncJavaObject obj = (VncJavaObject)args.first();
return new VncJavaObject(obj.getDelegate());
}
else {
throw new VncException(String.format(
"Function 'remove-formal-type' is not supported on non Java object (%s)",
Types.getType(args.first())));
}
}
private static final long serialVersionUID = -1848883965231344442L;
}
public static class JavaClassFn extends AbstractJavaFn {
public JavaClassFn() {
super(
"class",
VncFunction
.meta()
.arglists("(class name)")
.doc("Returns the Java class for the given name. Throws an exception if the class is not found.")
.examples("(class :java.util.ArrayList)")
.seeAlso("class-of", "class-name", "class-version",
"cast", "formal-type", "remove-formal-type")
.build());
}
@Override
public VncVal apply(final VncList args) {
ArityExceptions.assertArity(this, args, 1);
sandboxFunctionCallValidation();
return new VncJavaObject(
JavaInteropUtil.toClass(
args.first(),
Namespaces.getCurrentNamespace().getJavaImports()));
}
private static final long serialVersionUID = -1848883965231344442L;
}
public static class JavaClassOfFn extends AbstractJavaFn {
public JavaClassOfFn() {
super(
"class-of",
VncFunction
.meta()
.arglists("(class-of x)")
.doc("Returns the Java class of a value.")
.examples(
"(class-of 100)",
"(class-of (. :java.awt.Point :new 10 10))")
.seeAlso("class", "class-name", "class-version")
.build());
}
@Override
public VncVal apply(final VncList args) {
ArityExceptions.assertArity(this, args, 1);
sandboxFunctionCallValidation();
if (Types.isVncJavaObject(args.first())) {
final Object obj = ((VncJavaObject)args.first()).getDelegate();
return new VncJavaObject(obj.getClass());
}
else {
return new VncJavaObject(args.first().getClass());
}
}
private static final long serialVersionUID = -1848883965231344442L;
}
public static class JavaClassNameFn extends AbstractJavaFn {
public JavaClassNameFn() {
super(
"class-name",
VncFunction
.meta()
.arglists("(class-name class)")
.doc("Returns the Java class name of a class.")
.examples("(class-name (class :java.util.ArrayList))")
.seeAlso("class", "class-of", "class-version")
.build());
}
@Override
public VncVal apply(final VncList args) {
ArityExceptions.assertArity(this, args, 1);
sandboxFunctionCallValidation();
if (Types.isVncJavaObject(args.first(), Class.class)) {
final Class> clazz = (Class>)((VncJavaObject)args.first()).getDelegate();
return new VncString(clazz.getName());
}
else {
throw new VncException(String.format(
"Function 'class-name' requires a Java class as argument. Got a '%s'.",
Types.getType(args.first())));
}
}
private static final long serialVersionUID = -1848883965231344442L;
}
public static class JavaModuleNameFn extends AbstractJavaFn {
public JavaModuleNameFn() {
super(
"module-name",
VncFunction
.meta()
.arglists("(module-name class)")
.doc("Returns the Java module name of a class.")
.examples("(module-name (class :java.util.ArrayList))")
.seeAlso("class", "class-name")
.build());
}
@Override
public VncVal apply(final VncList args) {
ArityExceptions.assertArity(this, args, 1);
sandboxFunctionCallValidation();
final long javaMajor = SystemFunctions.javaMajorVersion();
if (javaMajor < 11) {
throw new VncException(
"The function 'module-name' is available for Java 11+ only");
}
else {
final Class> clazz = JavaInteropUtil.toClass(
args.first(),
Namespaces.getCurrentNamespace().getJavaImports());
ReturnValue ret = invokeInstanceMethod(clazz, null, "getModule", new Object[]{});
ret = invokeInstanceMethod(ret.getValue(), null, "getName", new Object[]{});
return new VncString((String)ret.getValue());
}
}
private static final long serialVersionUID = -1848883965231344442L;
}
public static class JavaClassVersionFn extends AbstractJavaFn {
public JavaClassVersionFn() {
super(
"class-version",
VncFunction
.meta()
.arglists("(class-version class)")
.doc(
"Returns the major version of a Java class.\n\n" +
"Java major versions:¶\n" +
" - Java 8 uses major version 52¶\n" +
" - Java 9 uses major version 53¶\n" +
" - Java 10 uses major version 54¶\n" +
" - Java 11 uses major version 55¶\n" +
" - Java 12 uses major version 56¶\n" +
" - Java 13 uses major version 57¶\n" +
" - Java 14 uses major version 58¶\n" +
" - Java 15 uses major version 59")
.examples("(class-version :com.github.jlangch.venice.Venice)")
.seeAlso("class", "class-of", "class-name")
.build());
}
@Override
public VncVal apply(final VncList args) {
ArityExceptions.assertArity(this, args, 1);
sandboxFunctionCallValidation();
final VncKeyword cl = Coerce.toVncKeyword(args.first());
final String name = cl.getValue().replace(".", "/") + ".class";
return new VncLong(ClassVersionChecker.getClassResourceMajorVersion(name));
}
private static final long serialVersionUID = -1848883965231344442L;
}
public static class JavaJarMavenManifestVersionFn extends AbstractJavaFn {
public JavaJarMavenManifestVersionFn() {
super(
"jar-maven-manifest-version",
VncFunction
.meta()
.arglists("(jar-maven-manifest-version group-id artefact-id)")
.doc(
"Returns the Maven version for a loaded JAR's manifest or " +
"nil if there is no Maven manifest.\n\n" +
"Reads the version from the JAR's Maven 'pom.properties' file at:¶\n" +
" /META-INF/maven/{group-id}/{artefact-id}/pom.properties\n\n" +
"A 'pom.properties' may look like:¶\n" +
" - artifactId=xchart¶\n" +
" - groupId=org.knowm.xchart¶\n" +
" - version=3.8.0\n")
.examples("(jar-maven-manifest-version :com.github.librepdf :openpdf)")
.seeAlso("java-package-version")
.build());
}
@Override
public VncVal apply(final VncList args) {
ArityExceptions.assertArity(this, args, 2);
sandboxFunctionCallValidation();
final VncKeyword groupID = Coerce.toVncKeyword(args.first());
final VncKeyword artefactID = Coerce.toVncKeyword(args.second());
try {
final String resource = String.format(
"/META-INF/maven/%s/%s/pom.properties",
groupID.getValue(),
artefactID.getValue());
final InputStream is = getClass().getResourceAsStream(resource);
if (is == null) {
return Constants.Nil;
}
else {
final Properties props = new Properties();
props.load(is);
return new VncString(props.getProperty("version"));
}
}
catch(Exception ex) {
return Constants.Nil;
}
}
private static final long serialVersionUID = -1848883965231344442L;
}
public static class JavaPackageVersionFn extends AbstractJavaFn {
public JavaPackageVersionFn() {
super(
"java-package-version",
VncFunction
.meta()
.arglists("(java-package-version class)")
.doc(
"Returns version information for a Java package or " +
"nil if the package does not exist or is not visible.")
.examples(
"(java-package-version :java.lang.String)",
"(java-package-version (class :java.lang.String))")
.seeAlso("jar-maven-manifest-version", "class")
.build());
}
@Override
public VncVal apply(final VncList args) {
ArityExceptions.assertArity(this, args, 1);
sandboxFunctionCallValidation();
try {
final Class> clazz = JavaInteropUtil.toClass(
args.first(),
Namespaces.getCurrentNamespace().getJavaImports());
final Package pkg = clazz.getPackage();
VncMap map = new VncOrderedMap();
if (pkg.getImplementationTitle() != null) {
map = map.assoc(new VncKeyword("implementation-title"), new VncString(pkg.getImplementationTitle()));
}
if (pkg.getImplementationTitle() != null) {
map = map.assoc(new VncKeyword("implementation-vendor"), new VncString(pkg.getImplementationVendor()));
}
if (pkg.getImplementationTitle() != null) {
map = map.assoc(new VncKeyword("implementation-version"), new VncString(pkg.getImplementationVersion()));
}
if (pkg.getImplementationTitle() != null) {
map = map.assoc(new VncKeyword("specification-title"), new VncString(pkg.getSpecificationTitle()));
}
if (pkg.getImplementationTitle() != null) {
map = map.assoc(new VncKeyword("specification-vendor"), new VncString(pkg.getSpecificationVendor()));
}
if (pkg.getImplementationTitle() != null) {
map = map.assoc(new VncKeyword("specification-version"), new VncString(pkg.getSpecificationVersion()));
}
return map;
}
catch(Exception ex) {
return Constants.Nil;
}
}
private static final long serialVersionUID = -1848883965231344442L;
}
public static class JavaClassLoaderFn extends AbstractJavaFn {
public JavaClassLoaderFn() {
super(
"classloader",
VncFunction
.meta()
.arglists(
"(classloader)",
"(classloader type)")
.doc(
"Returns the classloader.")
.examples(
";; Returns the current classloader\n" +
"(classloader)",
";; Returns the system classloader\n" +
"(classloader :system)",
";; Returns the classloader which loaded the Venice classes\n" +
"(classloader :application)",
";; Returns the thread-context classloader\n" +
"(classloader :thread-context)")
.seeAlso("class", "classloader-of")
.build());
}
@Override
public VncVal apply(final VncList args) {
ArityExceptions.assertArity(this, args, 0, 1);
sandboxFunctionCallValidation();
if (args.size() == 0) {
// current classloader
final ClassLoader cl = Thread.currentThread().getContextClassLoader();
return new VncJavaObject(cl != null ? cl.getClass()
: VeniceInterpreter.class.getClassLoader());
}
else {
if (Types.isVncKeyword(args.first())) {
final VncKeyword type = (VncKeyword)args.first();
if ("system".equals(type.getValue())) {
return new VncJavaObject(ClassLoader.getSystemClassLoader());
}
else if ("application".equals(type.getValue())) {
return new VncJavaObject(VeniceInterpreter.class.getClassLoader());
}
else if ("thread-context".equals(type.getValue())) {
final ClassLoader cl = Thread.currentThread().getContextClassLoader();
return cl == null ? Constants.Nil : new VncJavaObject(cl);
}
}
throw new VncException("Function 'classloader' unknown argument");
}
}
private static final long serialVersionUID = -1848883965231344442L;
}
public static class JavaClassLoaderOfFn extends AbstractJavaFn {
public JavaClassLoaderOfFn() {
super(
"classloader-of",
VncFunction
.meta()
.arglists(
"(classloader-of x)")
.doc(
"Returns the classloader of a value or a Java class. \n\n" +
"Note:¶\n" +
"Some Java VM implementations may use 'null' to represent " +
"the bootstrap class loader. This method will return 'nil' " +
"in such implementations if this class was loaded by the " +
"bootstrap class loader.")
.examples(
"(classloader-of (class :java.awt.Point))",
"(classloader-of (. :java.awt.Point :new 10 10))",
"(classloader-of (class-of \"abcdef\"))",
"(classloader-of \"abcdef\")")
.seeAlso("class", "classloader")
.build());
}
@Override
public VncVal apply(final VncList args) {
ArityExceptions.assertArity(this, args, 1);
sandboxFunctionCallValidation();
if (Types.isVncJavaObject(args.first(), Class.class)) {
final Object obj = ((VncJavaObject)args.first()).getDelegate();
final ClassLoader cl = ((Class>)obj).getClassLoader();
return cl == null ? Constants.Nil : new VncJavaObject(cl);
}
else if (Types.isVncJavaObject(args.first())) {
final Object obj = ((VncJavaObject)args.first()).getDelegate();
final ClassLoader cl = obj.getClass().getClassLoader();
return cl == null ? Constants.Nil : new VncJavaObject(cl);
}
else {
final ClassLoader cl = args.first().getClass().getClassLoader();
return cl == null ? Constants.Nil : new VncJavaObject(cl);
}
}
private static final long serialVersionUID = -1848883965231344442L;
}
public static class JavaExStacktraceFn extends AbstractJavaFn {
public JavaExStacktraceFn() {
super(
"stacktrace",
VncFunction
.meta()
.arglists("(stacktrace ex)")
.doc("Returns the stacktrace of a java exception")
.examples("(println (stacktrace (. :VncException :new (str \"test\"))))")
.build());
}
@Override
public VncVal apply(final VncList args) {
ArityExceptions.assertArity(this, args, 1);
sandboxFunctionCallValidation();
if (Types.isVncJavaObject(args.first())) {
final VncJavaObject obj = (VncJavaObject)args.first();
if (obj.getDelegate() instanceof Exception) {
final Exception ex = (Exception)obj.getDelegate();
final StringWriter wr = new StringWriter();
ex.printStackTrace(new PrintWriter(wr));
return new VncString(wr.getBuffer().toString());
}
else {
throw new VncException(String.format(
"Function 'stacktrace' accepts only Java objects holding "
+ "objects of type :java.lang.Exception type. Got a %s.",
new VncKeyword(obj.getDelegate().getClass().getName())));
}
}
else {
throw new VncException(String.format(
"Function 'stacktrace' requires a Java exception object. Got a %s.",
Types.getType(args.second())));
}
}
private static final long serialVersionUID = -1848883965231344442L;
}
public static class JavaExistsClassQFn extends AbstractJavaFn {
public JavaExistsClassQFn() {
super(
"exists-class?",
VncFunction
.meta()
.arglists("(exists-class? name)")
.doc("Returns true the Java class for the given name exists otherwise returns false.")
.examples("(exists-class? :java.util.ArrayList)")
.build());
}
@Override
public VncVal apply(final VncList args) {
ArityExceptions.assertArity(this, args, 1);
sandboxFunctionCallValidation();
try {
JavaInteropUtil.toClass(
args.first(),
Namespaces.getCurrentNamespace().getJavaImports());
return VncBoolean.True;
}
catch(Exception ex) {
return VncBoolean.False;
}
}
private static final long serialVersionUID = -1848883965231344442L;
}
public static class SupersFn extends AbstractJavaFn {
public SupersFn() {
super(
"supers",
VncFunction
.meta()
.arglists("(supers class)")
.doc("Returns the immediate and indirect superclasses and interfaces of class, if any.")
.examples("(supers :java.util.ArrayList)")
.build());
}
@Override
public VncVal apply(final VncList args) {
ArityExceptions.assertArity(this, args, 1);
sandboxFunctionCallValidation();
final Class> clazz = JavaInteropUtil.toClass(
args.first(),
Namespaces.getCurrentNamespace().getJavaImports());
final List> classes = new ArrayList<>();
final List> superclasses = ReflectionUtil.getAllSuperclasses(clazz);
final List> interfaces = ReflectionUtil.getAllInterfaces(superclasses);
classes.addAll(superclasses);
classes.addAll(interfaces);
return VncList.ofList(JavaInteropUtil.toVncKeywords(ReflectionUtil.distinct(classes)));
}
private static final long serialVersionUID = -1848883965231344442L;
}
public static class BasesFn extends AbstractJavaFn {
public BasesFn() {
super(
"bases",
VncFunction
.meta()
.arglists("(bases class)")
.doc("Returns the immediate superclass and interfaces of class, if any.")
.examples("(bases :java.util.ArrayList)")
.build());
}
@Override
public VncVal apply(final VncList args) {
ArityExceptions.assertArity(this, args, 1);
sandboxFunctionCallValidation();
final Class> clazz = JavaInteropUtil.toClass(
args.first(),
Namespaces.getCurrentNamespace().getJavaImports());
final List> classes = new ArrayList<>();
final Class> superclass = ReflectionUtil.getSuperclass(clazz);
if (superclass != null) {
classes.add(superclass);
}
classes.addAll(ReflectionUtil.getAllDirectInterfaces(clazz));
return VncList.ofList(JavaInteropUtil.toVncKeywords(ReflectionUtil.distinct(classes)));
}
private static final long serialVersionUID = -1848883965231344442L;
}
public static class DescribeJavaClassFn extends AbstractJavaFn {
public DescribeJavaClassFn() {
super(
"describe-class",
VncFunction
.meta()
.arglists("(describe-class class)")
.doc("Describes a Java class.")
.examples("(describe-class :java.util.ArrayList)")
.build());
}
@Override
public VncVal apply(final VncList args) {
ArityExceptions.assertArity(this, args, 1);
sandboxFunctionCallValidation();
final Class> clazz = JavaInteropUtil.toClass(
args.first(),
Namespaces.getCurrentNamespace().getJavaImports());
VncHashMap map = new VncHashMap();
map = map.assoc(
new VncKeyword("constructors"),
VncList.ofList(
ReflectionUtil
.getPublicConstructors(clazz)
.stream()
.map(c -> mapConstructor(c))
.collect(Collectors.toList())));
map = map.assoc(
new VncKeyword("methods"),
VncList.empty()
.addAllAtEnd(
VncList.ofList(
ReflectionUtil
.getAllPublicInstanceMethods(clazz, true)
.stream()
.filter(m -> !skippedFn.contains(m.getName()))
.map(m -> mapMethod(m))
.collect(Collectors.toList())))
.addAllAtEnd(
VncList.ofList(
ReflectionUtil
.getAllPublicStaticMethods(clazz, true)
.stream()
.filter(m -> !skippedFn.contains(m.getName()))
.map(m -> mapMethod(m))
.collect(Collectors.toList()))));
map = map.assoc(
new VncKeyword("fields"),
VncList.empty()
.addAllAtEnd(
VncList.ofList(
ReflectionUtil
.getPublicInstanceFields(clazz)
.stream()
.map(m -> mapField(m))
.collect(Collectors.toList())))
.addAllAtEnd(
VncList.ofList(
ReflectionUtil
.getPublicStaticFields(clazz)
.stream()
.map(m -> mapField(m))
.collect(Collectors.toList()))));
map = map.assoc(
new VncKeyword("bean"),
VncList.empty()
.addAllAtEnd(
VncList.ofList(
ReflectionUtil
.getBeanGetterMethods(clazz)
.stream()
.map(m -> mapBeanGetter(m))
.collect(Collectors.toList())))
.addAllAtEnd(
VncList.ofList(
ReflectionUtil
.getBeanSetterMethods(clazz)
.stream()
.map(m -> mapBeanSetter(m))
.collect(Collectors.toList()))));
return map;
}
private static final long serialVersionUID = -1848883965231344442L;
}
public static class JavaObjQFn extends AbstractJavaFn {
public JavaObjQFn() {
super(
"java-obj?",
VncFunction
.meta()
.arglists("(java-obj? obj)")
.doc("Returns true if obj is a Java object")
.examples("(java-obj? (. :java.math.BigInteger :new \"0\"))")
.build());
}
@Override
public VncVal apply(final VncList args) {
ArityExceptions.assertArity(this, args, 1);
sandboxFunctionCallValidation();
return VncBoolean.of(Types.isVncJavaObject(args.first()));
}
private static final long serialVersionUID = -1848883965231344442L;
}
public static class JavaEnumQFn extends AbstractJavaFn {
public JavaEnumQFn() {
super(
"enum?",
VncFunction
.meta()
.arglists("(enum? class)")
.doc(
"Returns true if class is a Java *enum*.\n" +
"\n" +
"Get all values of a Java *enum*: \n\n" +
"``` \n" +
"(. :java.time.Month :values) \n" +
"``` \n\n" +
"Get a Java *enum* value: \n\n" +
"``` \n" +
"(let [jan (. :java.time.Month :JANUARY)] \n" +
" (. :java.time.LocalDate :of 1994 jan 21)) \n" +
"``` \n\n" +
"This can be simplified to: \n\n" +
"``` \n" +
"(. :java.time.LocalDate :of 1994 :JANUARY 21) \n" +
"``` \n")
.examples("(enum? :java.time.Month)")
.build());
}
@Override
public VncVal apply(final VncList args) {
ArityExceptions.assertArity(this, args, 1);
sandboxFunctionCallValidation();
try {
final Class> clazz = JavaInteropUtil.toClass(
args.first(),
Namespaces.getCurrentNamespace().getJavaImports());
return VncBoolean.of(clazz.isEnum());
}
catch(Exception ex) {
return VncBoolean.False;
}
}
private static final long serialVersionUID = -1848883965231344442L;
}
public static class JavaEnumerationToListFn extends AbstractJavaFn {
public JavaEnumerationToListFn() {
super(
"java-enumeration-to-list",
VncFunction
.meta()
.arglists("(java-enumeration-to-list e)")
.doc("Converts a Java enumeration to a list")
.build());
}
@Override
public VncVal apply(final VncList args) {
ArityExceptions.assertArity(this, args, 1);
sandboxFunctionCallValidation();
if (Types.isVncJavaObject(args.first(), Enumeration.class)) {
final Enumeration> e = (Enumeration>)Coerce.toVncJavaObject(args.first()).getDelegate();
final List list = StreamUtil
.stream(e)
.map(v -> JavaInteropUtil.convertToVncVal(v))
.collect(Collectors.toList());
return VncList.ofList(list);
}
else {
throw new VncException(String.format(
"Function 'java-enumeration-to-list' does not allow %s as parameter",
Types.getType(args.first())));
}
}
private static final long serialVersionUID = -1848883965231344442L;
};
public static class JavaIterToListFn extends AbstractJavaFn {
public JavaIterToListFn() {
super(
"java-iterator-to-list",
VncFunction
.meta()
.arglists("(java-iterator-to-list e)")
.doc("Converts a Java iterator to a list")
.build());
}
@Override
public VncVal apply(final VncList args) {
ArityExceptions.assertArity(this, args, 1);
sandboxFunctionCallValidation();
if (Types.isVncJavaObject(args.first(), Iterator.class)) {
final Iterator> i = (Iterator>)Coerce.toVncJavaObject(args.first()).getDelegate();
final List list = StreamUtil
.stream(i)
.map(v -> JavaInteropUtil.convertToVncVal(v))
.collect(Collectors.toList());
return VncList.ofList(list);
}
else {
throw new VncException(String.format(
"Function 'java-iterator-to-list' does not allow %s as parameter",
Types.getType(args.first())));
}
}
private static final long serialVersionUID = -1848883965231344442L;
};
public static class JavaStringListFn extends AbstractJavaFn {
public JavaStringListFn() {
super(
"java-string-list",
VncFunction
.meta()
.arglists("(java-string-list l)")
.doc("Converts a Venice list/vector to a Java `String` list")
.examples(
"(java-string-list '(\"ab\" \"cd\" \"ef\"))",
"(java-string-list '(\"ab\" 1I 2 3.2 3.56M))")
.seeAlso("string-array")
.build());
}
@Override
public VncVal apply(final VncList args) {
ArityExceptions.assertArity(this, args, 1);
sandboxFunctionCallValidation();
if (Types.isVncSequence(args.first())) {
final VncSequence seq = Coerce.toVncSequence(args.first());
final ArrayList list = new ArrayList<>();
for(VncVal v : seq) {
list.add(v == Nil ? null : v.toString());
}
return new VncJavaObject(list);
}
else {
throw new VncException(String.format(
"Function 'java-string-list' does not allow %s as parameter",
Types.getType(args.first())));
}
}
private static final long serialVersionUID = -1848883965231344442L;
};
public static class JavaIntListFn extends AbstractJavaFn {
public JavaIntListFn() {
super(
"java-int-list",
VncFunction
.meta()
.arglists("(java-int-list l)")
.doc("Converts a Venice list/vector to a Java `Integer` list")
.examples(
"(java-int-list '(1I 2I 3I))",
"(java-int-list '(1I 2 3.2 3.56M))")
.seeAlso("int-array")
.build());
}
@Override
public VncVal apply(final VncList args) {
ArityExceptions.assertArity(this, args, 1);
sandboxFunctionCallValidation();
if (Types.isVncSequence(args.first())) {
final VncSequence seq = Coerce.toVncSequence(args.first());
final ArrayList list = new ArrayList<>();
int ii=0;
for(VncVal v : seq) {
if (v == Nil) {
list.add(null);
}
else if (!Types.isVncNumber(v)) {
throw new VncException(String.format(
"The value at pos %d in the collection is not a number",
ii));
}
else {
list.add(((VncNumber)v).toJavaInteger());
}
}
return new VncJavaObject(list);
}
else {
throw new VncException(String.format(
"Function 'java-int-list' does not allow %s as parameter",
Types.getType(args.first())));
}
}
private static final long serialVersionUID = -1848883965231344442L;
};
public static class JavaLongListFn extends AbstractJavaFn {
public JavaLongListFn() {
super(
"java-long-list",
VncFunction
.meta()
.arglists("(java-long-list l)")
.doc("Converts a Venice list/vector to a Java `Long` list")
.examples(
"(java-long-list '(1 2 3))",
"(java-long-list '(1I 2 3.2 3.56M))")
.seeAlso("long-array")
.build());
}
@Override
public VncVal apply(final VncList args) {
ArityExceptions.assertArity(this, args, 1);
sandboxFunctionCallValidation();
if (Types.isVncSequence(args.first())) {
final VncSequence seq = Coerce.toVncSequence(args.first());
final ArrayList list = new ArrayList<>();
int ii=0;
for(VncVal v : seq) {
if (v == Nil) {
list.add(null);
}
else if (!Types.isVncNumber(v)) {
throw new VncException(String.format(
"The value at pos %d in the collection is not a number",
ii));
}
else {
list.add(((VncNumber)v).toJavaLong());
}
}
return new VncJavaObject(list);
}
else {
throw new VncException(String.format(
"Function 'java-long-list' does not allow %s as parameter",
Types.getType(args.first())));
}
}
private static final long serialVersionUID = -1848883965231344442L;
};
public static class JavaFloatListFn extends AbstractJavaFn {
public JavaFloatListFn() {
super(
"java-float-list",
VncFunction
.meta()
.arglists("(java-float-list l)")
.doc("Converts a Venice list/vector to a Java `Float` list")
.examples(
"(java-float-list '(1.0F 2.0F 3.0F))",
"(java-float-list '(1I 2 3.2 3.56M))")
.seeAlso("java-double-list","float-array")
.build());
}
@Override
public VncVal apply(final VncList args) {
ArityExceptions.assertArity(this, args, 1);
sandboxFunctionCallValidation();
if (Types.isVncSequence(args.first())) {
final VncSequence seq = Coerce.toVncSequence(args.first());
final ArrayList list = new ArrayList<>();
int ii=0;
for(VncVal v : seq) {
if (v == Nil) {
list.add(null);
}
else if (!Types.isVncNumber(v)) {
throw new VncException(String.format(
"The value at pos %d in the collection is not a number",
ii));
}
else {
list.add(((VncNumber)v).toJavaFloat());
}
}
return new VncJavaObject(list);
}
else {
throw new VncException(String.format(
"Function 'java-float-list' does not allow %s as parameter",
Types.getType(args.first())));
}
}
private static final long serialVersionUID = -1848883965231344442L;
};
public static class JavaDoubleListFn extends AbstractJavaFn {
public JavaDoubleListFn() {
super(
"java-double-list",
VncFunction
.meta()
.arglists("(java-double-list l)")
.doc("Converts a Venice list/vector to a Java `Double` list")
.examples(
"(java-double-list '(1.0 2.0 3.0))",
"(java-double-list '(1I 2 3.2 3.56M))")
.seeAlso("java-float-list", "double-array")
.build());
}
@Override
public VncVal apply(final VncList args) {
ArityExceptions.assertArity(this, args, 1);
sandboxFunctionCallValidation();
if (Types.isVncSequence(args.first())) {
final VncSequence seq = Coerce.toVncSequence(args.first());
final ArrayList list = new ArrayList<>();
int ii=0;
for(VncVal v : seq) {
if (v == Nil) {
list.add(null);
}
else if (!Types.isVncNumber(v)) {
throw new VncException(String.format(
"The value at pos %d in the collection is not a number",
ii));
}
else {
list.add(((VncNumber)v).toJavaDouble());
}
}
return new VncJavaObject(list);
}
else {
throw new VncException(String.format(
"Function 'java-double-list' does not allow %s as parameter",
Types.getType(args.first())));
}
}
private static final long serialVersionUID = -1848883965231344442L;
};
public static class JavaObjWrapFn extends AbstractJavaFn {
public JavaObjWrapFn() {
super(
"java-wrap",
VncFunction
.meta()
.arglists("(java-wrap val)")
.doc("Wraps a venice value")
.build());
}
@Override
public VncVal apply(final VncList args) {
ArityExceptions.assertArity(this, args, 1);
sandboxFunctionCallValidation();
final VncVal arg = args.first();
return arg instanceof VncTunnelAsJavaObject
? arg
: new VncTunnelAsJavaObject(arg);
}
private static final long serialVersionUID = -1848883965231344442L;
}
public static class JavaObjUnwrapFn extends AbstractJavaFn {
public JavaObjUnwrapFn() {
super(
"java-unwrap",
VncFunction
.meta()
.arglists("(java-unwrap val)")
.doc("Unwraps a venice value")
.build());
}
@Override
public VncVal apply(final VncList args) {
ArityExceptions.assertArity(this, args, 1);
sandboxFunctionCallValidation();
final VncVal arg = args.first();
return arg instanceof VncTunnelAsJavaObject
? ((VncTunnelAsJavaObject)arg).getDelegate()
: arg;
}
private static final long serialVersionUID = -1848883965231344442L;
}
public static class JavaUnwrapOptionalFn extends AbstractJavaFn {
public JavaUnwrapOptionalFn() {
super(
"java-unwrap-optional",
VncFunction
.meta()
.arglists("(java-unwrap-optional val)")
.doc("Unwraps a Java :java.util.Optional to its contained value or nil")
.build());
}
@Override
@SuppressWarnings("unchecked")
public VncVal apply(final VncList args) {
ArityExceptions.assertArity(this, args, 1);
sandboxFunctionCallValidation();
if (Types.isVncJavaObject(args.first(), java.util.Optional.class)) {
final VncJavaObject obj = (VncJavaObject)args.first();
final Optional