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

Accessing map via primitive does not work #196

Closed
ethlo opened this issue Apr 21, 2016 · 5 comments
Closed

Accessing map via primitive does not work #196

ethlo opened this issue Apr 21, 2016 · 5 comments

Comments

@ethlo
Copy link
Contributor

ethlo commented Apr 21, 2016

Accessing a java.util.Map<Integer, Integer> value by primitive value (int) will not return a value.

Expected:
{{map[1]}} should work

ethlo added a commit to ethlo/pebble that referenced this issue Apr 21, 2016
@ethlo
Copy link
Contributor Author

ethlo commented Apr 21, 2016

I see that the 1 is translated to a java.lang.Long, and thus the get method of the map will not consider it equal to an java.lang.Integer of the same number value.

@mbosecke
Copy link
Collaborator

You're exactly right, all literal numbers in Pebble are treated as Longs. I suspect it also doesn't work for Map<Short, ?> and Map<Byte,?> and possibly others. It would only work if your keys happen to be of type Long. This is a really tricky problem to solve because it's impossible to know the type of a Map at runtime due to type erasure.

The only solution I can think of at the moment is brute force; cast the Long into every known java primitive type and attempt to look up a value in the map with that type. This is completely insane so I'm going to have to continue to think about it and I'm open to any suggestions.

@DanStout
Copy link
Contributor

DanStout commented Oct 19, 2016

Well, we may not be able to determine the type of the map itself at runtime, but we can check the type of its keys. Perhaps the check could work like:

  • If the map is empty, do nothing
  • Else if we get the first key in the map and its type is numeric (i.e. Number.class.isAssignableFrom(key.getClass()) returns true) convert it into that type and use that with map.get() as shown in my little example below
  • Else do what is already being done (get using the plain key value)

Little test:

public static void main(String[] args)
{
    Map<Integer, String> origMap = new HashMap<>();
    int iIndex = 1;
    long lIndex = 1;
    origMap.put(iIndex, "Test");

    Map<?, ?> objMap = origMap;

    System.out.println("Map contains int 1: " + objMap.containsKey(iIndex));
    System.out.println("Map contains long 1: " + objMap.containsKey(lIndex));

    if (!objMap.isEmpty())
    {
        Set<?> set = objMap.keySet();
        Class<?> keyClass = set.iterator().next().getClass();
        // If we didn't know that lIndex was of type long, we would need to check if it was a
        // Number before casting it to that before calling the below method
        Object cast = cast(lIndex, keyClass);
        System.out.println("Map contains cast 1: " + objMap.containsKey(cast));
    }
}

private static Object cast(Number index, Class<?> desiredType)
{
    if (desiredType == Integer.class)
    {
        return index.intValue();
    }
    else if (desiredType == Byte.class)
    {
        return index.byteValue();
    }
    // Todo: Other types
    return null;
}

Outputs:

Map contains int 1: true
Map contains long 1: false
Map contains cast 1: true

For reference, GetAttributeExpression currently works like so:

if (object instanceof Map && ((Map<?, ?>) object).containsKey(attributeNameValue)) {
    return ((Map<?, ?>) object).get(attributeNameValue);
}

ebussieres added a commit that referenced this issue Apr 27, 2017
@ebussieres
Copy link
Member

Just commited a fix for this

@ethlo
Copy link
Contributor Author

ethlo commented Apr 27, 2017

Nice work!

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

No branches or pull requests

4 participants