Skip to content

Commit 06eb74a

Browse files
committed
Add TypeAdapterFactory for validating Gobpie configuration
1 parent adc0c57 commit 06eb74a

File tree

3 files changed

+90
-20
lines changed

3 files changed

+90
-20
lines changed
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package api.json;
2+
3+
import com.google.gson.Gson;
4+
import com.google.gson.JsonParseException;
5+
import com.google.gson.TypeAdapter;
6+
import com.google.gson.TypeAdapterFactory;
7+
import com.google.gson.internal.bind.ReflectiveTypeAdapterFactory;
8+
import com.google.gson.reflect.TypeToken;
9+
10+
import java.lang.reflect.Field;
11+
import java.util.LinkedHashMap;
12+
import java.util.Map;
13+
14+
/**
15+
* A class for handling unexpected fields in GobPie configuration.
16+
* Code adapted from <a href="https://github.com/google/gson/issues/188">a Gson library issue</a>.
17+
*
18+
* @since 0.0.4
19+
*/
20+
21+
public class GobPieConfValidatorAdapterFactory implements TypeAdapterFactory {
22+
23+
@Override
24+
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
25+
// If the type adapter is a reflective type adapter, we want to modify the implementation using reflection.
26+
// The trick is to replace the Map object used to look up the property name.
27+
// Instead of returning null if the property is not found,
28+
// we throw a Json exception to terminate the deserialization.
29+
TypeAdapter<T> delegateAdapter = gson.getDelegateAdapter(this, type);
30+
31+
// Check if the type adapter is a reflective, cause this solution only work for reflection.
32+
if (delegateAdapter instanceof ReflectiveTypeAdapterFactory.Adapter) {
33+
34+
try {
35+
// Get reference to the existing boundFields.
36+
Field f = findBoundField(delegateAdapter.getClass());
37+
f.setAccessible(true);
38+
// Finally, push our custom map back using reflection.
39+
f.set(delegateAdapter, getBoundFields(f, delegateAdapter));
40+
} catch (Exception e) {
41+
// Should never happen if the implementation doesn't change.
42+
throw new IllegalStateException(e);
43+
}
44+
45+
}
46+
return delegateAdapter;
47+
}
48+
49+
@SuppressWarnings("unchecked")
50+
private static <T> Object getBoundFields(Field f, TypeAdapter<T> delegate) throws IllegalAccessException {
51+
Object boundFields = f.get(delegate);
52+
53+
// Then replace it with our implementation throwing exception if the value is null.
54+
boundFields = new LinkedHashMap<>((Map<Object, Object>) boundFields) {
55+
56+
@Override
57+
public Object get(Object key) {
58+
Object value = super.get(key);
59+
if (value == null) {
60+
throw new JsonParseException(String.valueOf(key));
61+
}
62+
return value;
63+
}
64+
65+
};
66+
return boundFields;
67+
}
68+
69+
private static Field findBoundField(Class<?> startingClass) throws NoSuchFieldException {
70+
for (Class<?> c = startingClass; c != null; c = c.getSuperclass()) {
71+
try {
72+
return c.getDeclaredField("boundFields");
73+
} catch (NoSuchFieldException e) {
74+
// OK: continue with superclasses
75+
}
76+
}
77+
throw new NoSuchFieldException("boundFields starting from " + (startingClass != null ? startingClass.getName() : null));
78+
}
79+
}

src/main/java/gobpie/GobPieConfReader.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package gobpie;
22

3+
import api.json.GobPieConfValidatorAdapterFactory;
34
import com.google.gson.*;
45
import magpiebridge.core.MagpieServer;
56
import org.apache.commons.lang3.exception.ExceptionUtils;
@@ -93,21 +94,25 @@ public GobPieConfiguration readGobPieConfiguration() {
9394
public GobPieConfiguration parseGobPieConf() {
9495
try {
9596
log.debug("Reading GobPie configuration from json");
96-
Gson gson = new GsonBuilder().create();
97+
Gson gson = new GsonBuilder()
98+
.registerTypeAdapterFactory(new GobPieConfValidatorAdapterFactory())
99+
.create();
97100
// Read json object
98101
JsonObject jsonObject = JsonParser.parseReader(new FileReader(gobPieConfFileName)).getAsJsonObject();
99102
// Convert json object to GobPieConfiguration object
100103
log.debug("GobPie configuration read from json");
101104
return gson.fromJson(jsonObject, GobPieConfiguration.class);
102-
} catch (FileNotFoundException e) {
103-
throw new GobPieException("Could not locate GobPie configuration file.", e, GobPieExceptionType.GOBPIE_CONF_EXCEPTION);
104105
} catch (JsonSyntaxException e) {
105106
throw new GobPieException("GobPie configuration file syntax is wrong.", e, GobPieExceptionType.GOBPIE_CONF_EXCEPTION);
107+
} catch (JsonParseException e) {
108+
throw new GobPieException("There was an unknown option \"" + e.getMessage() + "\" in the GobPie configuration. Please check for any typos.", e, GobPieExceptionType.GOBPIE_CONF_EXCEPTION);
109+
} catch (FileNotFoundException e) {
110+
throw new GobPieException("Could not locate GobPie configuration file.", e, GobPieExceptionType.GOBPIE_CONF_EXCEPTION);
106111
}
107112
}
108113

109114

110-
/**
115+
/**
111116
* Method for forwarding Error messages to MagpieServer.
112117
*
113118
* @param popUpMessage The message shown on the pop-up message.

src/test/java/GobPieConfTest.java

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -137,22 +137,8 @@ void testReadGobPieConfigurationWithWrongJSONSyntax() {
137137
@Test
138138
void testReadGobPieConfigurationWithExtraField() {
139139
GobPieConfReader gobPieConfReader = preFileSetup(5);
140-
GobPieConfiguration expectedGobPieConfiguration =
141-
new GobPieConfiguration.Builder()
142-
.setGoblintConf("goblint.json")
143-
.setGoblintExecutable("/home/user/goblint/analyzer/goblint")
144-
.setPreAnalyzeCommand(new String[]{"cmake", "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON", "-B", "build"})
145-
.setAbstractDebugging(false)
146-
.setShowCfg(false)
147-
.setIncrementalAnalysis(true)
148-
.setExplodeGroupWarnings(false)
149-
.createGobPieConfiguration();
150-
151-
GobPieConfiguration actualGobPieConfiguration = gobPieConfReader.readGobPieConfiguration();
152-
assertTrue(systemOut.getLines().anyMatch(line -> line.contains("Reading GobPie configuration from json")));
153-
verify(magpieServer).forwardMessageToClient(new MessageParams(MessageType.Error, "There was an unknown option in the GobPie configuration. Please check for any typos."));
154-
//assertTrue(systemOut.getLines().anyMatch(line -> line.contains("GobPie configuration read from json")));
155-
assertEquals(expectedGobPieConfiguration, actualGobPieConfiguration);
140+
GobPieException thrown = assertThrows(GobPieException.class, gobPieConfReader::readGobPieConfiguration);
141+
assertEquals("There was an unknown option \"extraField\" in the GobPie configuration. Please check for any typos.", thrown.getMessage());
156142
}
157143

158144
/**

0 commit comments

Comments
 (0)