Reputation: 1341
In SWIG 3.0.8 there is no implementation for std::list
in the C++ to Java map, only std::vector
. This isn't very ideal for most cases, so I was wondering if it is possible to create my own SWIG definition of std::list
and how would I do so?
Upvotes: 1
Views: 685
Reputation: 88721
I've written a set of typemaps that should just work for wrapping std::list
nicely in Java. They use java.util.AbstractSequentialList
as a base class, so there's only ever one copy of data in existence and it works nicely as both a Java and C++ data structure. This answer is broadly an improvement and port of the same techniques I used in an older answer wrapping std::vector
similarly.
Firstly I pulled the 'autobox' typemap out of my older answer and into a standalone file, autobox.i since I'm now reusing it quite a lot:
// Java typemaps for autoboxing in return types of generics
%define AUTOBOX(CTYPE, JTYPE)
%typemap(autobox) CTYPE, const CTYPE&, CTYPE& "JTYPE"
%enddef
AUTOBOX(double, Double)
AUTOBOX(float, Float)
AUTOBOX(boolean, Boolean)
AUTOBOX(signed char, Byte)
AUTOBOX(short, Short)
AUTOBOX(int, Integer)
AUTOBOX(long, Long)
AUTOBOX(SWIGTYPE, $typemap(jstype,$1_basetype))
Then I used this in my std_list.i file below:
%include <autobox.i>
%include <stdint.i>
%{
#include <list>
#include <algorithm>
%}
namespace std {
template <typename T> class list {
public:
// This typedef is a weird hack to make stuff work
typedef std::list<T>::iterator iterator;
typedef size_t size_type;
typedef T value_type;
typedef T& reference;
void assign(size_type n, const value_type &val);
bool empty() const;
list(size_type n, const value_type &value=value_type());
list(const list &o);
list();
~list();
size_type max_size () const;
void pop_back();
void pop_front();
void push_back(const value_type &x);
void push_front(const value_type &x);
void remove(const T &v);
// Possible bug: jint != size_type
jint size () const;
void sort();
%javamethodmodifiers "private";
// Only for helping implement listIterator
iterator begin();
iterator insert(iterator pos, const value_type &v);
%extend {
static void set(iterator pos, const value_type& v) {
*pos = v;
}
jint previous_index(const iterator& pos) const {
return pos == self->begin() ? -1 : std::distance(self->begin(), static_cast<std::list<T>::const_iterator>(pos));
}
jint next_index(const iterator& pos) const {
return pos == self->end() ? self->size() : std::distance(self->begin(), static_cast<std::list<T>::const_iterator>(pos));
}
static iterator next(iterator pos) {
return ++pos;
}
static iterator previous(iterator pos) {
return --pos;
}
static value_type deref(const iterator& pos) {
return *pos;
}
static void advance(iterator& pos, jint index) {
std::advance(pos, index);
}
bool has_next(const iterator& pos) const {
return pos != $self->end();
}
}
%javamethodmodifiers "public";
};
}
%typemap(javaimports) std::list %{
import java.util.AbstractSequentialList;
import java.util.ListIterator;
import java.util.NoSuchElementException;
import java.util.Collection;
%}
%typemap(javabase) std::list "AbstractSequentialList<$typemap(autobox,$1_basetype::value_type)>"
#define JAVA_VALUE_TYPE $typemap(autobox,$1_basetype::value_type)
#define JAVA_ITERATOR_TYPE $typemap(jstype, $1_basetype::iterator)
%typemap(javacode,noblock=1) std::list {
public $javaclassname(Collection c) {
this();
ListIterator<JAVA_VALUE_TYPE> it = listIterator(0);
for (Object o: c) {
it.add((JAVA_VALUE_TYPE)o);
}
}
public ListIterator<JAVA_VALUE_TYPE> listIterator(int index) {
return new ListIterator<JAVA_VALUE_TYPE>() {
private JAVA_ITERATOR_TYPE pos;
private JAVA_ITERATOR_TYPE last;
private ListIterator<JAVA_VALUE_TYPE> init(int index) {
pos = $javaclassname.this.begin();
$javaclassname.advance(pos, index);
return this;
}
public void add(JAVA_VALUE_TYPE v) {
// Technically we can invalidate last here, but this makes more sense
last=$javaclassname.this.insert(pos, v);
}
public void set(JAVA_VALUE_TYPE v) {
if (null==last) {
throw new IllegalStateException();
}
$javaclassname.set(last, v);
}
public void remove() {
if (null==last) {
throw new IllegalStateException();
}
$javaclassname.this.remove(last);
last=null;
}
public int previousIndex() {
return $javaclassname.this.previous_index(pos);
}
public int nextIndex() {
return $javaclassname.this.next_index(pos);
}
public JAVA_VALUE_TYPE previous() {
if (previousIndex() < 0) {
throw new NoSuchElementException();
}
last = pos;
pos = $javaclassname.previous(pos);
return $javaclassname.deref(last);
}
public JAVA_VALUE_TYPE next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
last = pos;
pos = $javaclassname.next(pos);
return $javaclassname.deref(last);
}
public boolean hasPrevious() {
return previousIndex() != -1;
}
public boolean hasNext() {
return $javaclassname.this.has_next(pos);
}
}.init(index);
}
}
This file implements AbstractSequentialList
, which mostly just boils down to implementing ListIterator
. That's somewhat fiddly because the way Java implements the concept of an iterator is somewhat different to the C++ abstraction, although not completely different.
Our Java implementation of ListIterator
is mostly just a wrapper around an opaque C++ iterator and some glue to call in to a little extra C++ code that actually uses std::advance
, std::distance
and operator++
/operator--
to meet the requirements needed. Inside the glue are the various checks needed to make the interface safe/robust as a Java programmer expects.
The SWIG interface to std::list consists of the following major parts:
std::list
itself. (Some is private because it makes no sense to Java as anything other than an implementation detail)%template
inside SWIG later on.std::list
Some additional Java code for every std::list<X>
we wrap:
listIterator
abstract method that returns an anonymous type that glues everything together to meet all of the requirements for a mutable ListIterator
.This is wrapped inside { }
with noblock turned on so that the preprocessor macros happen, but that { }
doesn't get inserted into the Java which is generated.
I also used this Java trick to pass data to an anonymous class during construction (but could have used the double brace magic instead).
With that in place we can validate it by running writing a SWIG module, test.i:
%module test
%include "std_list.i"
%include <std_string.i>
%template(DoubleList) std::list<double>;
%template(StringList) std::list<std::string>;
And some actual Java to exercise it:
import java.util.ArrayList;
public class run {
public static void dump(java.util.AbstractCollection c) {
for (Object o: c) {
System.out.println(o);
}
}
public static void main(String[] argv) {
System.loadLibrary("test");
for (int i = 0; i < 1; ++i) {
go();
// System.gc();
}
}
public static void go() {
StringList sl = new StringList();
dump(sl);
sl.add(0,"HELLO"); // 1 arg form also worked
sl.add(1,"WORLD");
sl.add(2,"testing");
sl.add(3,"some more");
System.out.println(sl.size());
dump(sl);
sl = new StringList(new ArrayList<String>() {{
add("A");
add("B");
add("C");
}});
dump(sl);
}
}
Which works as expected:
swig3.0 -java -c++ -Wall test.i
javac *.java
g++ -Wall -Wextra -shared -o libtest.so test_wrap.cxx -I/usr/lib/jvm/default-java/include/ -I/usr/lib/jvm/default-java/include/linux -fPIC -D_GLIBCXX_DEBUG
LD_LIBRARY_PATH=. java run
Gives:
4
HELLO
WORLD
testing
some more
A
B
C
A random note: hasNext()
and forward iteration is probably faster than hasPrevious()
and reverse iteration simply because it's easier to avoid the std::distance
call int that case.
(Caveat: I read the Java docs on what the semantics of the ListIterator member functions should be in rather a hurry. I could have got one or more of them subtly wrong).
Upvotes: 1