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

Add parser for Optional types to avoid the null madness #78

Merged
merged 2 commits into from
Sep 14, 2015
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
45 changes: 45 additions & 0 deletions src/main/java/com/suse/saltstack/netapi/parser/JsonParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
Expand All @@ -31,9 +32,11 @@
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.reflect.Type;
import java.lang.reflect.ParameterizedType;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
* Parser for Saltstack responses.
Expand Down Expand Up @@ -79,6 +82,7 @@ public JsonParser(TypeToken<T> type) {
.registerTypeAdapter(StartTime.class, new StartTimeAdapter().nullSafe())
.registerTypeAdapter(Stats.class, new StatsDeserializer())
.registerTypeAdapter(Arguments.class, new ArgumentsDeserializer())
.registerTypeAdapterFactory(new OptionalTypeAdapterFactory())
.create();
}

Expand Down Expand Up @@ -126,6 +130,47 @@ public Date read(JsonReader jsonReader) throws IOException {
}
}

/**
* TypeAdaptorFactory creating TypeAdapters for Optional
*/
private class OptionalTypeAdapterFactory implements TypeAdapterFactory {

@Override
@SuppressWarnings("unchecked")
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
Copy link
Member

Choose a reason for hiding this comment

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

Public methods should generally have javadoc, consider to add some documentation.

Copy link
Member Author

Choose a reason for hiding this comment

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

create is overridden from TypeAdapterFactory so its already documented there i put the @Override on it to make it clear.

Type type = typeToken.getType();
boolean isOptional = typeToken.getRawType() == Optional.class;
boolean isParameterized = type instanceof ParameterizedType;
if (isOptional && isParameterized) {
Type elementType = ((ParameterizedType) type).getActualTypeArguments()[0];
TypeAdapter<?> elementAdapter = gson.getAdapter(TypeToken.get(elementType));
return (TypeAdapter<T>) optionalAdapter(elementAdapter);
} else {
return null;
}
}

private <A> TypeAdapter<Optional<A>> optionalAdapter(TypeAdapter<A> innerAdapter) {
return new TypeAdapter<Optional<A>>() {
@Override
public Optional<A> read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return Optional.empty();
} else {
A value = innerAdapter.read(in);
return Optional.of(value);
}
}

@Override
public void write(JsonWriter out, Optional<A> optional) throws IOException {
Copy link
Member

Choose a reason for hiding this comment

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

Aren't read() and write() overrides from TypeAdapter? Let's put the @Override annotation?

innerAdapter.write(out, optional.orElse(null));
}
};
}
}

/**
* Deserializer for the Stats object received from the API.
*/
Expand Down
37 changes: 37 additions & 0 deletions src/test/java/com/suse/saltstack/netapi/parser/JsonParserTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.google.gson.JsonParseException;
import com.suse.saltstack.netapi.calls.wheel.Key;
import com.google.gson.reflect.TypeToken;
import com.suse.saltstack.netapi.datatypes.Arguments;
import com.suse.saltstack.netapi.datatypes.Job;
import com.suse.saltstack.netapi.datatypes.ScheduledJob;
Expand All @@ -14,7 +15,9 @@
import com.suse.saltstack.netapi.datatypes.Token;
import java.util.Date;
import java.util.Arrays;
import java.util.Optional;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;
import org.junit.Test;

Expand All @@ -24,6 +27,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertFalse;

/**
* Json parser unit tests.
Expand Down Expand Up @@ -202,6 +206,39 @@ public void testSaltStackJobsWithKwargsParser() throws Exception {
assertEquals("lucid", job.getUser());
}

@Test
public void testOptionalParser() {
InputStream is = this.getClass()
.getResourceAsStream("/optional_parser_test.json");
JsonParser<OptionalTest> parser = new JsonParser<>(new TypeToken<OptionalTest>(){});
OptionalTest result = parser.parse(is);
assertFalse(result.nullString.isPresent());
assertFalse(result.absentString.isPresent());
result.valueString.ifPresent((value) ->
assertEquals("string with value", value)
);
List<Optional<Integer>> expected = new LinkedList<>();
expected.add(Optional.of(1));
expected.add(Optional.of(2));
expected.add(Optional.of(3));
expected.add(Optional.empty());
expected.add(Optional.of(5));
assertEquals(expected, result.maybeInts);
}

@Test
public void testOptionalSingleValue() {
JsonParser<Optional<Integer>> parser =
new JsonParser<>(new TypeToken<Optional<Integer>>(){});
InputStream nullValue = this.getClass()
.getResourceAsStream("/single_null_value.json");
assertFalse(parser.parse(nullValue).isPresent());

InputStream intValue = this.getClass()
.getResourceAsStream("/single_int_value.json");
assertEquals(new Integer(123), parser.parse(intValue).get());
}

@Test
public void testSaltStackJobsWithArgsAsKwargsParser() throws Exception {
InputStream is = this.getClass()
Expand Down
15 changes: 15 additions & 0 deletions src/test/java/com/suse/saltstack/netapi/parser/OptionalTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.suse.saltstack.netapi.parser;

import java.util.Optional;
import java.util.List;

/**
* Helper Type to test the Optional Parser
*/
public class OptionalTest {

public Optional<String> nullString = Optional.empty();
public Optional<String> valueString = Optional.empty();
public Optional<String> absentString = Optional.empty();
public List<Optional<Integer>> maybeInts;
}
5 changes: 5 additions & 0 deletions src/test/resources/optional_parser_test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"nullString": null,
"valueString": "string with value",
"maybeInts": [1, 2, 3, null, 5]
}
1 change: 1 addition & 0 deletions src/test/resources/single_int_value.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
123
1 change: 1 addition & 0 deletions src/test/resources/single_null_value.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
null