hara.object think data, escape encapsulation

Author: Chris Zheng  (z@caudate.me)
Date: 29 June 2017
Repository: https://github.com/zcaudate/hara
Version: 2.5.10

1    Introduction

hara.object is a library for converting java classes into clojure data types. It is somewhat like the bean command but enables more control and customisation of the output.

1.1    Installation

Add to project.clj dependencies:

[im.chit/hara.object "2.5.10"]

All functionality is found contained in the hara.object namespace

(require '[hara.object :as object])

1.2    Motivation

hara.object works at the level of meta-programming. Below shows a simple example of the concept of Dog as an Object and as data:

fig.1  -  Class as Data

There are advantages of using pure data for the representation of the Dog concept.

  • generic methods con be used to manipulate the data
  • the entire structure is transparent is better for reasoning
  • simpler representation (though at the cost of Type Correctness)

In this way, many objects can be turned into maps/data for consumption by clojure methods. This makes working with many big java libraries much easier before.

2    Index

3    API

3.1    Core



map-like ^

creates an accessibility layer for map-like objects

v 2.3
(defmacro map-like
  [& {:as classes}]
  `(vector ~@(map (fn [[cls opts]]
                    `(map-like/extend-map-like ~cls ~opts))
                  classes)))
link
(map-like org.eclipse.jgit.revwalk.RevCommit {:tag "commit" :include [:commit-time :name :author-ident :full-message]}) (map-like org.eclipse.jgit.lib.PersonIdent {:tag "person" :exclude [:time-zone]}) (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))})

string-like ^

creates an accessibility layer for string-like objects

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

vector-like ^

creates an accessibility layer for vector-like objects

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

meta-read ^

accesses the read-attributes of an object

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

meta-write ^

accesses the write-attributes of an object

v 2.3
(defn meta-write
  [^Class cls]
  (assoc (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?})})})

3.2    Fields



to-data ^

creates the object from a string or map

v 2.3
(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.AbstractCollection obj)
          (to-data (.iterator ^java.util.AbstractCollection obj))

          :else obj)))
link
(read/to-data "hello") => "hello" (read/to-data (write/from-map {:name "hello" :species "dog"} Pet)) => (contains {:name "hello"})

from-data ^

creates the object from data

v 2.3
(defn from-data
  [arg ^Class cls]
  (let [^Class targ (type arg)]
    (cond
      ;; If there is a direct match
      (reflect-util/param-arg-match cls targ)
      arg

      ;; Special case for String/CharArray
      (and (string? arg) (= cls (Class/forName "[C")))
      (.toCharArray arg)
      
      ;; 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"]

read-all-getters ^

returns fields of an object and base classes

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

read-getters ^

returns fields of an object through getter methods

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

read-reflect-fields ^

fields of an object from reflection

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

write-all-setters ^

write all setters of an object and base classes

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

write-reflect-fields ^

write fields of an object from reflection

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

write-setters ^

write fields of an object through setter methods

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