Evil Washing Machine
Evil Washing Machine

Reputation: 1341

How would you create a typemap for std::list<std::string> to List<String> in C++ to Java in SWIG?

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

Answers (1)

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:

  1. Declaration of the relevant parts of std::list itself. (Some is private because it makes no sense to Java as anything other than an implementation detail)
  2. Some extra private code to let us instantiate some of the templated C++ iterator code we need when we use %template inside SWIG later on.
  3. Setting up imports/baseclass for std::list
  4. Some helper macros to make it easier to write 'the type that Java knows an opaque handle to a C++ iterator as' and 'the type that Java thinks our container holds, including any autoboxing if needed' as they get written a lot.
  5. Some additional Java code for every std::list<X> we wrap:

    1. Another constructor as recommended by Java collections interface for copying from one collection to another. (This one uses the Java iterator we created to do it fairly efficiently).
    2. An implementation of 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

Related Questions