Run Clojure REPL from java, providing custom context - clojure

How can I start a Clojure REPL from my java application and provide some "predefined variables" (I´m a Clojure newbie, I guess there´s a better term for that)? In fact I already tried to make it work by coincidence... I started with clojure.main and added an additional call to RT.var(). Is this the correct way to do it?
import clojure.lang.Symbol;
import clojure.lang.Var;
import clojure.lang.RT;
public class MyClojure {
final static private Symbol CLOJURE_MAIN = Symbol.intern("clojure.main");
final static private Var REQUIRE = RT.var("clojure.core", "require");
final static private Var MAIN = RT.var("clojure.main", "main");
// Provide some context, this is my custimisation...
#SuppressWarnings("unused")
final static private Var Test = RT.var("testme", "myvar", "This is the initial value");
public static void main(String[] args) throws Exception {
REQUIRE.invoke(CLOJURE_MAIN);
MAIN.applyTo(RT.seq(args));
}
}
EDIT: My application is not a web application. It´s a standalone desktop application that basically displays/edits a pojo data model. I now want to add support to expose the data model from the running application to a clojure repl. I would then be able to modify/inspect the data model thrugh both, the original application and the repl.

you're looking for the clojure.contrib/server-socket's create-repl-server function:
here is an excerpt from the examples:
(use '[clojure.contrib.server-socket :only [create-repl-server]])
(gen-class
:name "org.project.ReplServer"
:implements [javax.servlet.ServletContextListener])
(defn -contextInitialized [this e]
(.start (Thread. (partial create-repl-server 11111))))
there are also a lot of ideas on this SO question

Related

Problems calling a clojure function that takes a map as parameter from java

I'm using maven with several modules, one in java, another in clojure. I'm calling a clojure function from java and want to pass in a HashMap as a parameter and return a HashMap.
(I ran lein uberjar and lein pom on the clojure project to make it work with maven. I can get things to work for clojure function with simple types e.g. String, so the maven setup does work.)
I am getting the following error when I run some java unit tests calling the java code:
java.lang.ClassCastException: class clojure.lang.LazySeq cannot be cast to class java.util.Map (clojure.lang.LazySeq is in unnamed module o
f loader 'app'; java.util.Map is in module java.base of loader 'bootstrap')
How can I get this to work? Is this the proper way to call clojure methods from java?
What about if the HashMap had a POJO object as a value rather than a String?
My java code:
import interop.Core;
public class BillingCalc {
static Map<String, String> nonEmptyItems(Map<String, String> items) {
return Core.non_empty_seats(new HashMap<String, String>());
}
}
My clojure code:
(ns interop.core
(:gen-class
:name interop.Core
:methods [^{:static true} [apply_vat_to_netto [java.math.BigDecimal java.math.BigDecimal] java.math.BigDecimal]
^{:static true} [non_empty_seats [java.util.Map] java.util.Map]]) )
(defn -filter-empty-seats
"filter out those with empty seats"
[seats]
(filter (fn [[_ v]] (pos? (:booked-items v))) seats))
(defn -non_empty_seats
[java-seats]
(-filter-empty-seats (into {} java-seats)))
I guess that your error is caused by this definition in :gen-class:
[non_empty_seats [java.util.Map] java.util.Map]]
From docs for :gen-class:
:methods [ [name [param-types] return-type], ...]
The expected type of returned value is java.util.Map, but filter in -filter-empty-seats returns instance of clojure.lang.LazySeq. You should rewrite -non_empty_seats like this:
(defn -non_empty_seats
[java-seats]
(into {} (-filter-empty-seats java-seats)))

How to access the .class of a java class in clojure

