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
-~----------~----~----~----~------~----~------~--~---

Reply via email to