I'm trying to get back into using Clojure a bit. This time around with actual package management and builds, so that I'm able to easily import Java classes and build a Windows / Linux / Mac OS X application in a single step.
However, I'll freely admit that I'm a Java newbie. I get the basic gist of the language, but find myself stalling when it comes to finding methods and fields on an object. One thing that I found myself missing, deeply, was an equivalent to Python's "dir" function, but for Java objects. Essentially, I wanted a set of functions that would let me list and search through methods, constructors and fields on a Java object. So I set up a few and dropped them in ~/.cljrc (My shell scripts to launch Clojure load this, so it's a handy place to put reader functions. ===.cljrc (in-ns 'user) (import '(java.util.regex Pattern Matcher) '(java.lang.reflect Modifier)) (defn #^{:doc "Takes a sequence of strings as arguments and joins them with the string `ch'. If `ch' is omitted, a space (\" \") will be used. If only one string is supplied, it will be returned unchanged. If no strings are supplied, an empty string (\"\") is returned." :test (fn [] (assert (= "" (join-strings nil))) (assert (= "a" (join-strings '("a")))) (assert (= "a1b" (join-strings 1 (list "a" "b")))) (assert (= "a b c" (join-strings ["a" "b" "c"])))) } join-strings ([strings] (join-strings " " strings)) ([ch strings] (let [strings (seq strings)] (if (empty? strings) "" (if (<= (.size strings) 1) (reduce str "" strings) (reduce (fn [a b] (str a ch b)) strings)))))) (defn #^{:doc "Takes as arguments a regular expression and a list. Returns the elements of the list for which the regular expression is a match. The comparison is done against (str element), but the returned values are the actual elements. The optional `prefix' argument is passed as a prefix to all elements in `li', but the elements are returned without the prefix."} filter-list-by-re ([] nil) ([li] li) ([re li] (filter-list-by-re re "" li)) ([re prefix li] (let [regex (. Pattern (compile re (. Pattern CASE_INSENSITIVE)))] (filter (fn [x] (.. regex (matcher (str prefix x)) (find))) li)))) (defn #^{:doc "Returns a listing of the methods, constructors and fields of an object as a hashmap."} dir ([ob] (let [methods (.. ob (getClass) (getMethods)) constructors (.. ob (getClass) (getConstructors)) fields (.. ob (getClass) (getFields))] (hash-map "method" (seq methods) "constructor" (seq constructors) "field" (seq fields)))) ([re ob] (let [old-map (dir ob)] (reduce merge (map (fn [key] (hash-map key (filter-list-by-re re key (get old-map key nil)))) (keys old-map)))))) (defn #^{:doc "Tests if the object `ob' has a method with the name `method-name'. Returns true if yes, false otherwise." :test (fn [] (assert (has-method? "size" (list 1))) (assert (not (has-method? "ize" (list 1)))))} has-method? [method-name ob] (< 0 (.size (get (dir (str "\\W" method-name "\\(") ob) "method")))) (defn #^{:doc "Returns the methods, constructors and fields on an object as a single list." } list-dir ([ob] (list-dir "" ob)) ([re ob] (reduce concat (vals (dir re ob))))) (defn #^{:doc "Prints the methods, constructors and fields on an object, one per line. If the optional `re' option is specified, only methods matching that regular expression will be printed. Note that the regular expression is case insensitive and can match the prefixes, so (print-dir \"^field\" \"\") will match only fields for the String class" } print-dir ([ob] (print-dir "" ob)) ([re ob] (let [old-map (dir re ob)] (println (join-strings "\n" (mapcat (fn [key] (map (fn [val] (str key ": " val)) (get old-map key))) (keys old-map))))))) (defn #^{:doc "Prints fields of `ob', one per line, showing field name and value. If the optional argument `re' is supplied, only fields matching that regular expression are printed."} print-fields ([ob] (print-fields "" ob)) ([re ob] (let [fields (get (dir re ob) "field")] (println (join-strings "\n" (map (fn [field] (join-strings " " (list (. Modifier (toString (. field (getModifiers)))) (.. field (getDeclaringClass) (getName)) ":" (. field (getName)) "=" (. field (get ob))))) fields)))))) === Of the functions outlined above, I find "print-dir" (prints one match par line) and print-fields (prints the fields on an object, showing type, name and value) to be the most useful. user=> (import '(java.io File)) nil user=> (print-dir "^constructor.*\\(.*String.*\\)" (File. ".")) constructor: public java.io.File(java.lang.String) constructor: public java.io.File(java.lang.String,java.lang.String) constructor: public java.io.File(java.io.File,java.lang.String) nil user=> (print-fields (File. ".")) public static final java.io.File : separatorChar = / public static final java.io.File : separator = / public static final java.io.File : pathSeparatorChar = : public static final java.io.File : pathSeparator = : nil By being able to do quick regexp searches over an actual object, I find it easier to then head to the documentation for that class, knowing what I'm looking for. I used http://pupeno.com/blog/how-to-create-a-clojure-application to get started in building an application. It was helpful enough, but left me with an app that required a recompile before every use. I find that the Clojure REPL is terribly useful for inspecting variables and classes and trying things out, so wanted an app that permitted both the easy class management of Maven and the flexibility of developing with an REPL. Hence, I made a few little changes to the application to make it optionally drop to REPL-mode: ===App.java package net.kearsley.reeder; import java.util.ArrayList; import clojure.lang.RT; import clojure.lang.Repl; public class App { private static final String MAINCLJ = "net/kearsley/reeder/ reeder.clj"; public static void main( String[] args ){ boolean use_repl = false; Repl new_repl = new Repl(); ArrayList<String> new_args = new ArrayList<String>(); for( int i = 0; i < args.length; i++ ){ if( args[ i ].equals( "--repl" ) || args[ i ].equals( "-i" ) ){ use_repl = true; } else{ new_args.add( args[ i ] ); } } args = new_args.toArray( new String[0] ); if( !use_repl ){ try { RT.loadResourceScript(MAINCLJ); RT.var("net.kearsley.reeder", "main").invoke(args); } catch(Exception e) { e.printStackTrace(); } } else { System.out.println( "Starting REPL." ); try { new_repl.main( args ); } catch( Exception e ){ e.printStackTrace(); } } } } === That way, when the application is loaded with the --repl or -i options, it drops into the REPL. This worked OK, but still didn't process ns directives correctly. So, I wrote a little script that launches the project, in repl mode, loading the file ~/.cljrc (where I keep a set of useful reader functions.) It uses, in the classpath, the clojure source directory, rather than the target class directory, thus ensuring that (ns (:require (net.kearsley.reeder db))) loads from the source files and not the target files. This means that I can alter and test the source, only having to recompile and restart the REPL if I change a Java class or add a new Clojure source file. Ideally. to use this script with a different maven / appassembler project, it simply needs the BASE_DIR and APPLICATION_NAME variables changed. ===run_project.sh #!/bin/sh BASE_DIR=/Users/kearsley/Programming/Projects/reeder APPLICATION_NAME=reeder PROJECT_DIR=$BASE_DIR/target SRC_DIR=$BASE_DIR/src/main/ CLASSPATH_PREFIX=$CLASSPATH:$PROJECT_DIR/:$SRC_DIR:$PROJECT_DIR/ classes/ export CLASSPATH_PREFIX pushd $PROJECT_DIR sh ./appassembler/bin/$APPLICATION_NAME --repl ~/.cljrc $* popd === --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Clojure" group. To post to this group, send email to clojure@googlegroups.com To unsubscribe from this group, send email to clojure+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/clojure?hl=en -~----------~----~----~----~------~----~------~--~---