Interop from clojure a with non-standard iterative java API
Asked Answered
A

3

5

I am working in clojure with a java class which provides a retrieval API for a domain specific binary file holding a series of records.

The java class is initialized with a file and then provides a .query method which returns an instance of an inner class which has only one method .next, thus not playing nicely with the usual java collections API. Neither the outer nor inner class implements any interface.

The .query method may return null instead of the inner class. The .next method returns a record string or null if no further records are found, it may return null immediately upon the first call.

How do I make this java API work well from within clojure without writing further java classes?

The best I could come up with is:


(defn get-records
  [file query-params]
  (let [tr (JavaCustomFileReader. file)]
    (if-let [inner-iter (.query tr query-params)] ; .query may return null                                                               
      (loop [it inner-iter
             results []]
       (if-let [record (.next it)]
         (recur it (conj results record))
         results))
      [])))

This gives me a vector of results to work with the clojure seq abstractions. Are there other ways to expose a seq from the java API, either with lazy-seq or using protocols?

Airline answered 10/7, 2012 at 13:9 Comment(2)
This is an interesting question, but what do you mean by a non-standard Java API?Balcony
@Balcony specifically I mean the Java code doesn't provide a java.util.Iterator nor implement java.lang.Iterable . Logically the java code is providing iteration but without a connection to the standard API for doing so.Airline
B
4

Without dropping to lazy-seq:

(defn record-seq
  [q]
  (take-while (complement nil?) (repeatedly #(.next q))))

Instead of (complement nil?) you could also just use identity if .next does not return boolean false.

(defn record-seq
  [q]
  (take-while identity (repeatedly #(.next q))))

I would also restructure a little bit the entry points.

(defn query
  [rdr params]
  (when-let [q (.query rdr params)]
    (record-seq q)))

(defn query-file
  [file params]
  (with-open [rdr (JavaCustomFileReader. file)]
    (doall (query rdr params))))
Buffoon answered 11/7, 2012 at 6:17 Comment(0)
S
4

Your code is not lazy as it would be if you were using Iterable but you can fill the gap with lazy-seq as follows.

(defn query-seq [q]
  (lazy-seq
    (when-let [val (.next q)]
      (cons val (query-seq q)))))

Maybe you shoul wrap the query method to protect yourself from the first null value as well.

Stamper answered 10/7, 2012 at 17:44 Comment(1)
Move the lazy-seq up around the when-let.Ma
F
4

Seems like a good fit for lazy-seq:

(defn query [file query]
  (.query (JavaCustomFileReader. file) query))

(defn record-seq [query]
  (when query
    (when-let [v (.next query)]
      (cons v (lazy-seq (record-seq query))))))

;; usage:
(record-seq (query "filename" "query params"))
Fluoride answered 10/7, 2012 at 17:59 Comment(0)
B
4

Without dropping to lazy-seq:

(defn record-seq
  [q]
  (take-while (complement nil?) (repeatedly #(.next q))))

Instead of (complement nil?) you could also just use identity if .next does not return boolean false.

(defn record-seq
  [q]
  (take-while identity (repeatedly #(.next q))))

I would also restructure a little bit the entry points.

(defn query
  [rdr params]
  (when-let [q (.query rdr params)]
    (record-seq q)))

(defn query-file
  [file params]
  (with-open [rdr (JavaCustomFileReader. file)]
    (doall (query rdr params))))
Buffoon answered 11/7, 2012 at 6:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.