diff options
Diffstat (limited to 'vim/bundle/slimv/swank-clojure/swank/util/class_browse.clj')
-rw-r--r-- | vim/bundle/slimv/swank-clojure/swank/util/class_browse.clj | 149 |
1 files changed, 149 insertions, 0 deletions
diff --git a/vim/bundle/slimv/swank-clojure/swank/util/class_browse.clj b/vim/bundle/slimv/swank-clojure/swank/util/class_browse.clj new file mode 100644 index 0000000..94f325b --- /dev/null +++ b/vim/bundle/slimv/swank-clojure/swank/util/class_browse.clj @@ -0,0 +1,149 @@ +;;; class-browse.clj -- Java classpath and Clojure namespace browsing + +;; by Jeff Valk +;; created 2009-10-14 + +;; Scans the classpath for all class files, and provides functions for +;; categorizing them. + +;; See the following for JVM classpath and wildcard expansion rules: +;; http://java.sun.com/javase/6/docs/technotes/tools/findingclasses.html +;; http://java.sun.com/javase/6/docs/technotes/tools/solaris/classpath.html + +(ns swank.util.class-browse + "Provides Java classpath and (compiled) Clojure namespace browsing. + Scans the classpath for all class files, and provides functions for + categorizing them. Classes are resolved on the start-up classpath only. + Calls to 'add-classpath', etc are not considered. + + Class information is built as a list of maps of the following keys: + :name Java class or Clojure namespace name + :loc Classpath entry (directory or jar) on which the class is located + :file Path of the class file, relative to :loc" + (:import [java.io File FilenameFilter] + [java.util StringTokenizer] + [java.util.jar JarFile JarEntry] + [java.util.regex Pattern])) + +;;; Class file naming, categorization + +(defn jar-file? [#^String n] (.endsWith n ".jar")) +(defn class-file? [#^String n] (.endsWith n ".class")) +(defn clojure-ns-file? [#^String n] (.endsWith n "__init.class")) +(defn clojure-fn-file? [#^String n] (re-find #"\$.*__\d+\.class" n)) +(defn top-level-class-file? [#^String n] (re-find #"^[^\$]+\.class" n)) +(defn nested-class-file? [#^String n] + ;; ^ excludes anonymous classes + (re-find #"^[^\$]+(\$[^\d]\w*)+\.class" n)) + +(def clojure-ns? (comp clojure-ns-file? :file)) +(def clojure-fn? (comp clojure-fn-file? :file)) +(def top-level-class? (comp top-level-class-file? :file)) +(def nested-class? (comp nested-class-file? :file)) + +(defn class-or-ns-name + "Returns the Java class or Clojure namespace name for a class relative path." + [#^String n] + (.replace + (if (clojure-ns-file? n) + (-> n (.replace "__init.class" "") (.replace "_" "-")) + (.replace n ".class" "")) + File/separator ".")) + +;;; Path scanning + +(defmulti path-class-files + "Returns a list of classes found on the specified path location + (jar or directory), each comprised of a map with the following keys: + :name Java class or Clojure namespace name + :loc Classpath entry (directory or jar) on which the class is located + :file Path of the class file, relative to :loc" + (fn [#^ File f _] + (cond (.isDirectory f) :dir + (jar-file? (.getName f)) :jar + (class-file? (.getName f)) :class))) + +(defmethod path-class-files :default + [& _] []) + +(defmethod path-class-files :jar + ;; Build class info for all jar entry class files. + [#^File f #^File loc] + (let [lp (.getPath loc)] + (try + (map (fn [fp] {:loc lp :file fp :name (class-or-ns-name fp)}) + (filter class-file? + (map #(.getName #^JarEntry %) + (enumeration-seq (.entries (JarFile. f)))))) + (catch Exception e [])))) ; fail gracefully if jar is unreadable + +(defmethod path-class-files :dir + ;; Dispatch directories and files (excluding jars) recursively. + [#^File d #^File loc] + (let [fs (.listFiles d (proxy [FilenameFilter] [] + (accept [d n] (not (jar-file? n)))))] + (reduce concat (for [f fs] (path-class-files f loc))))) + +(defmethod path-class-files :class + ;; Build class info using file path relative to parent classpath entry + ;; location. Make sure it decends; a class can't be on classpath directly. + [#^File f #^File loc] + (let [fp (.getPath f), lp (.getPath loc) + m (re-matcher (re-pattern (Pattern/quote + (str "^" lp File/separator))) fp)] + (if (not (.find m)) ; must be descendent of loc + [] + (let [fpr (.substring fp (.end m))] + [{:loc lp :file fpr :name (class-or-ns-name fpr)}])))) + +;;; Classpath expansion + +(def java-version + (Float/parseFloat (.substring (System/getProperty "java.version") 0 3))) + +(defn expand-wildcard + "Expands a wildcard path entry to its matching .jar files (JDK 1.6+). + If not expanding, returns the path entry as a single-element vector." + [#^String path] + (let [f (File. path)] + (if (and (= (.getName f) "*") (>= java-version 1.6)) + (-> f .getParentFile + (.list (proxy [FilenameFilter] [] + (accept [d n] (jar-file? n))))) + [f]))) + +(defn scan-paths + "Takes one or more classpath strings, scans each classpath entry location, and + returns a list of all class file paths found, each relative to its parent + directory or jar on the classpath." + ([cp] + (if cp + (let [entries (enumeration-seq + (StringTokenizer. cp File/pathSeparator)) + locs (mapcat expand-wildcard entries)] + (reduce concat (for [loc locs] (path-class-files loc loc)))) + ())) + ([cp & more] + (reduce #(concat %1 (scan-paths %2)) (scan-paths cp) more))) + +;;; Class browsing + +(def available-classes + (filter (complement clojure-fn?) ; omit compiled clojure fns + (scan-paths (System/getProperty "sun.boot.class.path") + (System/getProperty "java.ext.dirs") + (System/getProperty "java.class.path")))) + +;; Force lazy seqs before any user calls, and in background threads; there's +;; no sense holding up SLIME init. (It's usually quick, but a monstrous +;; classpath could concievably take a while.) + +(def top-level-classes + (future (doall (map (comp class-or-ns-name :name) + (filter top-level-class? + available-classes))))) + +(def nested-classes + (future (doall (map (comp class-or-ns-name :name) + (filter nested-class? + available-classes))))) |