|
4 | 4 | Formerly known as `refactor-nrepl.ns.slam.hound.search`." |
5 | 5 | (:require |
6 | 6 | [clojure.java.io:as io] |
7 | | - [clojure.string:as string] |
8 | | - [compliment.utils]) |
| 7 | + [clojure.string:as string]) |
9 | 8 | (:import |
10 | | - (java.io File))) |
11 | | - |
12 | | -(defn-get-available-classes [] |
13 | | - (let [classes (compliment.utils/classes-on-classpath)] |
14 | | - (into [] |
15 | | - (comp (keep (fn [s] |
16 | | -;; https://github.com/alexander-yakushev/compliment/issues/105 |
17 | | - (when (io/resource (-> s (string/replace"." File/separator) (str".class"))) |
18 | | - s))) |
19 | | - (distinct) |
20 | | - (map symbol)) |
21 | | - classes))) |
22 | | - |
23 | | -(defavailable-classes |
24 | | - (delay (get-available-classes))) |
| 9 | + (java.io File) |
| 10 | + (java.nio.file Files) |
| 11 | + (java.util.concurrent.locks ReentrantLock) |
| 12 | + (java.util.function Function Predicate) |
| 13 | + (java.util.jar JarEntry JarFile) |
| 14 | + (java.util.stream Collectors))) |
25 | 15 |
|
26 | | -(defn-get-available-classes-by-last-segment [] |
27 | | - (group-by #(symbol (peek (string/split (str %)#"\."))) @available-classes)) |
| 16 | +(def ^:privatesimple-cache (atom {})) |
| 17 | + |
| 18 | +(defn-classpath-strings [] |
| 19 | + (into [] (keep #(System/getProperty %)) |
| 20 | + ["sun.boot.class.path""java.ext.dirs""java.class.path"])) |
28 | 21 |
|
29 | | -(defavailable-classes-by-last-segment |
30 | | - (delay (get-available-classes-by-last-segment))) |
| 22 | +(let [lock (ReentrantLock.)] |
| 23 | + (defn-recompute-if-classpath-changed [value-fn] |
| 24 | + (.lock lock) |
| 25 | + (try (let [cache @simple-cache |
| 26 | + cp-hash (reduce hash-combine0 (classpath-strings)) |
| 27 | + same-cp? (= cp-hash (:classpath-hash cache)) |
| 28 | + cached-value (:files-on-classpath cache)] |
| 29 | + (if (and (some? cached-value) same-cp?) |
| 30 | + cached-value |
| 31 | + (let [value (value-fn)] |
| 32 | + (reset! simple-cache {:classpath-hash cp-hash |
| 33 | +:files-on-classpath value}) |
| 34 | + value))) |
| 35 | + (finally (.unlock lock))))) |
31 | 36 |
|
32 | | -(defnreset |
33 | | -"Reset the cache ofclasses" |
| 37 | +(defn-classpath |
| 38 | +"Returns a sequence ofFile objects of the elements on the classpath." |
34 | 39 | [] |
35 | | - (alter-var-root #'available-classes (constantly (delay (get-available-classes)))) |
36 | | - (alter-var-root #'available-classes-by-last-segment (constantly (delay (get-available-classes-by-last-segment))))) |
| 40 | + (mapcat #(.split ^String % File/pathSeparator) (classpath-strings))) |
| 41 | + |
| 42 | +(defn-file-seq-nonr |
| 43 | +"A tree seq on java.io.Files, doesn't resolve symlinked directories to avoid |
| 44 | + infinite sequence resulting from recursive symlinked directories." |
| 45 | + [dir] |
| 46 | + (tree-seq |
| 47 | + (fn [^File f] (and (.isDirectory f) (not (Files/isSymbolicLink (.toPath f))))) |
| 48 | + (fn [^File d] (seq (.listFiles d))) |
| 49 | + dir)) |
| 50 | + |
| 51 | +(defn-list-files |
| 52 | +"Given a path (either a jar file, directory with classes or directory with |
| 53 | + paths) returns all files under that path." |
| 54 | + [^String path, scan-jars?] |
| 55 | + (cond (.endsWith path"/*") |
| 56 | + (for [^File jar (.listFiles (File. path)) |
| 57 | +:when (.endsWith ^String (.getName jar)".jar") |
| 58 | + file (list-files (.getPath jar) scan-jars?)] |
| 59 | + file) |
| 60 | + |
| 61 | + (.endsWith path".jar") |
| 62 | + (if scan-jars? |
| 63 | + (try (-> (.stream (JarFile. path)) |
| 64 | + (.filter (reify Predicate |
| 65 | + (test [_ entry] |
| 66 | + (not (.isDirectory ^JarEntry entry))))) |
| 67 | + (.map (reify Function |
| 68 | + (apply [_ entry] |
| 69 | + (.getName ^JarEntry entry)))) |
| 70 | + (.collect (Collectors/toList))) |
| 71 | + (catch Exception _)) |
| 72 | + ()) |
| 73 | + |
| 74 | + (= path"") () |
| 75 | + |
| 76 | + (.exists (File. path)) |
| 77 | + (let [root (File. path) |
| 78 | + root-path (.toPath root)] |
| 79 | + (for [^File file (file-seq-nonr root) |
| 80 | +:when (not (.isDirectory file))] |
| 81 | + (let [filename (str (.relativize root-path (.toPath file)))] |
| 82 | + (cond-> filename |
| 83 | +;; Replace Windows backslashes with slashes. |
| 84 | + (not= File/separator"/") (.replace File/separator"/") |
| 85 | + (.startsWith filename"/") (.substring filename1))))))) |
| 86 | + |
| 87 | +(defmacrolist-jdk9-base-classfiles |
| 88 | +"Because on JDK9+ the classfiles are stored not in rt.jar on classpath, but in |
| 89 | + modules, we have to do extra work to extract them." |
| 90 | + [] |
| 91 | + (when (try (ns-resolve *ns* 'java.lang.module.ModuleFinder) (catch Exception _)) |
| 92 | + `(-> (.findAll (java.lang.module.ModuleFinder/ofSystem)) |
| 93 | + (.stream) |
| 94 | + (.flatMap (reify Function |
| 95 | + (apply [_ mref#] |
| 96 | + (.list (.open ^java.lang.module.ModuleReference mref#))))) |
| 97 | + (.collect (Collectors/toList))))) |
| 98 | + |
| 99 | +(defn-all-files-on-classpath* |
| 100 | +"Given a list of files on the classpath, returns the list of all files, |
| 101 | + including those located inside jar files." |
| 102 | + [classpath] |
| 103 | + (let [seen (java.util.HashMap.) |
| 104 | + seen? (fn [x] (.putIfAbsent seen x x))] |
| 105 | + (-> [] |
| 106 | + (into (comp (map #(list-files %true)) cat (remove seen?)) classpath) |
| 107 | + (into (remove seen?) (list-jdk9-base-classfiles))))) |
| 108 | + |
| 109 | +(defn-classes-on-classpath* [files] |
| 110 | + (let [filename->classname |
| 111 | + (fn [^String file] |
| 112 | + (when (.endsWith file".class") |
| 113 | + (when-not (or (.contains file"__") |
| 114 | + (.contains file"$") |
| 115 | + (.equals file"module-info.class")) |
| 116 | + (let [c (-> (subs file0 (- (.length file)6));; .class |
| 117 | +;; Resource separator is always / on all OSes. |
| 118 | + (.replace"/""."))] |
| 119 | +;; https://github.com/alexander-yakushev/compliment/issues/105 |
| 120 | + (when (io/resource (-> c (string/replace"." File/separator) (str".class"))) |
| 121 | + c)))))] |
| 122 | + (into [] (comp (keep filename->classname) (distinct) (map symbol)) files))) |
| 123 | + |
| 124 | +(defnavailable-classes [] |
| 125 | + (classes-on-classpath* (all-files-on-classpath* (classpath)))) |
| 126 | + |
| 127 | +(defn-get-available-classes-by-last-segment [] |
| 128 | + (group-by #(symbol (peek (string/split (str %)#"\."))) (available-classes))) |
| 129 | + |
| 130 | +(defnavailable-classes-by-last-segment [] |
| 131 | + (recompute-if-classpath-changed #(get-available-classes-by-last-segment))) |