API in Clojure for Java folklore

83 views
Skip to first unread message

ka

unread,
May 21, 2010, 8:28:22 PM5/21/10
to Clojure
Hi all,

I've written an API in Clojure to be consumed by people working in the
Java world. Now most of these people haven't even heard of Clojure;
but all they care about is 1) a working API and 2) with nice
documentation.

1) Something about the API - I tried out various things, types,
records, protocols (from the master). But finally I've decided to
stick with 2-3 defrecords + gen-class. The API is just a bunch of
static methods with zero state (earlier I was thinking fancy stuff
like OO, and keeping state for each object ... but didn't like any of
it). Anyway, so now I've a working API.

2) The problem I have now is how to put Javadocs in all my static
methods? I don't want to create documentation that is detached from
the code.

For example, if I declare a method in gen-class as -
#^{:static true} [myCoolFn [int int] clojure.lang.LazySeq]

and definition of the function-
(defn -myCoolFn
"My cool documentation"
[a b]
...
)

The .class file generated and hence the API -
2.1) Doesn't have the parameters named int a, int b :(. They are
named as int arg0, int arg1.
2.2) "My cool documentation" doesn't come up in the Javadocs.

I'm not much familiar with Java so please advise.
Thanks!

--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en

ka

unread,
May 23, 2010, 12:12:21 AM5/23/10
to Clojure
Hi, any responses?

Erik Söhnel

unread,
May 24, 2010, 4:05:21 AM5/24/10
to Clojure
hi,

you could use a simple class signature generator and then run javadoc
on it:

(def javadoc-strings (atom {}))

(defn javadoc [class name & body]
(swap! javadoc-strings update-in [class] #(conj (or % []) %2) [name
(apply str body)])
nil)

(defn -myMethod
"My cool documentation"
[x y]
'...)

(javadoc 'MyClass '-myMethod
"
/* .....
* " (:doc (meta #'-myMethod)) "
*/
public Object MyMethod(int x, int y) { };
")

(javadoc 'Bar 'one "
/*
*
*/
public Object one(int t) {};
")

(javadoc 'Bar 'two "
/*
*
*/
public Object one(int t) {};
")

(deftype Bar [x]
Foo
(one [t] (+ x 2))
(two [t] (+ x 2)))

(defn make-docclass [p c]
(let [cname (str c)
methods (into {} (get @javadoc-strings c))]
(str "package " p \; \newline
"public class " c "{" \newline
(apply str (interpose \newline (map second methods)))
"}")))

(clojure.contrib.io/make-parents (java.io.File. "<dir>/src/bar/foo/
Bar.java"))
(clojure.contrib.io/spit
"<dir>/src/bar/foo/Bar.java"
(make-docclass "bar.foo" 'Bar))

;; in the <dir> directory, call:
;; javadoc -sourcepath src bar.foo


Depending on your time and macro-foo skills, you can extend this to
automtically generate documentation from a genclass definition or a
whole namespace. But for a one-shot documentation of a simple API,
this might be enough.

Erik

Jason Smith

unread,
May 24, 2010, 11:01:16 AM5/24/10
to Clojure
It sure seems like we need a good stub generator for Clojure. Java-
interop just doesn't seem complete without it.

ka

unread,
May 26, 2010, 8:58:36 PM5/26/10
to Clojure
Hi all,

Inspired by Erik above, I've written a macro: gen-class+javadoc
(http://gist.github.com/415269).

It works quite well for me on Windows XP. For *nix, I think people
will have to make at least one change - the shell command (at line
57).

What it does -
1. Generates a javadoc for your API made using gen-class.

How to use -
1. Just use gen-class+javadoc macro instead of the gen-class macro.
_No other changes are required by the coder_.

Functioning -
1. It calls gen-class first as usual with the args.
2. Generates a .java file containing the javadoc.
3. Runs the javadoc command and generates docs in the doc/ directory.
4. Deletes the .java file created above.

Few points to note -
1. It is by no means complete. For eg. it doesn't handle interface
related stuff yet.
2. It uses macroexpand inside a macro, but some people on Clojure#
pointed out that this is not recommended. I'm very new to macro-foo,
so please let me know of the alternatives.
3. The macro itself contains a lot of side effects, dunno if this is a
very big issue.

Try it out if you expose an API through gen-class.

Any comments / suggestions / critiques are welcome.

- Thanks


ka

unread,
May 28, 2010, 5:36:42 PM5/28/10
to Clojure
I've also added a :class-doc key to the macro -

(gen-class+javadoc
:class-doc "Class Docs
<ul> <li> One </li> <li> Two </li> </ul>
"
:name "a.b.c.MyCoolClass"
:methods [
#^{:static true} [method1
[clojure.lang.PersistentArrayMap] int]
#^{:static true} [method2 [int int float]
clojure.lang.LazySeq]
#^{:static true} [method3 [] Object]
])

Thanks

Jason Smith

unread,
Jun 1, 2010, 2:42:11 PM6/1/10
to Clojure
[Note, you can implement this solution by writing stub classes by
hand. This means you write stubbed out Java classes, run Javac first,
and allow Clojure to overwrite the stubbed class files when it
compiles. This is easy to do in Maven, and not difficult in ANT. And
I have not actually tried this yet, so it's possible that there are
wrinkles that still need to be ironed out.]

For a general solution to this problem, which covers not only Javadoc
but also the compile-time dependency "chicken-and-egg" problem, I
believe we need two stages of compilation to be compatible with
Javac.

1) Generate Java stubs from Clojure.
2) Generate class files from Clojure.

These need to be separate steps, since (1) must be run before Javac
and (2) must be run afterwards.

Here is how it would work in the bigger scheme of things.

a) gen-stubs generates stubbed Java files. These files are actual
Java source, but the methods just throw NotImplementedException.
(1) includes Javadoc from Clojure metadata
(2) includes annotations
(3) includes Types, including generic definitions
b) Run Javac against Java classes and Clojure (Java) stubs.
Everything compiles, and the annotation processor (Java 5 and 6) is
happy as well.
c) Run Clojure compile, which overwrites all the stubbed classes from
step (b).

d) (OPTIONAL) Run Javadoc against Java + Clojure stubs. This will
produce complete, seamless Javadoc for consumption by Java users.

By using two steps and stubs, the chicken-and-egg problem is solved.
You don't get odd compile-time dependency problems. Even though there
are two separate compilations happening, it works as if there is only
one.

I am hoping that someone else will implement this solution before I
get around to needing it! :-)

