jvm.artifact
Maven coordinates, repository paths, and artifact representations
Normalize dependency coordinates and convert them between records, vectors, strings, paths, files, and repository metadata.
1 Overview
jvm.artifact centers on the Rep record. A representation stores group, artifact, extension, classifier, version, scope, exclusions, properties, and file information in one value.
2 Walkthrough
2.1 Normalize coordinates
(require '[jvm.artifact :as artifact])
(def clojure-rep
(artifact/rep '[org.clojure/clojure "1.11.1"]))
(artifact/rep? clojure-rep)
(artifact/rep->coord clojure-rep)
(artifact/rep->string clojure-rep)
(artifact/rep->path clojure-rep)
2.2 Convert output formats
Use artifact when the caller chooses an output tag dynamically. Direct conversion functions are clearer when the format is fixed.
(artifact/artifact :string '[org.clojure/clojure "1.11.1"])
(artifact/artifact :path "org.clojure:clojure:1.11.1")
(artifact/artifact :default '[org.clojure/clojure "1.11.1"])
3 Supporting namespaces
jvm.artifact.common contains repository path and resource-entry helpers. jvm.artifact.search searches artifact contents and class entries.
4 API
- artifact
- artifact-coord
- artifact-default
- artifact-path
- artifact-string
- artifact-symbol
- coord->rep
- coord?
- path->rep
- rep
- rep->coord
- rep->path
- rep->string
- rep-default
- rep?
- string->rep
v 3.0
(defn artifact
([x]
(artifact :default x))
([type x]
(protocol.classloader/-artifact type x)))
link
(artifact :string '[hara/hara "2.4.0"]) => "hara:hara:jar:2.4.0" (artifact :path "hara:hara:2.4.0") => (str base/*local-repo* "/hara/hara/2.4.0/hara-2.4.0.jar")
artifact-coord ^
[x] [_ x]
converts an artifact in any format to the coord representation
v 3.0
(invoke/definvoke artifact-coord
[:method {:multi protocol.classloader/-artifact
:val :coord}]
([x]
(artifact-coord nil x))
([_ x]
(-> (rep x) rep->coord)))
link
(artifact-coord "hara:hara:jar:2.4.0") => '[hara/hara "2.4.0"]
artifact-default ^
[x] [_ x]
converts an artifact in any format to the default representation
v 3.0
(invoke/definvoke artifact-default
[:method {:multi protocol.classloader/-artifact
:val :default}]
([x]
(artifact-default nil x))
([_ x]
(rep x)))
link
(artifact-default '[hara/hara "2.4.0"]) => rep?
artifact-path ^
[x] [_ x]
converts an artifact in any format to the path representation
v 3.0
(invoke/definvoke artifact-path
[:method {:multi protocol.classloader/-artifact
:val :path}]
([x]
(artifact-path nil x))
([_ x]
(if (and (string? x)
(.endsWith ^String x "jar"))
x
(-> (rep x)
rep->path))))
link
(artifact-path '[hara/hara "2.4.0"]) => (str base/*local-repo* "/hara/hara/2.4.0/hara-2.4.0.jar")
artifact-string ^
[x] [_ x]
converts an artifact in any format to the string representation
v 3.0
(invoke/definvoke artifact-string
[:method {:multi protocol.classloader/-artifact
:val :string}]
([x]
(artifact-string nil x))
([_ x]
(-> (rep x) rep->string)))
link
(artifact-string '[hara/hara "2.4.0"]) => "hara:hara:jar:2.4.0"
artifact-symbol ^
[x] [_ x]
converts an artifact in any format to the symbol representation
v 3.0
(invoke/definvoke artifact-symbol
[:method {:multi protocol.classloader/-artifact
:val :symbol}]
([x]
(artifact-symbol nil x))
([_ x]
(-> (rep x) rep->coord first)))
link
(artifact-symbol '[hara/hara "2.4.0"]) => 'hara/hara
coord->rep ^
[[name version & {:keys [scope exclusions]}]]
converts a coord to a rep instance
v 3.0
(defn coord->rep
([[name version & {:keys [scope exclusions]}]]
(let [[group artifact] (clojure.string/split (str name) #"/")
artifact (or artifact
group)]
(Rep. group artifact "jar" nil version {} nil scope exclusions))))
link
(coord->rep '[hara/hara "2.4.0"]) => (contains {:group "hara" :artifact "hara" :version "2.4.0"})
v 3.0
(defn coord?
([obj]
(and (vector? obj)
(symbol? (first obj))
(string? (second obj)))))
link
(coord? '[org.clojure/clojure "1.8.0"]) => true (coord? '[1 2 3]) => false
v 3.0
(defn path->rep
([x]
(let [arr (->> (re-pattern base/*sep*)
(clojure.string/split (.replaceAll ^String x base/*local-repo* ""))
(remove empty?))
extension (-> (last arr)
(clojure.string/split #".")
last)
version (last (butlast arr))
artifact (last (butlast (butlast arr)))
group (clojure.string/join "." (butlast (butlast (butlast arr))))]
(Rep. group artifact extension nil version {} x nil nil nil nil))))
link
(path->rep (str base/*local-repo* "/hara/hara/2.4.0/hara-2.4.0.jar")) => (contains {:group "hara" :artifact "hara" :version "2.4.0"})
v 3.0
(defn rep
([obj]
(protocol.classloader/-rep obj)))
link
(str (rep '[hara/hara "2.4.0"])) => "hara:hara:jar:2.4.0" (str (rep "hara:hara:2.4.0")) => "hara:hara:jar:2.4.0"
rep->coord ^
[{:keys [group artifact version exclusions scope]}]
encodes the rep to a coordinate
v 3.0
(defn rep->coord
([{:keys [group artifact version exclusions scope]}]
(filterv identity (concat [(symbol group artifact) version]
(if exclusions [:exclusions exclusions])
(if scope [:scope scope])))))
link
(-> {:group "hara" :artifact "hara" :version "2.4.0"} (map->Rep) (rep->coord)) => '[hara/hara "2.4.0"]
v 3.0
(defn rep->path
([{:keys [group artifact version extension]}]
(clojure.string/join base/*sep*
[base/*local-repo* (.replaceAll ^String group "\." base/*sep*)
artifact version (str artifact "-" version "." (or extension "jar"))])))
link
(-> {:group "hara" :artifact "hara" :version "2.4.0"} (map->Rep) (rep->path)) => #"/hara/hara/2.4.0/hara-2.4.0.jar"
rep->string ^
[{:keys [group artifact extension version]}]
encodes the rep to a string
v 3.0
(defn rep->string
([{:keys [group artifact extension version]}]
(clojure.string/join ":" [group
artifact
(if extension
(str extension ":" version)
version)])))
link
(-> {:group "hara" :artifact "hara" :version "2.4.0"} (map->Rep) (rep->string)) => "hara:hara:2.4.0"
v 3.0
(invoke/definvoke rep-default
[:method {:multi protocol.classloader/-rep
:val :default}]
([x]
(cond (instance? Rep x) x
(map? x) (map->Rep x)
(coord? x) (coord->rep x)
(string? x)
(if (.startsWith ^String x base/*local-repo*)
(path->rep x)
(string->rep x))
(symbol? x)
(coord->rep [x])
:else
(throw (Exception. (str "Invalid form: (" (type x) ") " x))))))
link
(into {} (rep-default "hara:hara:2.4.0")) => {:properties {}, :group "hara", :classifier nil, :file nil, :exclusions nil, :scope nil, :extension "jar", :artifact "hara", :version "2.4.0"}
v 3.0
(defn string->rep
([s]
(let [[group artifact extension? classifer? version :as array] (clojure.string/split s #":")]
(case (count array)
1 (path->rep s)
2 (Rep. group artifact "jar" nil nil {} nil nil nil)
3 (Rep. group artifact "jar" nil extension? {} nil nil nil)
4 (Rep. group artifact extension? nil classifer? {} nil nil nil)
5 (Rep. group artifact extension? classifer? version {} nil nil nil)))))
link
(string->rep "hara:hara:2.4.0") => (contains {:group "hara" :artifact "hara" :version "2.4.0"})
- *java-class-path*
- *java-home*
- *java-runtime-jar*
- *local-repo*
- *sep*
- resource-entry
- resource-entry-symbol
*java-class-path* ^
NONE
(defonce ^:dynamic *java-class-path*
(->> (clojure.string/split (System/getProperty "java.class.path") #":")
(filter (fn [^String x] (.endsWith x ".jar")))))
link
*java-runtime-jar* ^
NONE
(defonce ^:dynamic *java-runtime-jar*
(str *java-home* "/lib/rt.jar"))
link
*local-repo* ^
NONE
(defonce ^:dynamic *local-repo*
(-> (System/getProperty "user.home")
(str *sep* ".m2" *sep* "repository")))
link
v 3.0
(defn resource-entry
([x]
(condp = (type x)
String x
Symbol (resource-entry-symbol x)
Class (-> (.getName ^Class x)
(.replaceAll "\." *sep*)
(str ".class")))))
link
(resource-entry "hello/world.txt") => "hello/world.txt" (resource-entry 'version-clj.core) => "version_clj/core.clj" (resource-entry java.io.File) => "java/io/File.class"
v 3.0
(defn resource-entry-symbol
([sym]
(let [sym-str (-> (str sym)
(.replaceAll "\." *sep*)
(.replaceAll "-" "_"))
f-char (-> sym-str (clojure.string/split (re-pattern *sep*)) last first)]
(str sym-str
(if (<= (int A) (int f-char) (int Z))
".class"
".clj")))))
link
(resource-entry-symbol 'code.test) => "code/test.clj" (resource-entry-symbol 'clojure.lang.AFn) => "clojure/lang/AFn.class"
v 3.0
(defn class-seq
([] (class-seq nil))
([coords]
(->> (for [jar (map #(artifact/artifact :path %) coords)
item (archive/list jar)]
(str item))
(filter #(.endsWith ^String % ".class"))
(map #(.substring ^String % 1 (- (.length ^String %) 6)))
(map #(.replaceAll ^String % "/" ".")))))
link
(some #{"clojure.lang.RT"} (class-seq '[[org.clojure/clojure "1.12.0"]])) => "clojure.lang.RT"
v 3.0
(defn search
([matches classes]
(let [match-fns (map search-match matches)
pre-fns (filter #(-> % meta :pre) match-fns)
post-fns (filter #(-> % meta :post) match-fns)]
(->> classes
(filter (fn [cls] (every? #(% cls) pre-fns)))
(map #(Class/forName %))
(filter (fn [cls] (every? #(% cls) post-fns)))
(sort-by #(.getName ^Class %))))))
link
(->> (search [#"^java.lang." java.lang.CharSequence] ["java.lang.String" "java.util.ArrayList"]) (map #(.getName ^Class %))) => ["java.lang.String"]
v 3.0
(defn search-match
([match]
(cond (fn? match)
^:post (fn [cls]
(boolean (match cls)))
(instance? Class match)
^:post (fn [cls] (.isAssignableFrom ^Class match cls))
(instance? java.util.regex.Pattern match)
^:pre (fn [cls] (boolean (re-find match cls)))
(string? match)
(let [pat (re-pattern match)]
^:pre (fn [cls]
(boolean (re-find pat cls))))
:else (throw (Exception. (str "Cannon compile match: " match))))))
link
((search-match #"hello") "hello.world") => true ((search-match java.util.List) java.util.ArrayList) => true