150 lines
5.7 KiB
Clojure
150 lines
5.7 KiB
Clojure
;;; 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)))))
|