Skip to content

Commit f3f37c6

Browse files
authored
Merge pull request #557 from gooddata/dataList
use dataList as execution result data
2 parents 0d643f3 + f5edf09 commit f3f37c6

8 files changed

Lines changed: 407 additions & 6 deletions

File tree

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
* Copyright (C) 2007-2017, GoodData(R) Corporation. All rights reserved.
3+
* This source code is licensed under the BSD-style license found in the
4+
* LICENSE.txt file in the root directory of this source tree.
5+
*/
6+
package com.gooddata.executeafm.result;
7+
8+
import com.fasterxml.jackson.annotation.JsonValue;
9+
import com.fasterxml.jackson.core.JsonParser;
10+
import com.fasterxml.jackson.databind.DeserializationContext;
11+
import com.fasterxml.jackson.databind.JsonDeserializer;
12+
import com.fasterxml.jackson.databind.JsonMappingException;
13+
import com.fasterxml.jackson.databind.JsonNode;
14+
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
15+
16+
import java.io.IOException;
17+
import java.io.UncheckedIOException;
18+
import java.util.List;
19+
import java.util.Spliterator;
20+
import java.util.stream.Collectors;
21+
22+
import static java.util.Spliterators.spliteratorUnknownSize;
23+
import static java.util.stream.StreamSupport.stream;
24+
25+
/**
26+
* Data of {@link ExecutionResult}, can be of three basic kinds - {@link #NULL}, list and simple value.
27+
*/
28+
@JsonDeserialize(using = Data.DataDeserializer.class)
29+
public interface Data {
30+
31+
Data NULL = new Data() {
32+
@Override
33+
public boolean isList() {
34+
return false;
35+
}
36+
37+
@Override
38+
public boolean isValue() {
39+
return false;
40+
}
41+
42+
@Override
43+
@JsonValue
44+
public String textValue() {
45+
return null;
46+
}
47+
};
48+
49+
/**
50+
* @return true if this instance is of kind list, false otherwise
51+
*/
52+
boolean isList();
53+
54+
/**
55+
* @return true if this instance is of kind value, false otherwise
56+
*/
57+
boolean isValue();
58+
59+
/**
60+
* @return true if this instance is the same as {@link #NULL}, false otherwise
61+
*/
62+
default boolean isNull() {
63+
return this == NULL;
64+
}
65+
66+
/**
67+
* @return text data value, throws exception for data which can't be represented as text
68+
*/
69+
String textValue();
70+
71+
/**
72+
* @return this instance cast to List, may throw exception if this instance is not of kind list.
73+
*/
74+
default List<Data> asList() {
75+
throw new UnsupportedOperationException("This is not a list");
76+
}
77+
78+
class DataDeserializer extends JsonDeserializer<Data> {
79+
@Override
80+
public Data deserialize(final JsonParser jp, final DeserializationContext ctxt) throws IOException {
81+
final JsonNode root = jp.readValueAsTree();
82+
if (root.isArray()) {
83+
final List<Data> list = stream(spliteratorUnknownSize(root.elements(), Spliterator.ORDERED), false)
84+
.map(elem -> {
85+
try {
86+
return ctxt.readValue(elem.traverse(jp.getCodec()), Data.class);
87+
} catch (IOException e) {
88+
throw new UncheckedIOException(e);
89+
}
90+
}).collect(Collectors.toList());
91+
return new DataList(list);
92+
} else if (root.isTextual()) {
93+
return new DataValue(root.textValue());
94+
} else if (root.isNull()) {
95+
return NULL;
96+
} else {
97+
throw JsonMappingException.from(jp, "Unknown value of type: " + root.getNodeType());
98+
}
99+
}
100+
101+
@Override
102+
public Data getNullValue(final DeserializationContext ctxt) throws JsonMappingException {
103+
return NULL;
104+
}
105+
}
106+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Copyright (C) 2007-2017, GoodData(R) Corporation. All rights reserved.
3+
* This source code is licensed under the BSD-style license found in the
4+
* LICENSE.txt file in the root directory of this source tree.
5+
*/
6+
package com.gooddata.executeafm.result;
7+
8+
import com.gooddata.util.GoodDataToStringBuilder;
9+
10+
import java.util.ArrayList;
11+
import java.util.Arrays;
12+
import java.util.List;
13+
14+
import static com.gooddata.util.Validate.notNull;
15+
import static java.util.stream.Collectors.toList;
16+
17+
/**
18+
* List value type of {@link Data}, basically wrapper for list of nested {@link Data}
19+
*/
20+
public class DataList extends ArrayList<Data> implements Data {
21+
22+
/**
23+
* Creates new instance of given list of data
24+
* @param values list to use as values, can't be null
25+
*/
26+
public DataList(final List<Data> values) {
27+
super(notNull(values, "values"));
28+
}
29+
30+
/**
31+
* Creates new instance by transforming the given array to list of simple or null values
32+
* @param array array of values
33+
*/
34+
DataList(final String[] array) {
35+
this(Arrays.stream(array).map(DataList::simpleValue).collect(toList()));
36+
}
37+
38+
/**
39+
* Creates new instance by transforming the given array to list of data lists of simple or null values
40+
* @param array array of values
41+
*/
42+
DataList(final String[][] array) {
43+
this(Arrays.stream(array).map(DataList::new).collect(toList()));
44+
}
45+
46+
private static Data simpleValue(final String value) {
47+
return value == null ? Data.NULL : new DataValue(value);
48+
}
49+
50+
@Override
51+
public boolean isList() {
52+
return true;
53+
}
54+
55+
@Override
56+
public boolean isValue() {
57+
return false;
58+
}
59+
60+
@Override
61+
public String textValue() {
62+
throw new UnsupportedOperationException("DataList doesn't contain text value");
63+
}
64+
65+
@Override
66+
public List<Data> asList() {
67+
return this;
68+
}
69+
70+
@Override
71+
public String toString() {
72+
return GoodDataToStringBuilder.defaultToString(this);
73+
}
74+
75+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright (C) 2007-2017, GoodData(R) Corporation. All rights reserved.
3+
* This source code is licensed under the BSD-style license found in the
4+
* LICENSE.txt file in the root directory of this source tree.
5+
*/
6+
package com.gooddata.executeafm.result;
7+
8+
import com.fasterxml.jackson.annotation.JsonValue;
9+
import com.fasterxml.jackson.core.JsonParser;
10+
import com.fasterxml.jackson.databind.DeserializationContext;
11+
import com.fasterxml.jackson.databind.JsonDeserializer;
12+
import com.fasterxml.jackson.databind.JsonMappingException;
13+
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
14+
import com.gooddata.util.GoodDataToStringBuilder;
15+
16+
import java.io.IOException;
17+
18+
import static com.gooddata.util.Validate.notNull;
19+
20+
/**
21+
* Simple value type of {@link Data}. Wrapper of textual value.
22+
*/
23+
public class DataValue implements Data {
24+
25+
private final String value;
26+
27+
/**
28+
* Creates new instance of given value.
29+
* @param value textual value, can't be null
30+
*/
31+
public DataValue(final String value) {
32+
this.value = notNull(value, "value");
33+
}
34+
35+
@JsonValue
36+
private String getValue() {
37+
return value;
38+
}
39+
40+
@Override
41+
public boolean isList() {
42+
return false;
43+
}
44+
45+
@Override
46+
public boolean isValue() {
47+
return true;
48+
}
49+
50+
@Override
51+
public String textValue() {
52+
return value;
53+
}
54+
55+
@Override
56+
public boolean equals(Object o) {
57+
if (this == o) return true;
58+
if (o == null || getClass() != o.getClass()) return false;
59+
60+
DataValue dataValue = (DataValue) o;
61+
62+
return value != null ? value.equals(dataValue.value) : dataValue.value == null;
63+
}
64+
65+
@Override
66+
public int hashCode() {
67+
return value != null ? value.hashCode() : 0;
68+
}
69+
70+
@Override
71+
public String toString() {
72+
return GoodDataToStringBuilder.defaultToString(this);
73+
}
74+
}

src/main/java/com/gooddata/executeafm/result/ExecutionResult.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,9 @@
3030
@JsonInclude(JsonInclude.Include.NON_NULL)
3131
public class ExecutionResult {
3232

33+
private final DataList data;
3334
private final Paging paging;
3435

35-
@JsonProperty("data")
36-
private List data;
3736
private List<List<List<ResultHeaderItem>>> headerItems;
3837
private List<List<List<String>>> totals;
3938

@@ -43,7 +42,7 @@ public class ExecutionResult {
4342
* @param paging result paging
4443
*/
4544
public ExecutionResult(final String[] data, final Paging paging) {
46-
this.data = asList(notNull(data, "data"));
45+
this.data = new DataList(notNull(data, "data"));
4746
this.paging = notNull(paging, "paging");
4847
}
4948

@@ -53,7 +52,7 @@ public ExecutionResult(final String[] data, final Paging paging) {
5352
* @param paging result paging
5453
*/
5554
public ExecutionResult(final String[][] data, final Paging paging) {
56-
this.data = stream(notNull(data, "data")).map(Arrays::asList).collect(toList());
55+
this.data = new DataList(notNull(data, "data"));
5756
this.paging = notNull(paging, "paging");
5857
}
5958

@@ -65,7 +64,7 @@ public ExecutionResult(final String[][] data, final Paging paging) {
6564
* @param totals data of totals, for each total in each dimension, there is a list of total's values
6665
*/
6766
@JsonCreator
68-
ExecutionResult(@JsonProperty("data") final List data,
67+
ExecutionResult(@JsonProperty("data") final DataList data,
6968
@JsonProperty("paging") final Paging paging,
7069
@JsonProperty("headerItems") final List<List<List<ResultHeaderItem>>> headerItems,
7170
@JsonProperty("totals") final List<List<List<String>>> totals) {
@@ -75,6 +74,13 @@ public ExecutionResult(final String[][] data, final Paging paging) {
7574
this.totals = totals;
7675
}
7776

77+
/**
78+
* @return result data
79+
*/
80+
public DataList getData() {
81+
return data;
82+
}
83+
7884
/**
7985
* @return result paging
8086
*/
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Copyright (C) 2007-2017, GoodData(R) Corporation. All rights reserved.
3+
* This source code is licensed under the BSD-style license found in the
4+
* LICENSE.txt file in the root directory of this source tree.
5+
*/
6+
package com.gooddata.executeafm.result
7+
8+
import spock.lang.Specification
9+
10+
import static com.gooddata.util.ResourceUtils.OBJECT_MAPPER
11+
12+
class DataListTest extends Specification {
13+
14+
def "should serialize simple"() {
15+
expect:
16+
OBJECT_MAPPER.writeValueAsString(new DataList([new DataValue('a'), new DataValue('b')])) == '["a","b"]'
17+
}
18+
19+
def "should serialize nested"() {
20+
expect:
21+
OBJECT_MAPPER.writeValueAsString(new DataList([
22+
new DataList([new DataValue('a'), new DataValue('b')]),
23+
new DataList([new DataValue('c'), Data.NULL])])) == '[["a","b"],["c",null]]'
24+
}
25+
26+
def "should deserialize simple"() {
27+
when:
28+
DataList data = OBJECT_MAPPER.readValue('["a","b"]', DataList)
29+
30+
then:
31+
data.size() == 2
32+
data[0] == new DataValue('a')
33+
data[1] == new DataValue('b')
34+
data
35+
}
36+
37+
def "should deserialize nested"() {
38+
when:
39+
DataList data = OBJECT_MAPPER.readValue('[["a","b"],["c",null]]', DataList)
40+
41+
then:
42+
data.size() == 2
43+
44+
data[0] instanceof DataList
45+
DataList row0 = data[0] as DataList
46+
row0.size() == 2
47+
48+
data[1] instanceof DataList
49+
DataList row1 = data[1] as DataList
50+
row1.size() == 2
51+
52+
row0[0] == new DataValue('a')
53+
row0[1] == new DataValue('b')
54+
55+
row1[0] == new DataValue('c')
56+
row1[1] == Data.NULL
57+
}
58+
59+
def "should behave as list"() {
60+
when:
61+
DataList data = new DataList([['a', 'b'], ['c', null]] as String[][])
62+
63+
then:
64+
data.isList()
65+
!data.isValue()
66+
data[1][1] == Data.NULL
67+
data.asList()[0] == new DataList(['a', 'b'] as String[])
68+
69+
when:
70+
data.textValue()
71+
72+
then:
73+
thrown(UnsupportedOperationException)
74+
}
75+
}

0 commit comments

Comments
 (0)