Obj icon indicating copy to clipboard operation
Obj copied to clipboard

Obj#setActiveGroupNames() fails if used on an immutable collection

Open Igrium opened this issue 4 years ago • 6 comments

The method Obj#setActiveGroupNames() fails with a NullPointerException if used on a collection generated by Set.of() or List.of(). This is because those collections throw an exception if you try to test if they contain null, which you do when in the block:

else if (groupNames.contains(null))
{
    throw new NullPointerException("The groupNames contains null");
}

This is pretty annoying because if you're trying to add a face to only one group, that's what you'll be using 90% of the time.

Igrium avatar Nov 07 '21 23:11 Igrium

It does not generally fail on an immutable collection, but only on one that does not permit null elements (and does the corresponding check in contains).

And... what should happen when someone does call this function with a null element?

One can write

obj.setActiveGroupNames(Arrays.asList("a", "b"));

or use Collections#singleton* for single elements, so I don't think that's an issue.

javagl avatar Nov 08 '21 14:11 javagl

Still worth noting as a bug; it shouldn't have taken me digging through the source code to figure it out. A simple try/catch in the implementation would fix it.

Igrium avatar Nov 08 '21 23:11 Igrium

The documentation of that method says that it will throw a NullPointerException when the collection contains null elements. It's reasonable to assume that it does so by checking whether the collection contains null elements, right? (I rather find the fact that set.contains(null) throws an NPE a bit obscure).

A simple try/catch in the implementation would fix it.

I'm curious what your suggested "simple" solution would look like.

javagl avatar Nov 09 '21 00:11 javagl

Something along these lines?

    @Override
    public void setActiveGroupNames(Collection<? extends String> groupNames)
    {
        if (groupNames == null)
        {
            return;
        }
        if (groupNames.size() == 0)
        {
            groupNames = Arrays.asList("default");
        } else {
            boolean containsNull = false
            try {
                containsNull = groupNames.contains(null)
            } catch(NullPointerException e) {}
            if (containsNull) {
                throw new NullPointerException("The groupNames contains null");
            }
        }
        nextActiveGroupNames = 
            Collections.unmodifiableSet(new LinkedHashSet<String>(groupNames));
    }

(I wrote this without an IDE so the syntax may be wrong)

Igrium avatar Nov 09 '21 01:11 Igrium

It was that boolean and the empty catch block that I wondered about, and is hard to justify.

Just using obj.setActiveGroupNames(Arrays.asList("example")); should be fine.

javagl avatar Nov 09 '21 02:11 javagl

Instead of that try-catch, I'd rather consider something like

for (String groupName : groupNames) {
    if (groupName == null) throw up;
}

for the check,

But I'm still a bit baffled by the NPE that Set#of is throwing for contains(null). I do not see any reason whatsoever (and not even the slightest technical justification) to not just return false in this case...

javagl avatar Nov 09 '21 13:11 javagl