JavaFX 8, Java 1.8.0_31, Windows 7 x64
I have a minimal JavaFX program in Clojure. The (ns...) clause is able to import the required Java packages fine except the classes in javafx.scene.control, such as Button and TextField, etc.
I have to put the import for these after initializing the toolkit. Why can't I import these classes before the toolkit is initialized? I'm not actually creating any objects yet... so I'm guessing JFX is somehow doing something in the background while these classes are imported, requiring the initialization first. Below is my complete lein project (minimized from the actual application where I saw this problem, and without all the nice macros that clean up the JFX syntax):
File project.clj:
(defproject jfx-so "0.1.0-SNAPSHOT"
:dependencies [[org.clojure/clojure "1.6.0"]]
:main jfx-so.core)
File src/jfx_so/core.clj:
(ns jfx-so.core
(:import [javafx.scene Scene]
[javafx.scene.layout BorderPane]
[javafx.stage Stage]))
(defonce force-toolkit-init (javafx.embed.swing.JFXPanel.))
;; For some reason the following must be imported after initting the toolkit
(import [javafx.scene.control Button])
(defn -main [& args]
(javafx.application.Platform/runLater
#(doto (Stage.)
(.setScene (Scene. (BorderPane. (Button. "Hello"))))
(.show))))
Thanks! :)
I haven't had a problem with this. Perhaps it has to do with your defonce?
I do my imports first. But I do make sure to init the FX-envoronment before instanciating any FX-classes. So after your -main-method I would put:
(defn -main [& args]
;;body here
)
;; initialze the environement
(javafx.embed.swing.JFXPanel.)
;; ensure I can keep reloading and running without restarting JVM every time
(javafx.application.Platform/setImplicitExit false)
;; then
(-main)
Hope this helps.
Related
I am trying to write a couple of wrapper functions for the code here
it basically has the form (as given in the example page) of
String json = ...
VPackParser parser = new VPackParser.Builder().build();
VPackSlice slice = parser.fromJson(json);
I am aware that To import inner classes one uses $, but every combination of the following doesn't seem to work.
(ns the.pain.is.real
(:require [clojure.reflect :as r])
(:import
com.arangodb.velocypack.VPackBuilder ;; fine
com.arangodb.velocypack.VPackSlice ;; fine
com.arangodb.velocypack.VPackParser ;; fine
com.arangodb.velocypack.VPack ;; fine
com.arangodb.velocypack.VPackParser$Builder ;; nope
))
I just get a Unhandled java.lang.ClassNotFoundException.
I had worked with some similar code that I had got working using:
(ns winning
(:import com.arangodb.ArangoDB$Builder))
(.build (-> (new ArangoDB$Builder)
(.host "127.0.0.1" 8529)
(.user username)
(.password password)))
Any ideas?
looking in the jar file does show
com/arangodb/velocypack/VPackParser$Builder.class
Is it just trial and error?
Answering my own question.
It appears that two versions were downloaded when using lein deps. Even though only [com.arangodb/velocypack "1.0.0"] was in the project file, both 1.0.0 and 1.0.10 were in the .m2 dir. The class com/arangodb/velocypack/VPackParser$Builder.class is only found in version 1.0.10.
After changing the project file to [com.arangodb/velocypack "1.0.10"] the import works.
Let's say I create a new Leiningen project (lein new app example) and add some code in example/src/example/core.clj that makes use of :gen-class:
(ns example.core
(:gen-class :extends javafx.application.Application))
(defn -start [this stage]
(.show stage))
(defn -main [& args]
(javafx.application.Application/launch example.core args))
If I then create a JAR (lein uberjar) and run it, everything works fine. However, if I instead try to run my app directly (lein run), I get a ClassNotFoundException. In addition, if I open a REPL (lein repl), I first get the same error as before, but after running this code:
(compile 'example.core)
the error no longer appears in lein run or lein repl.
Could someone please explain to me what exactly is going on here, and how I can run my app directly without needing to manually compile my code from a REPL?
Edit: After fooling around a bit more, I found that the solution to this problem is to add
:aot [example.core]
to project.clj. (Thanks #Mars!) I'm still confused, though, because I had previously tried simply removing ^:skip-aot, which (according to the docs) should work:
This will be AOT compiled by default; to disable this, attach
^:skip-aot metadata to the namespace symbol.
But it doesn't. Why?
Another edit (if I should split any of this into a separate question, let me know and I'll do so): I've been playing with hyphens (lein new app my-example), and weird stuff has been happening. This doesn't work:
(ns my-example.core
(:gen-class :extends javafx.application.Application))
;; ...
(defn -main [& args]
(javafx.application.Application/launch my-example.core args))
But this does:
(ns my-example.core
(:gen-class
:name my-example.Core
:extends javafx.application.Application))
;; ...
(defn -main [& args]
(javafx.application.Application/launch my-example.Core args))
So my class name can either start with a lowercase letter (example.core) or contain a hyphen (my-example.Core), but not both? I really don't get it.
And finally, lein uberjar fails on that final example (with the explicit :name), because
Warning: The Main-Class specified does not exist within the jar. It may not be executable as expected. A gen-class directive may be missing in the namespace which contains the main method.
As far as I can tell, the only way to fix that is to split the Application subclass into a separate namespace. Is there another way?
Agreed with #Mars, the problem is that lein run does not AOT the example.core namespace. The default Leiningen template made the example.core non AOT:
(defproject example "0.1.0-SNAPSHOT"
...
:main ^:skip-aot example.core
...)
My best guess is that you could define your app using defrecord and use that as a class instead of the namespace.
I just started on my first clojure project using lein, the code here:
(ns fileops.core
(:use
[clojure.core :only (slurp)]
[clojure-csv.core :only (parse-csv)]
[fileops.core]))
(defn -main
"I don't do a whole lot ... yet."
[& args]
(read-file "sample.csv"))
(defn read-file
"open and read the csv file"
[fname]
(with-open [file (clojure.java.io/reader fname)]
(parse-csv (slurp fname))))
I tried to run this using "lein run" but I keep getting this error:
Caused by: java.lang.RuntimeException: Unable to resolve symbol: read-file in this context
at clojure.lang.Util.runtimeException(Util.java:219)
at clojure.lang.Compiler.resolveIn(Compiler.java:6874)
at clojure.lang.Compiler.resolve(Compiler.java:6818)
at clojure.lang.Compiler.analyzeSymbol(Compiler.java:6779)
at clojure.lang.Compiler.analyze(Compiler.java:6343)
... 52 more
What am I doing wrong?
You have used only slurp from clojure core, meaning that every other core function is now unavailable to you :) Try changing your ns to use :require instead of :use, as this is more idiomatic.
One thing to note is that order does matter in clojure, so if you don't declare a function at the top of your file, as in C and some other languages, the earlier functions will not be able to make reference to them. This is what was causing your error before and is why I like to define my -main function at the bottom. It's a matter of style.
Another thing is that your -main function is taking variable args right now and not using them. In Clojure it is idiomatic to use _ to refer to a parameter that doesn't get used. You could use & _ to avoid error messages, for when the user passes in unnecessary args, but I would just have the -main function parameterless from the start. This is because nothing needs to be provided to main when you run the program, and errors do make debugging easier. It is good to know what is getting used and where. The sample.csv file is already provided and is having read-file called on it, so the program should run if your read-file function is correct and the sample.csv file is in the proper location.
Regarding your -main function, it would be nice to put a little test in there to see if it executes properly when you run it, so I changed it to print out the contents of the csv file onto your console. This way of printing from a file is efficient and worth studying in its own right.
Finally, Make sure you include clojure-csv.core in your project.clj file.
core.clj:
(ns fileops.core
(:require
[clojure-csv.core :refer [parse-csv]]))
(defn read-file
"open and read the csv file"
[fname]
(with-open [file (clojure.java.io/reader fname)]
(parse-csv (slurp fname))))
(defn -main []
(println (clojure.string/join "\n" (read-file "resources/test.csv"))))
project.clj:
...
:dependencies [[org.clojure/clojure "1.5.1"]
[clojure-csv/clojure-csv "2.0.1"]
...]
:main fileops.core
You need to declare fileops.core as :main, as shown above. This tells Leiningen what function to execute when you enter lein run. Very important and tricky stuff.
So now make sure you are in the root of your project directory and at the terminal, run the following:
lein clean
lein deps
lein run
Good luck!
Further reading:
8th light blog on name-spaces
flying machine studios explanation of lein run
read-file should be before main in your source OR you should put before -main a declare cause like that:
(declare read-file)
I used to like to include all of clojure.contrib, and require all the libraries. This makes find-doc useful as a discovery tool.
Nowadays (clojure 1.4) clojure.contrib is split into many sub-libraries. And that rather spoils my scheme, and it also means that I am constantly having to restart the JVM every time I need a new library.
So I'm busy constructing a project.clj file with many lines:
[org.clojure/algo.generic "0.0.6"]
....
[org.clojure/data.xml "0.0.4"]
....
So that I can get leiningen to put every clojure contrib library on the classpath, whether I need them or not.
And I reckon that this is going to be a spectacular pain in the neck, what with the version numbers, and all.
And I wonder if anyone has a better way to do the same thing?
EDIT: Thinking about it, if there's a web page somewhere that has a list of library names and current versions, I can turn that into a project file fairly easily.
You could use pomegranate if you just want to run it in the REPL (which seems like it would be the only appropriate use case, right?). You can have it look up the latest versions using the Maven Central API. I think this is better than maintaining some sort of dependencies project, generated or otherwise.
(require '[cemerick.pomegranate :refer [add-dependencies]])
(add-dependencies
:coordinates '[[clj-http "0.5.8"]]
:repositories {"clojars" "http://clojars.org/repo"})
(require '[clj-http.client :as client])
;; contrib project names from https://github.com/clojure
(def contrib ["tools.nrepl" "tools.trace" "tools.namespace" "tools.macro"
"test.generative" "math.numeric-tower" "core.match" "core.logic"
"data.priority-map" "core.contracts" "tools.cli" "java.jmx"
"java.jdbc" "java.classpath" "data.xml" "data.json" "core.unify"
"core.incubator" "core.cache" "algo.monads" "data.generators"
"core.memoize" "math.combinatorics" "java.data" "tools.logging"
"data.zip" "data.csv" "algo.generic" "data.codec"
"data.finger-tree"])
(defn add-contrib-dependencies
"look up the latest version of every contrib project in maven central,
and add them as dependencies using pomegranate."
[project-names]
(add-dependencies
:coordinates
(map (juxt
(comp symbol (partial format "org.clojure/%s"))
(fn [proj]
(Thread/sleep 100)
(-> "http://search.maven.org/solrsearch/select?q=%s&rows=1&wt=json"
(format proj)
(client/get {:as :json})
:body :response :docs first :latestVersion)))
project-names)))
Now you can just invoke this function on the list of project names:
user=> (add-contrib-dependencies contrib)
{[org.clojure/data.zip "0.1.1"] nil,
[org.clojure/java.classpath "0.2.0"] nil,
[org.clojure/core.cache "0.6.2"] nil, ...}
UPDATE: as suggested earlier, I had made this answer into a library. It can be used either as nREPL middleware or invoked manually from a running REPL session. The code can be found at https://github.com/rplevy/contrib-repl, where usage instructions can also be found.
I feel your pain. It should be helpful http://dev.clojure.org/display/community/Where+Did+Clojure.Contrib+Go
I've written a small Swing App before in Clojure and now I'd like to create an Ajax-style Web-App. Compojure looks like the best choice right now, so that's what I'm going to try out.
I'd like to have a real tiny edit/try feedback-loop, so I'd prefer not to restart the web server after each small change I do.
What's the best way to accomplish this? By default my Compojure setup (the standard stuff with ant deps/ant with Jetty) doesn't seem to reload any changes I do. I'll have to restart with run-server to see the changes. Because of the Java-heritage and the way the system is started etc. This is probably perfectly normal and the way it should be when I start the system from command-line.
Still, there must be a way to reload stuff dynamically while the server is running. Should I use Compojure from REPL to accomplish my goal? If I should, how do I reload my stuff there?
This is quite an old question, and there have been some recent changes that make this much easier.
There are two main things that you want:
Control should return to the REPL so you can keep interacting with your server. This is accomplished by adding {:join? false} to options when starting the Jetty server.
You'd like to automatically pick up changes in certain namespaces when the files change. This can be done with Ring's "wrap-reload" middleware.
A toy application would look like this:
(ns demo.core
(:use webui.nav
[clojure.java.io]
[compojure core response]
[ring.adapter.jetty :only [run-jetty]]
[ring.util.response]
[ring.middleware file file-info stacktrace reload])
(:require [compojure.route :as route] view)
(:gen-class))
; Some stuff using Fleet omitted.
(defroutes main-routes
(GET "/" [] (view/layout {:body (index-page)})
(route/not-found (file "public/404.html"))
)
(defn app
[]
(-> main-routes
(wrap-reload '(demo.core view))
(wrap-file "public")
(wrap-file-info)
(wrap-stacktrace)))
(defn start-server
[]
(run-jetty (app) {:port 8080 :join? false}))
(defn -main [& args]
(start-server))
The wrap-reload function decorates your app routes with a function that detects changes in the listed namespaces. When processing a request, if those namespaces have changed on disk, they are reloaded before further request processing. (My "view" namespace is dynamically created by Fleet, so this auto-reloads my templates whenever they change, too.)
I added a few other pieces of middleware that I've found consistently useful. wrap-file handles static assets. wrap-file-info sets the MIME type on those static assets. wrap-stacktrace helps in debugging.
From the REPL, you could start this app by using the namespace and calling start-server directly. The :gen-class keyword and -main function mean that the app can also be packaged as an uberjar for startup from outside the REPL, too. (There's a world outside the REPL? Well, some people have asked for it anyway...)
Here's an answer I got from James Reeves in the Compojure Google Group (the answer's here with his permission):
You can reload a namespace in Clojure using the :reload key on the use
or require commands. For example, let's say you have a file "demo.clj" that contains your routes:
(ns demo
(:use compojure))
(defroutes demo-routes
(GET "/"
"Hello World")
(ANY "*"
[404 "Page not found"]))
At the REPL, you can use this file and start a server:
user=> (use 'demo)
nil
user=> (use 'compojure)
nil
user=> (run-server {:port 8080} "/*" (servlet demo-routes))
...
You could also put the run-server command in another clojure file.
However, you don't want to put it in the same file as the stuff you want to reload.
Now make some changes to demo.clj. At the REPL type:
user=> (use 'demo :reload)
nil
And your changes should now show up on http://localhost:8080
I wanted to add an answer, since things have changed a bit since the newest answer and I had spent a bit of time looking for this myself.
Install leiningen (just follow the instructions there)
Create project
lein new compojure compojure-test
Edit the ring section of project.clj
:ring {:handler compojure-test.handler/app
:auto-reload? true
:auto-refresh? true}
Start the server on whatever port you want
lein ring server-headless 8080
Check that the server is running in your browser, the default base route should just say "Hello world". Next, go modify your handler (it's in src/project_name). Change the hello world text, save the file and reload the page in your browser. It should reflect the new text.
Following up on Timothy's link to Jim Downing's setup, I recently posted on a critical addition to that baseline that I found was necessary to enable automatic redeployment of compojure apps during development.
I have a shell script that looks like this:
#!/bin/sh
CLASSPATH=/home/me/install/compojure/compojure.jar
CLASSPATH=$CLASSPATH:/home/me/clojure/clojure.jar
CLASSPATH=$CLASSPATH:/home/me/clojure-contrib/clojure-contrib.jar
CLASSPATH=$CLASSPATH:/home/me/elisp/clojure/swank-clojure
for f in /home/me/install/compojure/deps/*.jar; do
CLASSPATH=$CLASSPATH:$f
done
java -server -cp $CLASSPATH clojure.lang.Repl /home/me/code/web/web.clj
web.clj looks like this
(use '[swank.swank])
(swank.swank/ignore-protocol-version "2009-03-09")
(start-server ".slime-socket" :port 4005 :encoding "utf-8")
Whenever I want to update the server I create an ssh tunnel from my local machine to the remote machine.
Enclojure and Emacs (running SLIME+swank-clojure) can connect to the remote REPL.
This is highly configuration dependent but works for me and I think you can adapt it:
Put compojure.jar and the jars under the compojure/deps directory are in your classpath. I use clojure-contrib/launchers/bash/clj-env-dir to do this, all you need to do is set the directory in CLOJURE_EXT and it will find the jars.
CLOJURE_EXT Colon-delimited list of paths to directories whose top-level
contents are (either directly or as symbolic links) jar
files and/or directories whose paths will be in Clojure's
classpath.
Launch clojure REPL
Paste in hello.clj example from compojure root directory
Check localhost:8080
Re-define the greeter
(defroutes greeter
(GET "/"
(html [:h1 "Goodbye World"])))
Check localhost:8080
There are also methods for attaching a REPL to an existing process, or you could keep a socket REPL embedded in your server or you could even define a POST call that will eval on the fly to allow you to redefine functions from the browser itself! There are lots of ways to approach this.
I'd like to follow up on mtnygard's answer and post the full project.clj file and core.clj file that got the given functionality working. A few modifications were made, and it's more barebones
pre-setup commands
lein new app test-web
cd test-web
mkdir resources
project.clj
(defproject test-web "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.5.1"]
[compojure "1.1.6"]
[ring "1.2.1"]]
:main ^:skip-aot test-web.core
:target-path "target/%s"
:profiles {:uberjar {:aot :all}})
core.clj
(ns test-web.core
(:use
[clojure.java.io]
[compojure core response]
[ring.adapter.jetty :only [run-jetty]]
[ring.util.response]
[ring.middleware file file-info stacktrace reload])
(:require [compojure.route :as route])
(:gen-class))
(defroutes main-routes
(GET "/" [] "Hello World!!")
(GET "/hello" [] (hello))
(route/not-found "NOT FOUND"))
(def app
(-> main-routes
(wrap-reload '(test-web.core))
(wrap-file "resources")
(wrap-file-info)
(wrap-stacktrace)))
(defn hello []
(str "Hello World!"))
(defn start-server
[]
(run-jetty #'app {:port 8081 :join? false}))
(defn -main [& args]
(start-server))
Pay Attention to the change from (defn app ...) to (def app ...)
This was crucial to getting the jetty server to work correctly
Compojure uses ring internally (by the same author), the ring web server options allow automatic realoading. So two alternatives would be :
lein ring server
lein ring server-headless
lein ring server 4000
lein ring server-headless 4000
Note that :
You need to have a line in your project.clj file that looks like:
:ring {:handler your.app/handler}