object object to data

Author: Chris Zheng  (z@caudate.me)
Date: 27 November 2018
Repository: https://github.com/zcaudate/hara
Version: 3.0.2

1    Introduction

hara.object allows flexible construction of configuration files.

1.1    Installation

Add to project.clj dependencies:

[hara/object "3.0.2"]

All functions are in the hara.object namespace.

 (use (quote hara.object))

2    Element



class-convert ^

converts a class to its representation.

v 3.0
(defn class-convert
  ([v] (class-convert v :class))
  ([v to] (-class-convert v to)))
link
(class-convert "byte") => Byte/TYPE (class-convert 'byte :string) => "byte" (class-convert (Class/forName "[[B") :string) => "byte[][]"

class-hierarchy ^

lists the class and interface hierarchy for the class

v 3.0
(defn class-hierarchy
  [obj]
  (let [t (common/context-class obj)]
    (vec (cons t (inheritance/ancestor-tree t)))))
link
(element/class-hierarchy String) => [java.lang.String [java.lang.Object #{java.io.Serializable java.lang.Comparable java.lang.CharSequence}]]

class-info ^

lists class information

v 3.0
(defn class-info
  [obj]
  (select-keys (type/seed :class (common/context-class obj))
               [:name :hash :modifiers]))
link
(element/class-info String) => (contains {:name "java.lang.String" :hash anything :modifiers #{:instance :class :public :final}})

constructor? ^

checks if if an element is a constructor

v 3.0
(defn constructor?
  [elem]
  (-> elem :tag (= :constructor)))
link
(-> (.getConstructors String) (first) (element/to-element) (element/constructor?)) => true

context-class ^

if x is a class, return x otherwise return the class of x

v 3.0
(defn context-class
  [obj]
  (if (class? obj) obj (type obj)))
link
(context-class String) => String (context-class "") => String

element ^

creates a element from a map

v 3.0
(defn element
  [body]
  (Element. body))
link
(element {}) => hara.object.element.common.Element

element? ^

checker for the element type

v 3.0
(defn element?
  [x]
  (instance? Element x))
link
(element? (element {})) => true

field? ^

checks if an element is a field

v 3.0
(defn field?
  [elem]
  (-> elem :tag (= :field)))
link
(-> (.getFields String) (first) (element/to-element) (element/field?)) => true

instance? ^

checks if an element is non static

v 3.0
(defn instance?
  [elem]
  (-> elem :modifiers :instance boolean))
link
(->> (.getMethods String) (map element/to-element) (filter element/instance?) first) ;;#elem[equals :: (java.lang.String, java.lang.Object) -> boolean]

method? ^

checks if an element is a method

v 3.0
(defn method?
  [elem]
  (-> elem :tag (= :method)))
link
(-> (.getMethods String) (first) (element/to-element) (element/method?)) => true

plain? ^

checks if an element is neither public or private

v 3.0
(defn plain?
  [elem]
  (-> elem :modifiers :plain boolean))
example not found

private? ^

checks if an element is private

v 3.0
(defn private?
  [elem]
  (-> elem :modifiers :private boolean))
link
(->> (.getDeclaredFields String) (map element/to-element) (filter element/private?) first) ;;#elem[value :: (java.lang.String) | byte[]]

public? ^

checks if an element is public

v 3.0
(defn public?
  [elem]
  (-> elem :modifiers :public boolean))
link
(->> (.getMethods String) (map element/to-element) (filter element/public?) first) ;;#elem[equals :: (java.lang.String, java.lang.Object) -> boolean]

static? ^

checks if an element is a static one

v 3.0
(defn static?
  [elem]
  (and (-> elem :modifiers :static boolean)
       (not (constructor? elem))))
link
(->> (.getMethods String) (map element/to-element) (filter element/static?) first) ;;#elem[valueOf :: (int) -> java.lang.String]

to-element ^

converts a `java.reflect` object to a `hara.object.element` one

v 3.0
(definvoke to-element
  [:memoize {:arglists '([obj])
             :function common/-to-element}])
link
(element/to-element (first (.getMethods String))) ;; #elem[equals :: (java.lang.String, java.lang.Object) -> boolean]

3    Query



delegate ^

allow transparent field access and manipulation to the underlying object.

v 3.0
(defn delegate
  [obj]
 (let [fields (->> (map (juxt (comp keyword :name) identity) (query-instance obj [:field]))
                   (into {}))]
   (Delegate. obj fields)))
link
(def -a- "hello") (def -*a- (delegate -a-)) (def -world-array- (.getBytes "world")) (mapv char (-*a- :value)) => [h e l l o] (-*a- :value -world-array-) (String. (-*a- :value)) => "world" -a- => "world"

query-class ^

queries the java view of the class declaration

v 3.0
(defn query-class
  [obj selectors]
  (select-class-elements (common/context-class obj) selectors))
link
(query-class String [#"^c" :name]) ;;=> ["charAt" "checkBounds" "codePointAt" "codePointBefore" ;; "codePointCount" "compareTo" "compareToIgnoreCase" ;; "concat" "contains" "contentEquals" "copyValueOf"]

query-hierarchy ^

lists what methods could be applied to a particular instance

v 3.0
(defn query-hierarchy
  [obj selectors]
  (let [grp (input/args-group selectors)
        tcls (common/context-class obj)
        elems (concat (select-class-elements tcls selectors)
                      (select-supers-elements tcls selectors))]
    (if (and (seq elems)
             (common/element? (first elems)))
      (order/order grp elems)
      (sort (set elems)))))
link
(query-hierarchy String [:name #"^to"]) => ["toCharArray" "toLowerCase" "toString" "toUpperCase"]

query-instance ^

lists what class methods could be applied to a particular instance

v 3.0
(defn query-instance
  [obj selectors]
  (let [tcls (type obj)]
    (select-instance-elements tcls (if (class? obj) obj) selectors)))
link
(query-instance "abc" [:name #"^to"]) => ["toCharArray" "toLowerCase" "toString" "toUpperCase"] (query-instance String [:name #"^to"]) => (contains ["toString"])

4    Framework



from-data ^

creates the object from data

v 3.0
(defn from-data
  [arg ^Class cls]
  (let [^Class targ (type arg)]
    (cond
      ;; If there is a direct match
      (or (element.util/param-arg-match cls targ)
          (element.util/param-float-match cls targ))
      arg
      
      ;; Special case for String/CharArray
      (and (string? arg) (= cls (Class/forName "[C")))
      (.toCharArray arg)

      ;; Special case for Enums
      (enum/enum? cls)
      (if (string? arg)
        (enum/to-enum arg cls)
        (throw (ex-info "Only strings supported for Enums" {:input arg
                                                            :type cls})))

      ;; If there is a vector
      (and (vector? arg)
           (.isArray cls))
      (let [cls (.getComponentType cls)]
        (->> arg
             (map #(from-data % cls))
             (into-array cls)))

      :else
      (let [{:keys [from-clojure from-string from-vector] :as mobj} (meta-write cls)]
        (cond

          from-clojure (from-clojure arg)

          ;; If input is a string and there is a from-string method
          (and (string? arg) from-string)
          (from-string arg)

          ;; If input is a string and there is a from-string method
          (and (vector? arg) from-vector)
          (from-vector arg)

          ;; If the input is a map
          (map? arg)
          (from-map arg cls)

          :else
          (throw (Exception. (format "Problem converting %s to %s" arg cls))))))))
link
(-> (write/from-data ["hello"] (Class/forName "[Ljava.lang.String;")) seq) => ["hello"]

get ^

accessor with either keyword or array lookup

v 3.0
(defn get
  [obj k]
  (cond (keyword? k)
        (get-with-keyword obj k)

        (.isArray (type obj))
        (nth obj k)

        (sequential? k)
        (get-with-array obj k)))
link
(access/get (test.Cat. "spike") :name) => "spike"

get-in ^

accesses the nested object using specifiedb path

v 3.0
(defn get-in
  [obj ks]
  (cond (empty? ks)
        obj
        
        :else
        (get-in (get obj (first ks)) (rest ks))))
link
(access/get-in (test.Cat. "spike") [:name])

keys ^

gets all keys of an object

v 3.0
(defn keys
  [obj]
  (-> obj type read/meta-read :methods clojure.core/keys))
link
(access/keys (test.Cat. "spike")) => (contains [:name])

map-like ^

creates an accessibility layer for map-like objects

v 3.0
(defmacro map-like
  [& {:as classes}]
  `(vector ~@(map (fn [[cls opts]]
                    `(map-like/extend-map-like ~cls ~opts))
                  classes)))
link
(framework/map-like org.eclipse.jgit.revwalk.RevCommit {:tag "commit" :include [:commit-time :name :author-ident :full-message]}) (framework/map-like org.eclipse.jgit.lib.PersonIdent {:tag "person" :exclude [:time-zone]}) (framework/map-like org.eclipse.jgit.api.Status {:tag "status" :display (fn [m] (reduce-kv (fn [out k v] (if (and (or (instance? java.util.Collection v) (instance? java.util.Map v)) (empty? v)) out (assoc out k v))) {} m))})

meta-read ^

access read-attributes with caching

v 3.0
(definvoke meta-read
  [:memoize]
  ([^Class cls]
   (assoc (protocol.object/-meta-read cls) :class cls)))
link
(read/meta-read Pet) => (contains-in {:class test.Pet :methods {:name fn? :species fn?}})

meta-write ^

access read-attributes with caching

v 3.0
(definvoke meta-write
  [:memoize]
  ([^Class cls]
   (assoc (protocol.object/-meta-write cls) :class cls)))
link
(write/meta-write DogBuilder) => (contains {:class test.DogBuilder :empty fn?, :methods (contains {:name (contains {:type java.lang.String, :fn fn?})})})

read-all-getters ^

returns fields of an object and base classes

v 3.0
(defn read-all-getters
  ([cls] (read-all-getters cls +read-get-opts+))
  ([cls {:keys [prefix template extra] :as opts}]
   (read-getters cls opts query/query-hierarchy)))
link
(-> (read/read-all-getters Dog) keys) => [:class :name :species]

read-fields ^

fields of an object from reflection

v 3.0
(defn read-fields
  ([cls]
   (read-fields cls query/query-class))
  ([cls query-fn]
   (->> (query-fn cls [:field])
        (map (juxt (comp keyword string/spear-case :name)
                   identity))
        (into {}))))
link
(-> (read/read-fields Dog) keys) => [:name :species]

read-getters ^

returns fields of an object through getter methods

v 3.0
(defn read-getters
  ([cls] (read-getters cls +read-get-opts+))
  ([cls opts] (read-getters cls +read-get-opts+ query/query-class))
  ([cls {:keys [prefix template extra] :as opts} query-fn]
   (->> [:method :instance :public (re-pattern (str "^" prefix ".+")) 1]
        (query-fn cls)
        (reduce (fn [out ele]
                  (conj out (create-read-method ele prefix template extra)))
                {}))))
link
(-> (read/read-getters Dog) keys) => [:name :species]

set ^

sets the fields of an object with a map

v 3.0
(defn set
  ([obj m]
   (reduce-kv (fn [obj k v]
                (set-with-keyword obj k v)
                obj)
              obj
              m)
   obj)
  ([obj k v]
   (set-with-keyword obj k v)
   obj))
link
(-> (doto (test.Cat. "spike") (access/set {:name "fluffy"})) (access/get :name)) => "fluffy"

string-like ^

creates an accessibility layer for string-like objects

v 3.0
(defmacro string-like
  [& {:as classes}]
  `(vector ~@(map (fn [[cls opts]]
                    `(string-like/extend-string-like ~cls ~opts))
                  classes)))
link
(framework/string-like java.io.File {:tag "path" :read (fn [f] (.getPath f)) :write (fn [^String path] (java.io.File. path))}) (object/to-data (java.io.File. "/home")) => "/home" (object/from-data "/home" java.io.File) => java.io.File ;; Enums are automatically string-like (object/to-data java.lang.Thread$State/NEW) => "NEW"

struct-accessor ^

creates an accessor function

v 3.0
(defn struct-accessor
  ([spec]
   (struct-accessor spec :getter))
  ([spec access]
   (case access
     :field  (fn field  [obj] (struct-fields spec obj))
     :getter (fn getter [obj] (struct-getters spec obj)))))
link
((struct-accessor {:value [:data] :msg [:detail-message :value]} :field) (ex-info "hello" {:a 1})) => (contains {:value {:a 1}, :msg bytes?})

struct-fields ^

creates a struct given an object and a field map

v 3.0
(defn struct-fields
  [spec obj]
  (let [cls (type obj)]
    (cond (vector? spec)
          ((apply comp (reverse (struct-field-functions spec cls))) obj)
          
          (map? spec)
          (reduce (fn [out [k entry]]
                    (cond (nil? obj)
                          out
                          
                          (vector? entry)
                          (let [entry (if (empty? entry)
                                        [k]
                                        entry)]
                            (assoc out k (struct-fields entry obj)))
                          
                          (map? entry)
                          (assoc out k (struct-fields entry ((field-function k cls) obj)))))
                  {}
                  spec))))
link
(struct-fields {:depth []} (ex-info "hello" {:a 1})) => (contains {:depth number?}) (struct-fields {:msg [:detail-message :value]} (ex-info "hello" {:a 1})) => (contains {:msg bytes?})

struct-getters ^

creates a struct given an object and a getter map

v 3.0
(defn struct-getters
  [spec obj]
  (cond (vector? spec)
        ((apply comp (map getter-function (reverse spec))) obj)
        
        (map? spec)
        (reduce (fn [out [k entry]]
                  (cond (nil? obj)
                        out
                        
                        (vector? entry)
                        (let [entry (if (empty? entry)
                                      [k]
                                      entry)]
                          (assoc out k (struct-getters entry obj)))
                        
                        (map? entry)
                        (assoc out k (struct-getters entry ((getter-function k) obj)))))
                {}
                spec)
        :else (throw (ex-info "Not valid." {:spec spec}))))
link
(struct-getters {:value [:data] :message [] :class {:name []}} (ex-info "hello" {:a 1})) => {:value {:a 1}, :message "hello", :class {:name "clojure.lang.ExceptionInfo"}}

to-data ^

creates the object from a string or map

v 3.0
(defn to-data
  [obj]
  (let [cls (type obj)
        {:keys [to-clojure to-string to-map to-vector methods]} (meta-read cls)]
    (cond (nil? obj) nil

          (instance? java.util.Map obj)
          obj
          
          to-clojure (to-clojure obj)

          to-string (to-string obj)

          to-map (to-map obj)

          to-vector (to-vector obj)

          methods  (reduce-kv
                    (fn [out k func]
                      (if-some [v (func obj)]
                        (assoc out k (to-data v))
                        out))
                    {}
                    methods)
          
          (.isArray ^Class cls)
          (->> (seq obj)
               (mapv to-data))

          (instance? java.lang.Iterable obj)
          (mapv to-data obj)

          (instance? java.util.Iterator obj)
          (->> obj iterator-seq (mapv to-data))

          (instance? java.util.Enumeration obj)
          (->> obj enumeration-seq (mapv to-data))

          (instance? java.util.AbstractCollection obj)
          (to-data (.iterator ^java.util.AbstractCollection obj))

          (instance? java.lang.Enum obj)
          (str obj)
          
          :else obj)))
link
(read/to-data "hello") => "hello" (read/to-data (write/from-map {:name "hello" :species "dog"} Pet)) => (contains {:name "hello"})

to-map ^

creates a map from an object

v 3.0
(defn to-map
  [obj]
  (let [cls (type obj)
        {:keys [to-map methods]} (meta-read cls)]
    (cond (nil? obj) nil

          (instance? java.util.Map obj)
          obj

          to-map (to-map obj)

          methods (reduce-kv (fn [out k func]
                               (if-some [v (func obj)]
                                 (assoc out k (to-data v))
                                 out))
                             {}
                             methods)

          :else
          (throw (Exception. (str "Cannot process object: " obj))))))
link
(read/to-map (Cat. "spike")) => (contains {:name "spike"})

unextend ^

unextend a given class from the object framework

v 3.0
(defn unextend
  [cls]
  [(fn/multi-remove protocol.object/-meta-read cls)
   (fn/multi-remove protocol.object/-meta-write cls)
   (fn/multi-remove print-method cls)
   (fn/memoize-remove read/meta-read cls)
   (fn/memoize-remove write/meta-write cls)])
link
(framework/unextend org.eclipse.jgit.lib.PersonIdent) ;;=> [[#multifn[-meta-read 0x4ead3109] nil #multifn[print-method 0xcd219d4]]]

vector-like ^

creates an accessibility layer for vector-like objects

v 3.0
(defmacro vector-like
  [& {:as classes}]
  `(vector ~@(map (fn [[cls opts]]
                    `(vector-like/extend-vector-like ~cls ~opts))
                  classes)))
link
(framework/vector-like org.eclipse.jgit.revwalk.RevWalk {:tag "commits" :read (fn [^org.eclipse.jgit.revwalk.RevWalk walk] (->> walk (.iterator) object/to-data))})

write-all-setters ^

write all setters of an object and base classes

v 3.0
(defn write-all-setters
  ([cls] (write-all-setters cls {}))
  ([cls opts]
   (write-setters cls opts query/query-hierarchy)))
link
(write/write-all-setters Dog) => {} (keys (write/write-all-setters DogBuilder)) => [:name]

write-fields ^

write fields of an object from reflection

v 3.0
(defn write-fields
  ([cls]
   (write-fields cls query/query-class))
  ([cls query-fn]
   (->> (query-fn cls [:field])
        (reduce (fn [out ele]
                  (let [k (-> ele :name string/spear-case keyword)
                        cls (.getType (get-in ele [:all :delegate]))]
                    (assoc out k {:type cls :fn ele})))
                {}))))
link
(-> (write/write-fields Dog) keys) => [:name :species]

write-setters ^

write fields of an object through setter methods

v 3.0
(defn write-setters
  ([cls] (write-setters cls {}))
  ([cls opts] (write-setters cls opts query/query-class))
  ([cls {:keys [prefix template]
         :or {prefix "set"
              template +write-template+}} query-fn]
   (->> [:method :instance :public (re-pattern (str "^" prefix ".+")) 2]
        (query-fn cls)
        (reduce (fn [out ele]
                  (conj out (create-write-method ele prefix template)))
                {}))))
link
(write/write-setters Dog) => {} (keys (write/write-setters DogBuilder)) => [:name]