clojure gen-class generated classes invocation issue - clojure

I defined the following MyCache.clj
(ns abcd.MyCache
(:gen-class
:name "abcd.MyCache"
:init "init"
:constructors { [java.text.DateFormat][] }
:methods [ [now [] void] [myformat [long] String] ]
:state "state"
:main false))
(defn -init[format]
([[] (atom {:format format})]))
(defn -now[this] ( (:format #(.state this)) (System/currentTimeMillis)))
(defn -myformat[this time]
( (:format #(.state this) (new java.util.Date time))))
I compiled the above file using (compile 'abcd.MyCache) successfully.
When I am trying to use the generated classes as shown below..I am getting errors. Please help.
user=> (new abcd.MyCache (new java.text.SimpleDateFormat "mmDDyyyy"))
IllegalArgumentException Key must be integer clojure.lang.APersistentVector.invoke (APersistentVector.java:265)

I don't feel well about this:
(defn -init[format]
([] [atom {:format format}]))
You are trying to get an element from a vector and it is expects an index (number).
What is correct is to deref the atom and get its value as the index of the vector. But again in your case, you are trying to query an empty vector.
Notice also, that [atom {:format format}] isn't the correct way to create an atom. You should use:
(atom {:format format})
And by the way, the following form is the preferred one to create Java objects (nothing wrong with (new) of course):
(Date.)
(DateFormat.)

Related

Convert from data.json to cheshire

I am completely new to Clojure. I still struggle with reading functions sometimes.
I am trying to change this function to use checkshire.
Here is my attempt :
defn- json->messages [json]
(let [records (:amazon.aws.sqs/records (cheshire/decode
json
:key-fn key-reader
:value-fn value-reader))
add-origin-queue (fn [record]
(let [event-source-arn (:amazon.aws.sqs/event-source-arn record)
queue-name (arn->queue-name event-source-arn)]
(assoc record :amazon.aws.sqs/queue-name queue-name)))]
(map add-origin-queue records)))
The function key-reader function:
(def ^:private
key-reader
(memoize (fn [key]
(let [kebab-key (if (= "md5OfBody" key)
"md5-of-body"
(csk/->kebab-case key))]
(keyword "amazon.aws.sqs" kebab-key)))))
The function :
(def ^:private
value-reader
(memoize (fn [key value]
(if (= key :amazon.aws.sqs/receipt-handle)
value-reader
value))))
I than call the function like so :
(json->messages msg)
msg is a json string.
However I am getting the error below with that attempt :
Execution error (ArityException) at tech.matterindustries.titan.ion.lambda.sqs-receive/json->messages (sqs_receive.clj:36).
Wrong number of args (5) passed to: cheshire.core/parse-smile
You are sending the wrong number of args to cheshire.core/parse-smile. Do you have a piece of sample data?
Please also keep your code clean & formatted, like this:
(defn- json->messages
[json]
(let [records (:amazon.aws.sqs/records (cheshire/decode json :key-fn key-reader :value-fn value-reader))
add-origin-queue (fn [record]
(let [event-source-arn (:amazon.aws.sqs/event-source-arn record)
queue-name (arn->queue-name event-source-arn)]
(assoc record :amazon.aws.sqs/queue-name queue-name)))]
(map add-origin-queue records)))
I could not find decode in the Cheshire docs, but in the source it has this:
(def decode "Alias to parse-string for clojure-json users" parse-string)
I am disappointed in their incomplete docs.
A quick google shows the docs:
(parse-string string & [key-fn array-coerce-fn])
Returns the Clojure object corresponding to the given JSON-encoded string.
An optional key-fn argument can be either true (to coerce keys to keywords),
false to leave them as strings, or a function to provide custom coercion.
The array-coerce-fn is an optional function taking the name of an array field,
and returning the collection to be used for array values.
This may not be clear. What it means is there are 3 legal ways to call parse-string:
(parse-string <json-str>)
(parse-string <json-str> <key-fn>)
(parse-string <json-str> <key-fn> <array-coerce-fn>)
So you can call it with 1, 2, or 3 args. You cannot add in :key-fn or :value-fn map keys, as in your example.
Please also note that your key-reader and value-reader look like they do not match what cheshire/read-string is expecting.

ClassNotFoundException when trying to use class created using gen-class

My code:
(ns model.document
(:gen-class
:name model.document
:implements java.io.Serializable
:state "state"
:init "init"
:constructors {[String String String] []}
:methods [[getContent [] String]
[getTitle [] String]
[getUrl [] String]]))
(defn -init [content title url]
[[] (atom {:content content
:title title
:url url})])
(defn- get-field [this k]
(#(.state this) k))
(defn getContent [this]
(get-field this :content))
(defn getTitle [this]
(get-field this :title))
(defn getUrl [this]
(get-field this :url))
And it's use:
(ns classification-server.classifier
(:require [model.document :refer :all]))
(new model.document "my-content" "my-title" "my-url")
And I get the unhelpful:
Caused by: java.lang.ClassNotFoundException: model.document, compiling:(classification_server/classifier.clj:13:12)
Please help me SO. You're my only hope...
The gen-class namespace you posted doesn’t compile, because the :implements specification expects a vector of symbols, not a symbol. If you change that line to
:implements [java.io.Serializable]
you will be able to compile (and instantiate) the class – however, it will not be functional, because there are other issues with your gen-class spec,such as the prefixed functions being absent (-getContent etc.).
I suggest you read the gen-class documentation and simplify the
problem further.

How do I get the instance of the class when using gen-class

I want to use the instance of the class constructed via gen-class in a method of the class.
How do I access it? What do I have insert for "this" in the following example:
(ns example
(:gen-class))
(defn -exampleMethod []
(println (str this)))
Or is it impossible when using gen-class?
The first argument of Clojure functions corresponding to a method generated by gen-class takes the current object whose method is being called.
(defn -exampleMethod [this]
(println (str this)))
In addition to this, you have to add a :methods option to gen-class when you define a method which comes from neither a superclass nor interfaces of the generated class. So a complete example would be as follows.
example.clj
(ns example)
(gen-class
:name com.example.Example
:methods [[exampleMethod [] void]])
(defn- -exampleMethod
[this]
(println (str this)))
REPL
user> (compile 'example)
example
user> (.exampleMethod (com.example.Example.))
com.example.Example#73715410
nil

Using Clojure's data structure with MapDB

I tried to use directly Clojure's hashmap with MapDB and ran into weird behaviour. I checked Clojure and MapDB sources and couldn't understand the problem.
First everything looks fine:
lein try org.mapdb/mapdb "1.0.6"
; defining a db for the first time
(import [org.mapdb DB DBMaker])
(defonce db (-> (DBMaker/newFileDB (java.io.File. "/tmp/mapdb"))
.closeOnJvmShutdown
.compressionEnable
.make))
(defonce fruits (.getTreeMap db "fruits-store"))
(do (.put fruits :banana {:qty 2}) (.commit db))
(get fruits :banana)
=> {:qty 2}
(:qty (get fruits :banana))
=> 2
(first (keys (get fruits :banana)))
=> :qty
(= :qty (first (keys (get fruits :banana))))
=> true
CTRL-D
=> Bye for now!
Then I try to access the data again:
lein try org.mapdb/mapdb "1.0.6"
; loading previsously created db
(import [org.mapdb DB DBMaker])
(defonce db (-> (DBMaker/newFileDB (java.io.File. "/tmp/mapdb"))
.closeOnJvmShutdown
.compressionEnable
.make))
(defonce fruits (.getTreeMap db "fruits-store"))
(get fruits :banana)
=> {:qty 2}
(:qty (get fruits :banana))
=> nil
(first (keys (get fruits :banana)))
=> :qty
(= :qty (first (keys (get fruits :banana))))
=> false
(class (first (keys (get fruits :banana))))
=> clojure.lang.Keyword
How come the same keyword be different with respect to = ?
Is there some weird reference problem happening ?
The problem is caused by the way equality of keywords works. Looking at the
implementation of the = function we see that since keywords are not
clojure.lang.Number or clojure.lang.IPersistentCollection their equality is
determined in terms of the Object.equals method. Skimming the source of
clojure.lang.Keyword we learn that keywords don't not override
Object.equals and therefore two keywords are equal iff they are the same
object.
The default serializer of MapDB is org.mapdb.SerializerPojo, a subclass of
org.mapdb.SerializerBase. In its documentation we can read that
it's a
Serializer which uses ‘header byte’ to serialize/deserialize most of classes
from ‘java.lang’ and ‘java.util’ packages.
Unfortunately, it doesn't work that well with clojure.lang classes; It doesn't
preserve identity of keywords, thus breaking equality.
In order to fix it let's attempt to write our own serializer using the
EDN format—alternatively, you could consider, say, Nippy—and use
it in our MapDB.
(require '[clojure.edn :as edn])
(deftype EDNSeralizer []
;; See docs of org.mapdb.Serializer for semantics.
org.mapdb.Serializer
(fixedSize [_]
-1)
(serialize [_ out obj]
(.writeUTF out (pr-str obj)))
(deserialize [_ in available]
(edn/read-string (.readUTF in)))
;; MapDB expects serializers to be serializable.
java.io.Serializable)
(def edn-serializer (EDNSeralizer.))
(import [org.mapdb DB DBMaker])
(def db (.. (DBMaker/newFileDB (java.io.File. "/tmp/mapdb"))
closeOnJvmShutdown
compressionEnable
make))
(def more-fruits (.. db
(createTreeMap "more-fruits")
(valueSerializer (EDNSeralizer.))
(makeOrGet)))
(.put more-fruits :banana {:qty 2})
(.commit db)
Once the more-fruits tree map is reopened in a JVM with EDNSeralizer defined
the :qty object stored inside will be the same object as any other :qty
instance. As a result equality checks will work properly.

clojure gen-class varargs constructor

in the :constructors map and subsequent -init definitions, how do I represent a varargs constructor (assuming the superclass has multiple constructors of which one is varargs) ?
Since varargs are essentially syntax sugar for Object arrays, you could just use "[Ljava.lang.Object;" as the type of constructor's parameter.
Here's some sample code:
(ns t.vtest
(:gen-class
:implements [clojure.lang.IDeref]
:init init
:state state
:constructors {["[Ljava.lang.Object;"] []}))
;; ^-----------------------
;; You should put "[Ljava.lang.Object;" for superclass varargs constructor here
;; I left it blank for the sake of working example
(defn -init
[args]
(println "first element of args" (aget args 0) "total elements" (alength args))
[[] (into [] args)])
(defn -deref
[this]
(.state this))
and that's how it looks in REPL
user=> #(t.vtest. (into-array Object ["A" "B" 1 2]))
first element of args A total elements 4
["A" "B" 1 2]
Since clojure don't support it at the moment you need to patch it with: https://groups.google.com/forum/#!topic/clojure/HMpMavh0WxA.
And use it with new meta tag:
(ns t.vtest
(:gen-class
:implements [clojure.lang.IDeref]
:init init
:state state
:constructors {^:varargs ["[Ljava.lang.Object;"] []}
))