Skip to content

Commit 3241b9b

Browse files
committed
fix: avoid NPE when constructing dynamic model
Fixes: https://github.ibm.com/arf/planning-sdk-squad/issues/2060 This commit fixes the scenario where an NPE could occur when constructing an instance of a dynamic model that contains no dynamic properties. We were simply missing a null check prior to using the generated builder class' dynamicProperties field.
1 parent e497de7 commit 3241b9b

File tree

8 files changed

+432
-31
lines changed

8 files changed

+432
-31
lines changed

src/main/java/com/ibm/cloud/sdk/core/service/model/DynamicModel.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,10 @@ public T get(String key) {
8585
* a map containing arbitrary properties to set on this object
8686
*/
8787
public void setProperties(Map<String, T> properties) {
88-
this.dynamicProperties = new HashMap<String, T>(properties);
88+
this.dynamicProperties = new HashMap<String, T>();
89+
if (properties != null) {
90+
this.dynamicProperties.putAll(properties);
91+
}
8992
}
9093

9194
/**

src/main/java/com/ibm/cloud/sdk/core/util/GsonSingleton.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ public static synchronized Gson getGsonWithoutPrettyPrinting() {
9898

9999
/**
100100
* Returns an instance of Gson with the "serialize nulls" config option enabled.
101-
* @return
101+
* @return a Gson instance configured to serialize nulls
102102
*/
103103
public static Gson getGsonWithSerializeNulls() {
104104
return createGson(false, true);

src/test/java/com/ibm/cloud/sdk/core/test/model/DiscriminatorSerializationTest.java

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@
3939
import com.ibm.cloud.sdk.core.util.GsonSingleton;
4040

4141
/**
42-
* This class contains tests of our DynamicModelTypeAdapterFactory.
43-
* The test data consists of a few dynamic models that were generated by the java generator
44-
* and then copied here to this project.
42+
* This class contains tests of our DynamicModelTypeAdapterFactory. The test
43+
* data consists of a few dynamic models that were generated by the java
44+
* generator and then copied here to this project.
4545
*/
4646
public class DiscriminatorSerializationTest {
4747
private boolean displayOutput = false;
@@ -82,27 +82,16 @@ private Truck createTruck(String discValue) {
8282
}
8383

8484
private AnimalDog createDog(String discValue) {
85-
AnimalDog model = new AnimalDog();
86-
model.setAnimalType(discValue);
87-
model.setBreed("Black Lab");
88-
model.put("collar_size", "XL");
89-
return model;
85+
return new AnimalDog.Builder().animalType(discValue).breed("Black Lab").add("collar_size", "XL").build();
9086
}
9187

9288
private AnimalCat createCat(String discValue) {
93-
AnimalCat model = new AnimalCat();
94-
model.setAnimalType(discValue);
95-
model.setColor("brown");
96-
model.put("collar_size", "S");
97-
return model;
89+
return new AnimalCat.Builder().animalType(discValue).color("brown").add("collar_size", "S").build();
9890
}
9991

10092
private AnimalIguana createIguana(String discValue) {
101-
AnimalIguana model = new AnimalIguana();
102-
model.setAnimalType(discValue);
103-
model.setTailLength(Long.valueOf(10));
104-
model.put("collar_size", "L");
105-
return model;
93+
return new AnimalIguana.Builder().animalType(discValue).tailLength(Long.valueOf(10)).add("collar_size", "L")
94+
.build();
10695
}
10796

10897
@Test
@@ -117,7 +106,8 @@ public void testTruck() {
117106
testSerDeser(model, Vehicle.class, Truck.class);
118107
}
119108

120-
// These classes simulate generated model classes that contain a list/map of discriminated oneOf parents.
109+
// These classes simulate generated model classes that contain a list/map of
110+
// discriminated oneOf parents.
121111
public class VehicleHolder {
122112
int size;
123113
List<Vehicle> vehicles;
@@ -141,13 +131,15 @@ public AnimalHolder(Map<String, Animal> animals) {
141131
@Test
142132
public void testVehicleList() {
143133

144-
// Create an instance of VehicleHolder that contains a list of Vehicle instances.
134+
// Create an instance of VehicleHolder that contains a list of Vehicle
135+
// instances.
145136
List<Vehicle> vehicleList = new ArrayList<>();
146137
vehicleList.add(createTruck("truck"));
147138
vehicleList.add(createCar("Car"));
148139
VehicleHolder expected = new VehicleHolder(vehicleList);
149140

150-
// Make sure we can serialize the model instance containing the list of oneOf parents.
141+
// Make sure we can serialize the model instance containing the list of oneOf
142+
// parents.
151143
String json = serialize(expected);
152144
assertNotNull(json);
153145
log("Vehicle holder (json): " + json);
@@ -230,7 +222,8 @@ public void testAnimalsNullElement() {
230222
animals.put("missing_dog", null);
231223
AnimalHolder expected = new AnimalHolder(animals);
232224

233-
// We have to enable "serialize nulls" because Gson's handling of maps seems to be inconsistent with their
225+
// We have to enable "serialize nulls" because Gson's handling of maps seems to
226+
// be inconsistent with their
234227
// support of lists.
235228
String json = GsonSingleton.getGsonWithSerializeNulls().toJson(expected);
236229
assertNotNull(json);
@@ -242,19 +235,19 @@ public void testAnimalsNullElement() {
242235
assertEquals(actual.animals, expected.animals);
243236
}
244237

245-
@Test(expectedExceptions = {JsonSyntaxException.class})
238+
@Test(expectedExceptions = { JsonSyntaxException.class })
246239
void testTruckDiscPropMissing() {
247240
Truck model = createTruck(null);
248241
testSerDeser(model, Vehicle.class, Truck.class);
249242
}
250243

251-
@Test(expectedExceptions = {JsonSyntaxException.class})
244+
@Test(expectedExceptions = { JsonSyntaxException.class })
252245
void testTruckEmptyDiscValue() {
253246
Truck model = createTruck("");
254247
testSerDeser(model, Vehicle.class, Truck.class);
255248
}
256249

257-
@Test(expectedExceptions = {JsonSyntaxException.class})
250+
@Test(expectedExceptions = { JsonSyntaxException.class })
258251
void testCarBadDiscValue() {
259252
Car model = createCar("LAMBO");
260253
testSerDeser(model, Vehicle.class, Car.class);
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/**
2+
* (C) Copyright IBM Corp. 2015, 2019.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*/
13+
14+
package com.ibm.cloud.sdk.core.test.model;
15+
16+
import static org.testng.Assert.assertEquals;
17+
import static org.testng.Assert.assertNotEquals;
18+
import static org.testng.Assert.assertNotNull;
19+
import static org.testng.Assert.assertTrue;
20+
21+
import org.testng.annotations.Test;
22+
23+
import com.ibm.cloud.sdk.core.test.model.generated.AnimalCat;
24+
25+
/**
26+
* A few simple tests that exercise the GenericModel methods.
27+
*/
28+
public class DynamicModelTest {
29+
private boolean displayOutput = false;
30+
31+
private void log(String msg) {
32+
if (displayOutput) {
33+
System.out.println(msg);
34+
}
35+
}
36+
37+
private AnimalCat createCat(String color, String propName, String propValue) {
38+
AnimalCat.Builder builder = new AnimalCat.Builder()
39+
.color(color)
40+
.animalType("Cat");
41+
if (propName != null) {
42+
builder.add(propName, propValue);
43+
}
44+
return builder.build();
45+
}
46+
47+
@Test
48+
public void testEquals1() {
49+
AnimalCat cat1 = createCat("brown", null, null);
50+
assertNotNull(cat1);
51+
52+
AnimalCat cat2 = createCat("brown", null, null);
53+
assertNotNull(cat2);
54+
55+
assertEquals(cat1, cat2);
56+
}
57+
58+
@Test
59+
public void testEquals2() {
60+
AnimalCat cat1 = createCat("brown", "prop", "value");
61+
assertNotNull(cat1);
62+
63+
AnimalCat cat2 = createCat("brown", "prop", "value");
64+
assertNotNull(cat2);
65+
66+
assertEquals(cat1, cat2);
67+
log("cat1: " + cat1.toString());
68+
log("cat2: " + cat2.toString());
69+
}
70+
71+
@Test
72+
public void testNotEquals1() {
73+
AnimalCat cat1 = createCat("brown", "prop1", "value");
74+
assertNotNull(cat1);
75+
76+
AnimalCat cat2 = createCat("brown", null, null);
77+
assertNotNull(cat2);
78+
79+
assertNotEquals(cat1, cat2);
80+
}
81+
82+
@Test
83+
public void testNotEquals2() {
84+
AnimalCat cat1 = createCat("brown", "prop1", "value");
85+
assertNotNull(cat1);
86+
87+
AnimalCat cat2 = createCat("brown", "prop2", "value");
88+
assertNotNull(cat2);
89+
90+
assertNotEquals(cat1, cat2);
91+
}
92+
93+
@Test
94+
public void testNullAdditionalProperty() {
95+
AnimalCat cat = createCat("brown", "prop1", null);
96+
assertNotNull(cat);
97+
log("cat: " + cat.toString());
98+
99+
assertTrue(cat.toString().contains("\"prop1\": null"));
100+
}
101+
102+
}

src/test/java/com/ibm/cloud/sdk/core/test/model/generated/Animal.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* (C) Copyright IBM Corp. 2019.
2+
* (C) Copyright IBM Corp. 2019, 2020.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
55
* the License. You may obtain a copy of the License at

src/test/java/com/ibm/cloud/sdk/core/test/model/generated/AnimalCat.java

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* (C) Copyright IBM Corp. 2019.
2+
* (C) Copyright IBM Corp. 2019, 2020.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
55
* the License. You may obtain a copy of the License at
@@ -12,6 +12,9 @@
1212
*/
1313
package com.ibm.cloud.sdk.core.test.model.generated;
1414

15+
import java.util.HashMap;
16+
import java.util.Map;
17+
1518
/**
1619
* Information about a cat.
1720
*/
@@ -22,6 +25,104 @@ public AnimalCat() {
2225
super();
2326
}
2427

28+
/**
29+
* Builder.
30+
*/
31+
public static class Builder {
32+
private String animalType;
33+
private String color;
34+
private Map<String, String> dynamicProperties;
35+
36+
public Builder(Animal animalCat) {
37+
this.animalType = animalCat.animalType;
38+
this.color = animalCat.color;
39+
this.dynamicProperties = animalCat.getProperties();
40+
}
41+
42+
/**
43+
* Instantiates a new builder.
44+
*/
45+
public Builder() {
46+
}
47+
48+
/**
49+
* Instantiates a new builder with required properties.
50+
*
51+
* @param animalType the animalType
52+
* @param color the color
53+
*/
54+
public Builder(String animalType, String color) {
55+
this.animalType = animalType;
56+
this.color = color;
57+
}
58+
59+
/**
60+
* Builds a AnimalCat.
61+
*
62+
* @return the new AnimalCat instance
63+
*/
64+
public AnimalCat build() {
65+
return new AnimalCat(this);
66+
}
67+
68+
/**
69+
* Set the animalType.
70+
*
71+
* @param animalType the animalType
72+
* @return the AnimalCat builder
73+
*/
74+
public Builder animalType(String animalType) {
75+
this.animalType = animalType;
76+
return this;
77+
}
78+
79+
/**
80+
* Set the color.
81+
*
82+
* @param color the color
83+
* @return the AnimalCat builder
84+
*/
85+
public Builder color(String color) {
86+
this.color = color;
87+
return this;
88+
}
89+
90+
/**
91+
* Add an arbitrary property.
92+
*
93+
* @param name the name of the property to add
94+
* @param value the value of the property to add
95+
* @return the AnimalCat builder
96+
*/
97+
public Builder add(String name, String value) {
98+
com.ibm.cloud.sdk.core.util.Validator.notNull(name, "name cannot be null");
99+
if (this.dynamicProperties == null) {
100+
this.dynamicProperties = new HashMap<String, String>();
101+
}
102+
this.dynamicProperties.put(name, value);
103+
return this;
104+
}
105+
}
106+
107+
protected AnimalCat(Builder builder) {
108+
com.ibm.cloud.sdk.core.util.Validator.notNull(builder.animalType,
109+
"animalType cannot be null");
110+
com.ibm.cloud.sdk.core.util.Validator.notNull(builder.color,
111+
"color cannot be null");
112+
animalType = builder.animalType;
113+
color = builder.color;
114+
this.setProperties(builder.dynamicProperties);
115+
}
116+
117+
/**
118+
* New builder.
119+
*
120+
* @return a AnimalCat builder
121+
*/
122+
public Builder newBuilder() {
123+
return new Builder(this);
124+
}
125+
25126
/**
26127
* Sets the animalType.
27128
*

0 commit comments

Comments
 (0)