Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to create a Map<str, byte[]> #550

Open
AbdealiLoKo opened this issue Jul 15, 2020 · 8 comments
Open

Unable to create a Map<str, byte[]> #550

AbdealiLoKo opened this issue Jul 15, 2020 · 8 comments

Comments

@AbdealiLoKo
Copy link
Contributor

AbdealiLoKo commented Jul 15, 2020

I am using a java library which expects me to send a Map<str, byte[]>.

I am unable to create Map<str, byte[]> in java. This I what have tried:

>>> import jnius
>>> jMap       = jnius.autoclass('java.util.HashMap')
>>> jString    = jnius.autoclass('java.lang.String')
>>> jByteArrayInputStream = autoclass('java.io.ByteArrayInputStream')
>>> mymap = jMap()

>>> mymap.put('a', b'xyz')
>>> type(mymap.get('a'))
str

>>> mymap.put('a', bytearray(b'xyz'))
JavaException: No methods matching your arguments, requested: ('a', bytearray(b'xyz')), available: ['(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;']

>>> mymap.put('a', jByteArrayInputStream(b'xyz').buf)
JavaException: No methods matching your arguments, requested: ('a', <jnius.ByteArray object at 0x7f0bb8884170>), available: ['(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;']

>>> mymap.put('a', [b'x', b'y', b'z'])
TypeError: an integer is required

>>> mymap.put('a', jString('xyz').getBytes())
JavaException: No methods matching your arguments, requested: ('a', <jnius.ByteArray object at 0x7f0bb8884170>), available: ['(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;']

I seem to be unable to create a Map<str, byte[]> right now and I am not sure how to do it ...

I am using:

  • Ubuntu 16.04
  • Python 3.6
  • Jnius 1.3.0
@hx2A
Copy link
Member

hx2A commented Jul 15, 2020

The problem here is this:

>>> mymap.put.signatures()
[(['java/lang/Object', 'java/lang/Object'], 'java/lang/Object')]

When pyjnius is converting the python variables to Java it uses the method signature to decide how to do the conversion before making the actual Java method call.

In Java you would write something like new HashMap<String, Byte[]>() to create the HashMap and the <String, Byte[]> would tell it what types to use. (As far as I know) there is no way to do the same thing in pyjnius. In Java if you wrote new HashMap() it would assume <Object, Object>, which is what you are getting here.

I looked through the code and I don't see a way to fix this without breaking other stuff. Is it possible for you to use a work-around like this?

class MyMap {

  Map<String, Byte[]> map;

  public MyMap() {
    this.map = new HashMap<String, Byte[]>();
  }

  public Map<String, Byte[]> getMap() {
    return map;
  }

  public Byte[] put(String k, byte[] v) {
    Byte[] array = new Byte[v.length];
    for (int i = 0; i < v.length; i++) {
      array[i] = v[i];
    }
    return map.put(k, array);
  }
}

Alternatively, you could try not using autoclass and instead create the pyjnius class yourself with the put method that has the appropriate signature. Not sure if that would work, but it might be worth a try if you don't want to use the above code.

@cmacdonald
Copy link
Contributor

@hx2A could another kwarg be used to force the type conversion?
I already plan to have one force the selection of the method signature.

@hx2A
Copy link
Member

hx2A commented Jul 15, 2020

@cmacdonald a kwarg could be used to do that but the HashMap object would still be HashMap<Object, Object>() and not HashMap<String, Byte[]>(), which is what @AbdealiJK needs. What if there was a way to pass kwargs to the constructor to have same effect as <String, Byte[]>?

Also note that in my simple example code above,

>>> myMap = MyMap()

>>> myMap.getMap().put.signatures()
[(['java/lang/Object', 'java/lang/Object'], 'java/lang/Object')]

>>> myMap.getMap().put('a', b'asdf')

>>> myMap.getMap().get('a')
'asdf'

>>> type(myMap.getMap().get('a'))
str

A kwarg that had the same effect as <String, Byte[]> wouldn't be enough.

@cmacdonald
Copy link
Contributor

the generics is just compiler decoration; the hashmap will store whatever object we give it in JNI. My understanding is this is some of the array conversion mapping that Jnius does. I'm not sure if it is in the way in or out. Way to test would be to put in Python and then read the object classname from Java (with generics turned off)

@hx2A
Copy link
Member

hx2A commented Jul 15, 2020

In jnius_conversion.pxi there is the function populate_args that does the argument conversion. In the above example, this block of code is converting the bytes to a string:

            elif isinstance(py_arg, base_string) and jstringy_arg(argtype):
                j_args[index].l = convert_pystr_to_java(
                    j_env, to_unicode(py_arg)
                )

What we would want instead is for it to use the convert_pyarray_to_java call found at the end:

            j_args[index].l = convert_pyarray_to_java(
                    j_env, argtype[1:], py_arg)

It would do this if we could force the argtype to be different.

I'm still not convinced this will solve @AbdealiJK 's problem though. If they create a HashMap like above and add nothing to it, it still will not work with a "java library which expects a Map<str, byte[]>". Right?

@cmacdonald
Copy link
Contributor

AIUI, at bytecode level:

Map<String,byte[]> map
byte[] arr = map.get("a");

is compiled to something equivalent to:

Map map;
byte[] arr = (byte[]) map.get("a");

Hence

If they create a HashMap like above and add nothing to it, it still will not work with a "java library which expects a Map<str, byte[]>

I believe yes

@hx2A
Copy link
Member

hx2A commented Jul 15, 2020

I believe yes

Interesting. I did some testing and I believe you are correct.

So perhaps all we need to do is make the kwarg you suggested?

@cmacdonald
Copy link
Contributor

cmacdonald commented Aug 4, 2021

I have rediscovered this bug. It seems impossible for a PythonJavaClass to return byte[] and for Java to recognise it as such.
Example at https://colab.research.google.com/drive/12qOcbVPE6m4NEAJ7D6ZquaKi6yi-o0TK?usp=sharing

I worked around it by having my Python method populate and return a ByteBuffer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants