Skip to content

Add support for wildcards on the CustomClassMapper. #792

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

Merged
merged 4 commits into from
Sep 13, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion firebase-database/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# 19.2.0
- [changed] Added support for type wildcards in GenericTypeIndicator, expanding
our custom class serialization to include classes with wildcard generics
(#792).

# 19.1.1
- [fixed] Fixed a crash that occurred when we attempted to start a network
connection during app shutdown (#672).
Expand Down Expand Up @@ -26,6 +31,6 @@
- [internal] Removed ``@PublicApi` annotations as they are no longer enforced
and have no semantic meaning.

# 16.0.6
# 16.0.6
- [fixed] Fixed an issue that could cause a NullPointerException during the
initial handshake with the Firebase backend (#119).
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,24 @@ private static <T> T deserializeToType(Object o, Type type) {
} else if (type instanceof Class) {
return deserializeToClass(o, (Class<T>) type);
} else if (type instanceof WildcardType) {
throw new DatabaseException("Generic wildcard types are not supported");
Type[] lowerBounds = ((WildcardType) type).getLowerBounds();
if (lowerBounds.length > 0) {
throw new DatabaseException("Generic lower-bounded wildcard types are not supported");
}

// Upper bounded wildcards are of the form <? extends Foo>. Multiple upper bounds are allowed
// but if any of the bounds are of class type, that bound must come first in this array. Note
// that this array always has at least one element, since the unbounded wildcard <?> always
// has at least an upper bound of Object.
Type[] upperBounds = ((WildcardType) type).getUpperBounds();
hardAssert(upperBounds.length > 0, "Wildcard type " + type + " is not upper bounded.");
return deserializeToType(o, upperBounds[0]);
} else if (type instanceof TypeVariable) {
// As above, TypeVariables always have at least one upper bound of Object.
Type[] upperBounds = ((TypeVariable<?>) type).getBounds();
hardAssert(upperBounds.length > 0, "Wildcard type " + type + " is not upper bounded.");
return deserializeToType(o, upperBounds[0]);

} else if (type instanceof GenericArrayType) {
throw new DatabaseException(
"Generic Arrays are not supported, please use Lists " + "instead");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import androidx.annotation.Keep;
import com.google.firebase.database.core.utilities.encoding.CustomClassMapper;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
Expand Down Expand Up @@ -628,6 +629,22 @@ public String getValue() {
}
}

private static class MultiBoundedMapBean<T extends String & Serializable> {
private Map<String, T> values;

public Map<String, T> getValues() {
return values;
}
}

private static class MultiBoundedMapHolderBean {
private MultiBoundedMapBean<String> map;

public MultiBoundedMapBean<String> getMap() {
return map;
}
}

private static class StaticFieldBean {
public static String value1 = "static-value";
public String value2;
Expand Down Expand Up @@ -1763,6 +1780,33 @@ public void subclassingGenericTypeIndicatorIsAllowed() {
assertEquals("foo", bean.value);
}

@Test
public void usingWildcardInGenericTypeIndicatorIsAllowed() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also add tests for:

  1. wildcard with lowerbound throws
  2. wildcard with multiple upperBounds uses the first one

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PTAL.

Map<String, String> fooMap = Collections.singletonMap("foo", "bar");
assertEquals(
fooMap,
CustomClassMapper.convertToCustomClass(
fooMap, new GenericTypeIndicator<Map<String, ? extends String>>() {}));
}

@Test(expected = DatabaseException.class)
public void usingLowerBoundWildcardsIsForbidden() {
Map<String, String> fooMap = Collections.singletonMap("foo", "bar");
CustomClassMapper.convertToCustomClass(
fooMap, new GenericTypeIndicator<Map<String, ? super String>>() {});
}

@Test
public void multiBoundedWildcardsUsesTheFirst() {
Map<String, Object> source =
Collections.singletonMap(
"map", Collections.singletonMap("values", Collections.singletonMap("foo", "bar")));
MultiBoundedMapHolderBean bean =
CustomClassMapper.convertToCustomClass(source, MultiBoundedMapHolderBean.class);
Map<String, String> expected = Collections.singletonMap("foo", "bar");
assertEquals(expected, bean.map.values);
}

@Test(expected = DatabaseException.class)
public void unknownTypeParametersNotSupported() {
deserialize("{'value': 'foo'}", new GenericTypeIndicatorSubclass<GenericBean<?>>() {});
Expand Down