|  | 
|  | 1 | +(ns java-javadocs.source-spike) | 
|  | 2 | +  ; (:require [clojure.string :as str] | 
|  | 3 | +  ;           [clojure.tools.deps :as deps] | 
|  | 4 | +  ;           [clojure.java.io :as io]) | 
|  | 5 | +  ; (:import [java.util.jar JarFile] | 
|  | 6 | +  ;          [com.github.javaparser StaticJavaParser ParserConfiguration ParserConfiguration$LanguageLevel] | 
|  | 7 | +  ;          [com.github.javaparser.ast CompilationUnit] | 
|  | 8 | +  ;          [com.github.javaparser.ast.body ClassOrInterfaceDeclaration MethodDeclaration] | 
|  | 9 | +  ;          [com.github.javaparser.ast.nodeTypes NodeWithJavadoc] | 
|  | 10 | +  ;          [com.github.javaparser.javadoc Javadoc] | 
|  | 11 | +  ;          [com.vladsch.flexmark.html2md.converter FlexmarkHtmlConverter])) | 
|  | 12 | + | 
|  | 13 | +; (set! *warn-on-reflection* true) | 
|  | 14 | + | 
|  | 15 | +; (defn- find-jar-coords [jar-url-str] | 
|  | 16 | +  ; (let [libs (:libs (deps/create-basis {:aliases []}))] | 
|  | 17 | +  ;   (first (for [[lib-sym lib-info] libs | 
|  | 18 | +  ;                path (:paths lib-info) | 
|  | 19 | +  ;                :when (str/includes? jar-url-str path)] | 
|  | 20 | +  ;            {:protocol :jar | 
|  | 21 | +  ;             :lib lib-sym | 
|  | 22 | +  ;             :version (select-keys lib-info [:mvn/version])})))) | 
|  | 23 | + | 
|  | 24 | +; (defn- find-javadoc-coords [^Class c] | 
|  | 25 | +  ; (let [class-name (.getName c) | 
|  | 26 | +  ;       url (.getResource c (str (.getSimpleName c) ".class"))] | 
|  | 27 | +  ;   (merge | 
|  | 28 | +  ;     {:class-name class-name} | 
|  | 29 | +  ;     (case (.getProtocol url) | 
|  | 30 | +  ;       "jar" (find-jar-coords (.toString url)) | 
|  | 31 | +  ;       "jrt" {:protocol :jrt | 
|  | 32 | +  ;              :lib 'java/java | 
|  | 33 | +  ;              :version {:mvn/version (System/getProperty "java.version")}} | 
|  | 34 | +  ;       "file" nil)))) | 
|  | 35 | + | 
|  | 36 | +; (defn- find-source-jar-path [{:keys [lib version]}] | 
|  | 37 | +  ; (let [group-path (str/replace (namespace lib) "." "/") | 
|  | 38 | +  ;       artifact (name lib) | 
|  | 39 | +  ;       version-str (:mvn/version version) | 
|  | 40 | +  ;       jar-name (str artifact "-" version-str "-sources.jar") | 
|  | 41 | +  ;       home (System/getProperty "user.home")] | 
|  | 42 | +  ;   (str home "/.m2/repository/" group-path "/" artifact "/" version-str "/" jar-name))) | 
|  | 43 | + | 
|  | 44 | +; (defn- download-source-jar [{:keys [lib version] :as coords}] | 
|  | 45 | +  ; (let [source-jar-path (find-source-jar-path coords)] | 
|  | 46 | +  ;   (when-not (.exists (io/file source-jar-path)) | 
|  | 47 | +  ;     (deps/resolve-deps {:deps {(symbol (str lib "$sources")) version}} nil)) | 
|  | 48 | +  ;   source-jar-path)) | 
|  | 49 | + | 
|  | 50 | +; (defn- source-path [class-name] | 
|  | 51 | +  ; (str (str/replace class-name "." "/") ".java")) | 
|  | 52 | + | 
|  | 53 | +; (defn- extract-source-from-jar [jar-path class-name] | 
|  | 54 | +  ; (with-open [jar (JarFile. ^String jar-path)] | 
|  | 55 | +  ;   (with-open [is (.getInputStream jar (.getJarEntry jar (source-path class-name)))] | 
|  | 56 | +  ;     (slurp is)))) | 
|  | 57 | + | 
|  | 58 | +; (defn- extract-source-from-jrt [class-name] | 
|  | 59 | +  ; (let [src-zip (str (System/getProperty "java.home") "/lib/src.zip") | 
|  | 60 | +  ;       entry-path (str "java.base/" (source-path class-name))] | 
|  | 61 | +  ;   (with-open [jar (JarFile. ^String src-zip)] | 
|  | 62 | +  ;     (with-open [is (.getInputStream jar (.getJarEntry jar entry-path))] | 
|  | 63 | +  ;       (slurp is))))) | 
|  | 64 | + | 
|  | 65 | +; (def highest-java-language-level | 
|  | 66 | +  ; (let [levels (ParserConfiguration$LanguageLevel/values) | 
|  | 67 | +  ;       sorted (sort-by #(.ordinal ^ParserConfiguration$LanguageLevel %) > levels)] | 
|  | 68 | +  ;   (first sorted))) | 
|  | 69 | + | 
|  | 70 | +; (defn- parse-java-source [^String source-code] | 
|  | 71 | +  ; (let [config (.setLanguageLevel (ParserConfiguration.) highest-java-language-level)] | 
|  | 72 | +  ;   (StaticJavaParser/setConfiguration config) | 
|  | 73 | +  ;   (StaticJavaParser/parse source-code))) | 
|  | 74 | + | 
|  | 75 | +; (defn- find-class-declaration [^CompilationUnit cu class-name] | 
|  | 76 | +  ; (let [simple-name (last (str/split class-name #"\."))] | 
|  | 77 | +  ;   (first (filter #(= simple-name (.getNameAsString ^ClassOrInterfaceDeclaration %)) | 
|  | 78 | +  ;                  (.findAll cu ClassOrInterfaceDeclaration))))) | 
|  | 79 | + | 
|  | 80 | +; (defn- find-method [^ClassOrInterfaceDeclaration class-decl method-name] | 
|  | 81 | +  ; (first (filter #(= method-name (.getNameAsString ^MethodDeclaration %)) | 
|  | 82 | +  ;                (.getMethods class-decl)))) | 
|  | 83 | + | 
|  | 84 | +; (defn- extract-javadoc [^NodeWithJavadoc node] | 
|  | 85 | +  ; (when-let [javadoc-opt (.getJavadoc node)] | 
|  | 86 | +  ;   (when (.isPresent javadoc-opt) | 
|  | 87 | +  ;     (.toText ^Javadoc (.get javadoc-opt))))) | 
|  | 88 | + | 
|  | 89 | +; (defn- first-sentence [text] | 
|  | 90 | +  ; (when text | 
|  | 91 | +  ;   (first (str/split text #"\.\s")))) | 
|  | 92 | + | 
|  | 93 | +; (defn- inherits-javadoc? [text] | 
|  | 94 | +  ; (and text (str/includes? text "{@inheritDoc}"))) | 
|  | 95 | + | 
|  | 96 | +; (defn- build-import-map [^CompilationUnit cu] | 
|  | 97 | +  ; (let [imports (.getImports cu)] | 
|  | 98 | +  ;   (reduce (fn [acc ^com.github.javaparser.ast.ImportDeclaration import-decl] | 
|  | 99 | +  ;             (let [import-name (.getNameAsString import-decl)] | 
|  | 100 | +  ;               (if (.isAsterisk import-decl) | 
|  | 101 | +  ;                 acc | 
|  | 102 | +  ;                 (let [simple-name (last (str/split import-name #"\."))] | 
|  | 103 | +  ;                   (assoc acc simple-name import-name))))) | 
|  | 104 | +  ;           {} | 
|  | 105 | +  ;           imports))) | 
|  | 106 | + | 
|  | 107 | +; (defn- resolve-type-name [import-map package-name simple-name] | 
|  | 108 | +  ; (cond | 
|  | 109 | +  ;   (str/includes? simple-name ".") simple-name | 
|  | 110 | +  ;   (get import-map simple-name) (get import-map simple-name) | 
|  | 111 | +  ;   package-name (str package-name "." simple-name) | 
|  | 112 | +  ;   :else (str "java.lang." simple-name))) | 
|  | 113 | + | 
|  | 114 | +; (defn- get-parent-types [^CompilationUnit cu ^ClassOrInterfaceDeclaration class-decl] | 
|  | 115 | +  ; (let [import-map (build-import-map cu) | 
|  | 116 | +  ;       package-name (when-let [pkg (.orElse ^java.util.Optional (.getPackageDeclaration cu) nil)] | 
|  | 117 | +  ;                      (.getNameAsString pkg)) | 
|  | 118 | +  ;       extended (.getExtendedTypes class-decl) | 
|  | 119 | +  ;       implemented (.getImplementedTypes class-decl) | 
|  | 120 | +  ;       all-parents (concat extended implemented)] | 
|  | 121 | +  ;   (map (fn [^com.github.javaparser.ast.type.ClassOrInterfaceType parent] | 
|  | 122 | +  ;          (let [name (.getNameAsString parent) | 
|  | 123 | +  ;                base-name (first (str/split name #"<"))] | 
|  | 124 | +  ;            (resolve-type-name import-map package-name base-name))) | 
|  | 125 | +  ;        all-parents))) | 
|  | 126 | + | 
|  | 127 | +; (defn- print-method-summary [^ClassOrInterfaceDeclaration class-decl] | 
|  | 128 | +  ; (let [methods (.getMethods class-decl) | 
|  | 129 | +  ;       public-methods (filter #(.isPublic ^MethodDeclaration %) methods)] | 
|  | 130 | +  ;   (doseq [^MethodDeclaration method public-methods] | 
|  | 131 | +  ;     (let [method-name (.getNameAsString method) | 
|  | 132 | +  ;           javadoc-text (extract-javadoc method) | 
|  | 133 | +  ;           summary (first-sentence javadoc-text)] | 
|  | 134 | +  ;       (when summary | 
|  | 135 | +  ;         (println (str "* " method-name " - " summary))))))) | 
|  | 136 | + | 
|  | 137 | +; (defn- handle-error [^Exception e ^Class class] | 
|  | 138 | +  ; (if (str/includes? (.getMessage e) "Could not find artifact") | 
|  | 139 | +  ;   (println "No source JAR available for:" class) | 
|  | 140 | +  ;   (println "Error:" (.getMessage e)))) | 
|  | 141 | + | 
|  | 142 | +; (defn- print-member-javadoc [cu class-decl member-name] | 
|  | 143 | +  ; (let [javadoc-text (extract-javadoc (find-method class-decl member-name))] | 
|  | 144 | +  ;   (if (inherits-javadoc? javadoc-text) | 
|  | 145 | +  ;     (let [parents (get-parent-types cu class-decl)] | 
|  | 146 | +  ;       (println "Inherited from parent. See:") | 
|  | 147 | +  ;       (doseq [parent parents] | 
|  | 148 | +  ;         (println (str "  " parent "/" member-name)))) | 
|  | 149 | +  ;     (println (.convert (.build (FlexmarkHtmlConverter/builder)) ^String javadoc-text))))) | 
|  | 150 | + | 
|  | 151 | +; (defn- print-class-javadoc [class-decl] | 
|  | 152 | +  ; (when-let [class-javadoc (extract-javadoc class-decl)] | 
|  | 153 | +  ;   (println (.convert (.build (FlexmarkHtmlConverter/builder)) ^String class-javadoc)) | 
|  | 154 | +  ;   (println "\n--- Methods ---\n")) | 
|  | 155 | +  ; (print-method-summary class-decl)) | 
|  | 156 | + | 
|  | 157 | +; (defn javadoc* [^Class class member-name] | 
|  | 158 | +  ; (try | 
|  | 159 | +  ;   (let [coords (find-javadoc-coords class) | 
|  | 160 | +  ;         class-name (:class-name coords) | 
|  | 161 | +  ;         source-code (case (:protocol coords) | 
|  | 162 | +  ;                       :jrt (extract-source-from-jrt class-name) | 
|  | 163 | +  ;                       :jar (extract-source-from-jar (download-source-jar coords) class-name)) | 
|  | 164 | +  ;         cu (parse-java-source source-code) | 
|  | 165 | +  ;         class-decl (find-class-declaration cu class-name)] | 
|  | 166 | +  ;     (if member-name | 
|  | 167 | +  ;       (print-member-javadoc cu class-decl member-name) | 
|  | 168 | +  ;       (print-class-javadoc class-decl))) | 
|  | 169 | +  ;   (catch Exception e | 
|  | 170 | +  ;     (handle-error e class)))) | 
|  | 171 | + | 
|  | 172 | +; (defmacro javadoc | 
|  | 173 | +  ; "Get javadoc for a class or class member. | 
|  | 174 | +  ; Usage: | 
|  | 175 | +  ;   (javadoc String)                         ; class javadoc | 
|  | 176 | +  ;   (javadoc String/valueOf)                 ; method javadoc | 
|  | 177 | +  ;   (javadoc java.lang.String)               ; fully qualified class | 
|  | 178 | +  ;   (javadoc java.lang.String/valueOf)       ; fully qualified with method | 
|  | 179 | +  ;   (javadoc StringUtils/isEmpty)            ; 3rd party class method" | 
|  | 180 | +  ; [class-or-member] | 
|  | 181 | +  ; (let [class-or-member-str (str class-or-member) | 
|  | 182 | +  ;       parts (str/split class-or-member-str #"/") | 
|  | 183 | +  ;       class-sym (symbol (first parts)) | 
|  | 184 | +  ;       member-name (second parts)] | 
|  | 185 | +  ;   `(javadoc* ~class-sym ~member-name))) | 
0 commit comments