I want to take use of the reflection in clojure.
Suppose there is a part of java code
import com.example.SampleClass;
import java.lang.reflect.Field;
public class Sample {
public static void main(String[] args) {
Field field = SampleClass.class.getDeclaredField("someFiledName");
}
}
I can not find how to express the meaning of SampleClass.class where there is not any instance of SampleClass. It is just imported so I can not use (.class SampleClass) or (.getClass SampleClass)
The class itself is the class - only Java, the language, needs .class
to get to that.
So you import the class and then you can call your function on it.
user=> (import 'java.lang.Math) ; that's not really needed
java.lang.Math
user=> (.getDeclaredField java.lang.Math "PI")
#object[java.lang.reflect.Field 0x74ad8d05 "public static final double java.lang.Math.PI"]

Clojure: call java static method/field via instance variable or string (not class name symbol)

I can do the following and it works...
=> (. java.awt.event.KeyEvent getKeyText 10)
"Enter"
But, I have an instance of java.awt.event.KeyEvent called ev. For example,
=> (class ev)
java.awt.event.KeyEvent
I want to call the method like this instead (but, this produces an error):
=> (. (class ev) getKeyText 10)
No matching method getKeyText found taking 1 args for class java.lang.Class
Is it possible to call a static method from an instance?
I have read the docs and searched stack overflow. The question here is not the same.
Just like in Java, you can only call static methods directly if you know at compile time what method of what class you want to call. Otherwise you need to use reflection on the Class object to find method handles.
Here is an attempt using the MethodHandle API.
(ns playground.static-method-handle
(:import [java.lang.invoke MethodType MethodHandles]))
;; Get a MethodType instance that encodes the shape of the method (return type and arguments)
(def getKeyText-method-type (MethodType/methodType String Integer/TYPE))
(defn call-getKeyText-on-class [cl key-code]
(let [lu (MethodHandles/lookup)
h (.findStatic lu cl "getKeyText" getKeyText-method-type)]
(.invokeWithArguments h [(int key-code)])))
and we use it like this:
(call-getKeyText-on-class KeyEvent 10)
;; => "Enter"

Can't call public method of non-public class: public (Google gcloud library)

I am attempting to use the gcloud library.
(ns firengine.state
(:import
[com.google.cloud AuthCredentials]
[com.google.cloud.datastore DatastoreOptions]))
(-> (DatastoreOptions/builder)
(.projectId "<project_id>")
(.authCredentials
(AuthCredentials/createForJson
(clojure.java.io/input-stream service-account-path)))
.build)
The above clojure code is translated from the following code snippet (ellided, click on "Run elsewhere").
import com.google.cloud.AuthCredentials;
import com.google.cloud.datastore.DatastoreOptions;
DatastoreOptions options = DatastoreOptions.builder()
.projectId(PROJECT_ID)
.authCredentials(AuthCredentials.createForJson(
new FileInputStream(PATH_TO_JSON_KEY))).build();
When I call this code from the Clojure REPL, I get the following error.
Unhandled java.lang.IllegalArgumentException
Can't call public method of non-public class: public
com.google.cloud.ServiceOptions$Builder
com.google.cloud.ServiceOptions$Builder.projectId(java.lang.String)
Reflector.java: 88 clojure.lang.Reflector/invokeMatchingMethod
Reflector.java: 28 clojure.lang.Reflector/invokeInstanceMethod
boot.user4590132375374459695.clj: 168 firengine.state/eval17529
boot.user4590132375374459695.clj: 167 firengine.state/eval17529
Compiler.java: 6927 clojure.lang.Compiler/eval
... elided ...
The com.google.cloud.datastore.DatastoreOptions code can be found here.
Updated June 29, 2016:
Pursuant to Schlomi's advice below, I thought that maybe if I AOT compiled my own wrapper around DatastoreOptions that it would work.
(ns firengine.datastore
(:import
[com.google.cloud AuthCredentials]
[com.google.cloud.datastore Datastore DatastoreOptions Entity Key KeyFactory])
(:gen-class
:state state
:init init
:constructors {[String String] []}))
(defn -init
[^String project-id ^String service-account-path]
(let [service-account (clojure.java.io/input-stream service-account-path)
credentials (AuthCredentials/createForJson service-account)
dsoptions (-> (DatastoreOptions/builder)
(.projectId project-id)
(.authCredentials credentials)
.build)]
[[] {:project-id project-id
:service-account-path service-account-path
:datastore-options dsoptions}]))
I modified my boot development task to include the following:
(deftask development
"Launch Immediate Feedback Development Environment"
[]
(comp
(aot :namespace '#{firengine.datastore})
(repl :port 6800)
(reload)
(watch)
(cljs)
(target :dir #{"target"})))
And I attempted to construct the object like so:
(def service-account-path (System/getenv "FIRENGINE_SERVICE_ACCOUNT_PATH"))
(def project-id (System/getenv "PROJECT_ID"))
(def datastore-options (firengine.datastore. project-id service-account-path))
Unfortunately, I still get the same error?
clojure.lang.Compiler$CompilerException: java.lang.reflect.InvocationTargetException, compiling:(state.clj:15:1)
java.lang.reflect.InvocationTargetException:
java.lang.IllegalArgumentException: Can't call public method of non-public class: public com.google.cloud.ServiceOptions$Builder com.google.cloud.ServiceOptions$Builder.projectId(java.lang.String)
Am I not really aot compiling firengine.datastore?
Yeah.... that problem. You wouldnt believe it, but its actually an open bug in the jdk from ... wait for it ... 1999!
You can read about it more in clojure jira and on google groups.
You might have to make your own java wrapper to avoid this, or ask the library author to take this old known java bug into consideration.
If you dont want to write your own java wrapper, and the author insists that "this is the best design, like, ever!", then you could always force it by setting the method accessibility with (.setAccessible method true) and some more custom reflection code..
Good luck!
As Shlomi said, that's a long running bug. Following the advice of Noam Ben Ari on the clj-jira, I've managed the circumvent the issue by writing a small java class that wraps the client creation. I can then use that directly from my clj code.
package pubsub_echo.pubsub;
import com.google.cloud.AuthCredentials;
import com.google.cloud.pubsub.PubSub;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class GCloudPubSub {
public PubSub getClient() throws FileNotFoundException, IOException {
PubSubOptions.Builder optionsBuilder = PubSubOptions.builder();
ClassLoader classLoader = getClass().getClassLoader();
FileInputStream authStream = new FileInputStream(classLoader.getResource("SERVICE_ACCOUNT.json").getPath());
AuthCredentials creds = AuthCredentials.createForJson(authStream);
return optionsBuilder
.authCredentials(creds)
.projectId("PROJECT_ID")
.build()
.service();
}
}
For guidance on adding Java compilation to your project:
https://github.com/technomancy/leiningen/blob/master/doc/MIXED_PROJECTS.md
so, i am a complete Clojure noob and ran into a similar error using Caffeine Cache: "IllegalArgumentException Can't call public method of non-public class: public default void com.github.benmanes.caffeine.cache.LocalManualCache.put(java.lang.Object,java.lang.Object) clojure.lang.Reflector.invokeMatchingMethod (Reflector.java:88)"
my original function signature was this:
(defn get
[cache key]
(.getIfPresent cache key)
)
and i think the issue is that Clojure could not figure out where to apply Cache's .getIfPresent function. Adding the type fixed it:
(defn get
[^Cache cache key]
(.getIfPresent cache key)
)
Even though it's not a direct answer and your question went over my head, I hope this helps.
Completely inspired by hironroy's answer, I worked through an isolated example of getting this working. Created the file below in src/gcloud/GcloduDatastore.java in the following github project.
package gcloud;
import com.google.cloud.AuthCredentials;
import com.google.cloud.datastore.Datastore;
import com.google.cloud.datastore.DatastoreOptions;
import com.google.cloud.datastore.DatastoreOptions.Builder;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class GCloudDatastore {
public static Datastore getDatastore() throws FileNotFoundException, IOException {
DatastoreOptions.Builder optionsBuilder = DatastoreOptions.builder();
FileInputStream authStream = new FileInputStream(System.getenv("SERVICE_ACCOUNT_DOT_JSON_PATH"));
AuthCredentials creds = AuthCredentials.createForJson(authStream);
return optionsBuilder
.authCredentials(creds)
.projectId(System.getenv("PROJECT_ID"))
.build()
.service();
}
}

How to write the following class in Clojure?

I want to write the following in Clojure, but I can't figure it out for the life of me:
new TouchCommand() {
#Override
public void itemTouched(TouchMenuItem selectedItem) {
}
I tried:
(reify com.vaadin.touchkit.TouchMenu$TouchCommand
(itemTouched [^com.vaadin.touchkit.TouchMenu$TouchMenuItem item]))
but it returns:
java.lang.IllegalArgumentException: Can't define method not in interfaces: itemTouched
even though "itemTouched" does exist in the interface. Can anyone help?
I haven't done extensive Java interop with Clojure so this might be wrong, but how about
(proxy [TouchCommand] []
(itemTouched [selectedItem]
(.. (getParent)
(navigateTo
(UiBasics.)))))