Skip to content

Commit

Permalink
Add TypeAdapterFactory for validating Gobpie configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
karoliineh committed Mar 6, 2024
1 parent adc0c57 commit 06eb74a
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 20 deletions.
79 changes: 79 additions & 0 deletions src/main/java/api/json/GobPieConfValidatorAdapterFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package api.json;

import com.google.gson.Gson;
import com.google.gson.JsonParseException;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.internal.bind.ReflectiveTypeAdapterFactory;
import com.google.gson.reflect.TypeToken;

import java.lang.reflect.Field;
import java.util.LinkedHashMap;
import java.util.Map;

/**
* A class for handling unexpected fields in GobPie configuration.
* Code adapted from <a href="https://github.com/google/gson/issues/188">a Gson library issue</a>.
*
* @since 0.0.4
*/

public class GobPieConfValidatorAdapterFactory implements TypeAdapterFactory {

@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
// If the type adapter is a reflective type adapter, we want to modify the implementation using reflection.
// The trick is to replace the Map object used to look up the property name.
// Instead of returning null if the property is not found,
// we throw a Json exception to terminate the deserialization.
TypeAdapter<T> delegateAdapter = gson.getDelegateAdapter(this, type);

// Check if the type adapter is a reflective, cause this solution only work for reflection.
if (delegateAdapter instanceof ReflectiveTypeAdapterFactory.Adapter) {

try {
// Get reference to the existing boundFields.
Field f = findBoundField(delegateAdapter.getClass());
f.setAccessible(true);
// Finally, push our custom map back using reflection.
f.set(delegateAdapter, getBoundFields(f, delegateAdapter));
} catch (Exception e) {
// Should never happen if the implementation doesn't change.
throw new IllegalStateException(e);
}

}
return delegateAdapter;
}

@SuppressWarnings("unchecked")
private static <T> Object getBoundFields(Field f, TypeAdapter<T> delegate) throws IllegalAccessException {
Object boundFields = f.get(delegate);

// Then replace it with our implementation throwing exception if the value is null.
boundFields = new LinkedHashMap<>((Map<Object, Object>) boundFields) {

@Override
public Object get(Object key) {
Object value = super.get(key);
if (value == null) {
throw new JsonParseException(String.valueOf(key));
}
return value;
}

};
return boundFields;
}

private static Field findBoundField(Class<?> startingClass) throws NoSuchFieldException {
for (Class<?> c = startingClass; c != null; c = c.getSuperclass()) {
try {
return c.getDeclaredField("boundFields");
} catch (NoSuchFieldException e) {
// OK: continue with superclasses
}
}
throw new NoSuchFieldException("boundFields starting from " + (startingClass != null ? startingClass.getName() : null));
}
}
13 changes: 9 additions & 4 deletions src/main/java/gobpie/GobPieConfReader.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package gobpie;

import api.json.GobPieConfValidatorAdapterFactory;
import com.google.gson.*;
import magpiebridge.core.MagpieServer;
import org.apache.commons.lang3.exception.ExceptionUtils;
Expand Down Expand Up @@ -93,21 +94,25 @@ public GobPieConfiguration readGobPieConfiguration() {
public GobPieConfiguration parseGobPieConf() {
try {
log.debug("Reading GobPie configuration from json");
Gson gson = new GsonBuilder().create();
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new GobPieConfValidatorAdapterFactory())
.create();
// Read json object
JsonObject jsonObject = JsonParser.parseReader(new FileReader(gobPieConfFileName)).getAsJsonObject();
// Convert json object to GobPieConfiguration object
log.debug("GobPie configuration read from json");
return gson.fromJson(jsonObject, GobPieConfiguration.class);
} catch (FileNotFoundException e) {
throw new GobPieException("Could not locate GobPie configuration file.", e, GobPieExceptionType.GOBPIE_CONF_EXCEPTION);
} catch (JsonSyntaxException e) {
throw new GobPieException("GobPie configuration file syntax is wrong.", e, GobPieExceptionType.GOBPIE_CONF_EXCEPTION);
} catch (JsonParseException e) {
throw new GobPieException("There was an unknown option \"" + e.getMessage() + "\" in the GobPie configuration. Please check for any typos.", e, GobPieExceptionType.GOBPIE_CONF_EXCEPTION);
} catch (FileNotFoundException e) {
throw new GobPieException("Could not locate GobPie configuration file.", e, GobPieExceptionType.GOBPIE_CONF_EXCEPTION);
}
}


/**
/**
* Method for forwarding Error messages to MagpieServer.
*
* @param popUpMessage The message shown on the pop-up message.
Expand Down
18 changes: 2 additions & 16 deletions src/test/java/GobPieConfTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -137,22 +137,8 @@ void testReadGobPieConfigurationWithWrongJSONSyntax() {
@Test
void testReadGobPieConfigurationWithExtraField() {
GobPieConfReader gobPieConfReader = preFileSetup(5);
GobPieConfiguration expectedGobPieConfiguration =
new GobPieConfiguration.Builder()
.setGoblintConf("goblint.json")
.setGoblintExecutable("/home/user/goblint/analyzer/goblint")
.setPreAnalyzeCommand(new String[]{"cmake", "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON", "-B", "build"})
.setAbstractDebugging(false)
.setShowCfg(false)
.setIncrementalAnalysis(true)
.setExplodeGroupWarnings(false)
.createGobPieConfiguration();

GobPieConfiguration actualGobPieConfiguration = gobPieConfReader.readGobPieConfiguration();
assertTrue(systemOut.getLines().anyMatch(line -> line.contains("Reading GobPie configuration from json")));
verify(magpieServer).forwardMessageToClient(new MessageParams(MessageType.Error, "There was an unknown option in the GobPie configuration. Please check for any typos."));
//assertTrue(systemOut.getLines().anyMatch(line -> line.contains("GobPie configuration read from json")));
assertEquals(expectedGobPieConfiguration, actualGobPieConfiguration);
GobPieException thrown = assertThrows(GobPieException.class, gobPieConfReader::readGobPieConfiguration);
assertEquals("There was an unknown option \"extraField\" in the GobPie configuration. Please check for any typos.", thrown.getMessage());
}

/**
Expand Down

0 comments on commit 06eb74a

Please sign in to comment.