Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

No iterator for Java when using SWIG with C++'s std::map

I have implemented a class with std::map in C++ and created interface using SWIG to be call from Java. However there is no iterator object that allows me to iterate through the entries in the SWIG wrapped std::map. Does anyone know how to create an iterator?

like image 686
delita Avatar asked Feb 27 '12 13:02

delita


1 Answers

In order to be able to iterate over an Object in Java it needs to implement Iterable. This in turn requires a member function called iterator() which returns a suitable implementation of an Iterator.

From your question it's not quite clear if what types you're using in the map and if you want to be able to iterate over the pairs (as you would in C++), the keys or the values. The solutions to the three variants are substantially similar, my example below picked the values.

First things first, the preamble for the SWIG interface file I used to test this:

%module test

%include "std_string.i"
%include "std_map.i"

In order to implement the iterable map I've declared, defined and wrapped another class in the SWIG interface file. This class, MapIterator implements the Iterator interface for us. It's a mixture of both Java and wrapped C++, where one was easier than the other to write. Firstly some Java, a typemap that gives the interface it implements and then two of the three methods required for the Iterable interface, given as a typemap:

%typemap(javainterfaces) MapIterator "java.util.Iterator<String>"
%typemap(javacode) MapIterator %{
  public void remove() throws UnsupportedOperationException {
    throw new UnsupportedOperationException();
  }

  public String next() throws java.util.NoSuchElementException {
    if (!hasNext()) {
      throw new java.util.NoSuchElementException();
    }

    return nextImpl();
  }
%}

Then we supply the C++ part of MapIterator, which has a private implementation of all but the exception throwing part of next() and the state needed for the iterator (expressed in terms of std::map's own const_iterator).

%javamethodmodifiers MapIterator::nextImpl "private";
%inline %{
  struct MapIterator {
    typedef std::map<int,std::string> map_t;
    MapIterator(const map_t& m) : it(m.begin()), map(m) {}
    bool hasNext() const {
      return it != map.end();
    }

    const std::string& nextImpl() {
      const std::pair<int,std::string>& ret = *it++;
      return ret.second;
    }
  private:
    map_t::const_iterator it;
    const map_t& map;    
  };
%}

Finally we need to tell SWIG that the std::map we're wrapping implements the Iterable interface and provide an extra member function for the purposes of wrapping std::map which returns a new instance of the MapIterator class we just wrote:

%typemap(javainterfaces) std::map<int,std::string> "Iterable<String>"

%newobject std::map<int,std::string>::iterator() const;
%extend std::map<int,std::string> {
  MapIterator *iterator() const {
    return new MapIterator(*$self);
  }
}

%template(MyMap) std::map<int,std::string>;

This could be more generic, with macros for example to hide the types of the map such that if you have multiple maps it's just a question of "calling" the macro for the appropriate maps just like you do with %template.

There's also a slight complication with maps of primitive types - you'll need to arrange for the Java side to use Double/Integer instead of double/int (autoboxing I believe is the term), unless you decided to wrap pairs already in which case you could make a pair with primitive members.

like image 184
Flexo Avatar answered Sep 22 '22 13:09

Flexo