Note also that this would allow you to combine Java, Groovy (which
already implements stubbed compilation), and Clojure all in the same
build with circular dependencies between all three languages.

ka

unread,
Jun 4, 2010, 10:03:11 AM6/4/10
to Clojure
@Jason

I'm supplying a Java API (implemented in Clojure) so needed a solution
quickly which just worked w/o me having to do special things.

Hence the gen-class+javadoc macro (http://gist.github.com/415269).
But I feel there should be something like this available in contrib
which handles the whole jing-bang of gen-class !

Currently I'm using the strategy of generating .class files from gen-
class, then generating java-stubs as required, running javadoc and
just deleting the java-stubs (as they provide no value in their own).

Why do you think keeping the java-stubs is necessary?

- Thanks


Jason Smith

unread,
Jun 4, 2010, 4:33:46 PM6/4/10
to Clojure
The Java stubs are, ideally, a temporary thing. They don't need to be
around forever. However, I know of no way at present to generate them
automatically.

Also, you are solving half the problem. Generating the stubs and
class files at the same time does not solve the compile-time
dependency problem. Consider:

Clojure A references Java class B references Clojure class C
references Java class D. A, B, C, D are in a single project.

You *must* run either gen-class or Javac first. Without some kind of
stubbing, the compile-time references are unknown, either way. Either
Java does not know about the Clojure gen-class classes that are to
come, or Clojure does not yet know about the Java classes in the
project.

So, treating Java as the least-common-denominator, you run:
* gen-stubs
* Javac
* gen-class

Since the stubs are around, you can also run JavaDoc.

The stubs don't need to be saved. In the Maven world, they are part of
the build, not source files.

ka

unread,
Jun 5, 2010, 7:41:16 AM6/5/10
to Clojure
Oh, now I understand fully your last post! I hadn't considered the
cyclic dependencies situation, thanks for pointing that out. Though I
can't see why right now; in future I might write some code (of mine)
in Java to be used from Clojure (maybe performance). In that
situation you are right we should -

1) Generate Java-stubs for Clojure gen-class, gen-interface.
My macro is a half-assed attempt to generate stubs automatically
using the gen-class args. One complexity is to choose return values
if return type is primitive.

2) Run javac to compile all java sources properly.

3) Run gen-class to replace stubbed .class files by Clojure
generated .class files.

4) Run javadoc to get the seamless documentation.

I'm sure I'm not qualified enough to try to code this up, given that I
haven't used maven or ant ever myself. But I'd like to give it a shot
given some guidance.

Do you think the right place for this is leiningen?

- Thanks

Jason Smith

unread,
Jun 7, 2010, 12:12:34 PM6/7/10
to Clojure
I think the right place for this is Maven, Ant, Leiningen, and command
line. It's a generic thing for any build system. :-) Generating
correct stubs is the common part, and then there is an integration
into each system. I'm most interested in having this for Maven, but
there's really not much to the Maven part of this (aside from some
rather arcane knowledge).

See also http://groups.google.com/group/clojure-maven-plugin/browse_thread/thread/1d710e7d75a564b7
(related)
> - Thanks- Hide quoted text -
>
> - Show quoted text -
Reply all
Reply to author
Forward
0 new messages