Skip to content

Commit

Permalink
Change json dependency from json-simple to GSON (#264)
Browse files Browse the repository at this point in the history
This PR updates the project's JSON handling dependency from `json-simple` to `GSON`. The switch to `GSON` addresses the need to suppress unchecked warnings (`@SuppressWarnings("unchecked")`) that were frequently required with `json-simple`. This change is also a proactive step in preparation for upcoming updates involving automatic source code fixes, which will heavily rely on JSON deserialization and parsing APIs, particularly for handling network connections and response parsing.
  • Loading branch information
nimakarimipour authored Jan 10, 2025
1 parent c8b0129 commit 3bae68e
Show file tree
Hide file tree
Showing 11 changed files with 415 additions and 283 deletions.
9 changes: 4 additions & 5 deletions annotator-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,17 @@ application {
}

dependencies {

implementation project(':injector')
implementation project(':annotator-scanner')
implementation deps.build.guava
implementation deps.build.json
implementation deps.build.gson
implementation deps.build.progressbar
implementation deps.build.javaparser
implementation deps.build.commonscli

testImplementation deps.build.commonsio
testImplementation 'junit:junit:4.13.1'
testImplementation deps.test.junit
testImplementation deps.test.mockito

}

// Exclude formatting files under resources
Expand Down Expand Up @@ -95,7 +94,7 @@ shadowJar {
}

// Exclude tests not supported by Java 11.
if(JavaVersion.current().isJava11()){
if (JavaVersion.current().isJava11()) {
// exclude Java17Test which is designed to test Java 17 features.
test {
filter {
Expand Down
286 changes: 102 additions & 184 deletions annotator-core/src/main/java/edu/ucr/cs/riple/core/Config.java

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@

package edu.ucr.cs.riple.core.module;

import com.google.gson.JsonObject;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Objects;
import org.json.simple.JSONObject;

/** Container class to hold paths to checker and scanner config files. */
public class ModuleConfiguration {
Expand Down Expand Up @@ -57,9 +57,9 @@ public class ModuleConfiguration {
* @param jsonObject Json Object to retrieve values.
* @return An instance of {@link ModuleConfiguration}.
*/
public static ModuleConfiguration buildFromJson(int id, Path globalDir, JSONObject jsonObject) {
String checkerConfigPath = (String) jsonObject.get("CHECKER");
String scannerConfigPath = (String) jsonObject.get("SCANNER");
public static ModuleConfiguration buildFromJson(int id, Path globalDir, JsonObject jsonObject) {
String checkerConfigPath = jsonObject.get("CHECKER").getAsString();
String scannerConfigPath = jsonObject.get("SCANNER").getAsString();
if (checkerConfigPath == null || scannerConfigPath == null) {
throw new IllegalArgumentException(
"Both paths to NullAway and Scanner config files must be set with CHECKER and SCANNER keys!");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.gson.JsonObject;
import edu.ucr.cs.riple.injector.changes.ASTChange;
import edu.ucr.cs.riple.injector.changes.AddAnnotation;
import edu.ucr.cs.riple.injector.location.Location;
Expand All @@ -38,7 +39,6 @@
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.json.simple.JSONObject;

/**
* Stores information suggesting adding @Nullable on an element in source code. These suggestions
Expand Down Expand Up @@ -185,7 +185,7 @@ public int hashCode() {
*
* @return Json instance.
*/
public JSONObject getJson() {
public JsonObject getJson() {
return changes.iterator().next().getLocation().accept(new LocationToJsonVisitor(), null);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
/*
* MIT License
*
* Copyright (c) 2025 Nima Karimipour
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

package edu.ucr.cs.riple.core.util;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSyntaxException;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

/** A utility class for parsing JSON files. */
public class JsonParser {

/** The JSON object to parse. */
private final JsonObject json;

/**
* Creates a new JsonParser with the given path.
*
* @param path The path to the JSON file.
*/
public JsonParser(Path path) {
this.json = parseJson(path);
}

/**
* Creates a new JsonParser with the given content.
*
* @param content The content of the JSON file.
*/
public JsonParser(String content) {
this.json = parseJson(content);
}

/**
* Used to retrieve a value from a key in the JSON object, the given key should be in the format
* of "key1:key2:key3". The method recursively searches for the key in the JSON object. It returns
* a {@link OrElse} object that can be used to retrieve the value or a default value if the key
* does not exist.
*
* @param key The key to search for in the JSON object.
* @return An {@link OrElse} object that can be used to retrieve the value or a default value if
* the key does not exist.
*/
public OrElse getValueFromKey(String key) {
JsonObject current = json;
String[] keys = key.split(":");
int index = 0;
while (index != keys.length - 1) {
if (current.keySet().contains(keys[index])) {
current = (JsonObject) current.get(keys[index]);
index++;
} else {
return new OrElse(JsonNull.INSTANCE);
}
}
return current.keySet().contains(keys[index])
? new OrElse(current.get(keys[index]))
: new OrElse(JsonNull.INSTANCE);
}

/**
* Used to retrieve an array of values from a key in the JSON object, the given key should be in
* the format of "key1:key2:key3". The method recursively searches for the key in the JSON object.
* It returns a {@link ListOrElse} object that can be used to retrieve the value or a default
* value if the key does not exist.
*
* @param key The key to search for in the JSON object.
* @param mapper The function to map the JSON object to the desired type.
* @return A {@link ListOrElse} object that can be used to retrieve the value or a default value
* if the key does not exist.
* @param <T> The type of the array elements.
*/
public <T> ListOrElse<T> getArrayValueFromKey(String key, Function<JsonObject, T> mapper) {
OrElse jsonValue = getValueFromKey(key);
if (jsonValue.value.equals(JsonNull.INSTANCE)) {
return new ListOrElse<>(Stream.of());
} else {
if (jsonValue.value instanceof JsonArray) {
return new ListOrElse<>(
StreamSupport.stream(((JsonArray) jsonValue.value).spliterator(), false)
.map(jsonElement -> mapper.apply(jsonElement.getAsJsonObject())));
}
throw new IllegalStateException(
"Expected type to be json array, found: " + jsonValue.value.getClass());
}
}

/**
* Parses a file in json format and returns as a JsonObject.
*
* @param path The path to the file.
* @return The JsonObject parsed from the file.
*/
private static JsonObject parseJson(Path path) {
try {
return com.google.gson.JsonParser.parseReader(
Files.newBufferedReader(path, Charset.defaultCharset()))
.getAsJsonObject();
} catch (JsonSyntaxException | IOException e) {
throw new RuntimeException("Error in parsing json at path: " + path, e);
}
}

/**
* Parses a string in json format and returns as a JsonObject.
*
* @param content The content to parse.
* @return The JsonObject parsed from the content.
*/
private static JsonObject parseJson(String content) {
try {
return com.google.gson.JsonParser.parseString(content).getAsJsonObject();
} catch (JsonSyntaxException e) {
throw new RuntimeException("Error in parsing: " + content, e);
}
}

/**
* A utility class to retrieve a value from a JSON object or a default value if the key does not
* exist.
*/
public static class OrElse {

/** The JSON element retrieved from the JSON object. */
private final JsonElement value;

/**
* Creates a new OrElse object with the given JSON element.
*
* @param value The retrieved JSON element.
*/
private OrElse(JsonElement value) {
this.value = value;
}

/**
* Returns the value as a {@link JsonPrimitive} if it is not {@link JsonNull}, otherwise returns
* the default value.
*
* @param defaultValue The default value to return if the key does not exist.
* @return The value as a {@link JsonPrimitive} if it is not {@link JsonNull}, otherwise returns
* the default value.
*/
public JsonPrimitive orElse(Object defaultValue) {
return value.equals(JsonNull.INSTANCE)
? new JsonPrimitive(String.valueOf(defaultValue))
: value.getAsJsonPrimitive();
}
}

/**
* A utility class to retrieve an array of values from a JSON object or a default value if the key
* does not exist.
*
* @param <T> The type of the array elements.
*/
public static class ListOrElse<T> {

/** The stream of values retrieved from the JSON object. */
private final Stream<T> value;

/**
* Creates a new ListOrElse object with the given stream of values.
*
* @param value The retrieved stream of values.
*/
private ListOrElse(Stream<T> value) {
this.value = value;
}

/**
* Returns the values as a list if it is not null, otherwise returns the default value.
*
* @param defaultValue The default value to return if the key does not exist.
* @return The values as a list if it is not null, otherwise returns the default value.
*/
public List<T> orElse(List<T> defaultValue) {
if (value == null) {
return defaultValue;
} else {
return this.value.collect(Collectors.toList());
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import edu.ucr.cs.riple.core.Config;
import edu.ucr.cs.riple.core.Context;
import edu.ucr.cs.riple.core.Report;
Expand All @@ -52,8 +54,6 @@
import java.util.stream.Stream;
import me.tongfei.progressbar.ProgressBar;
import me.tongfei.progressbar.ProgressBarStyle;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;

/** Utility class. */
public class Utility {
Expand Down Expand Up @@ -88,36 +88,38 @@ public static void executeCommand(Config config, String command) {
* @param context Annotator context.
* @param reports Immutable set of reports.
*/
@SuppressWarnings("unchecked")
public static void writeReports(Context context, ImmutableSet<Report> reports) {
Path reportsPath = context.config.globalDir.resolve("reports.json");
JSONObject result = new JSONObject();
JSONArray reportsJson = new JSONArray();
for (Report report : reports) {
JSONObject reportJson = report.root.getJson();
reportJson.put("LOCAL EFFECT", report.localEffect);
reportJson.put("OVERALL EFFECT", report.getOverallEffect(context.config));
reportJson.put("Upper Bound EFFECT", report.getUpperBoundEffectOnDownstreamDependencies());
reportJson.put("Lower Bound EFFECT", report.getLowerBoundEffectOnDownstreamDependencies());
reportJson.put("FINISHED", !report.requiresFurtherProcess(context.config));
JSONArray followUps = new JSONArray();
JsonObject result = new JsonObject();
JsonArray reportsJson = new JsonArray();
// Sort reports based on the overall effect in descending order.
List<Report> sorted =
reports.stream()
.sorted(
(r1, r2) ->
Integer.compare(
r2.getOverallEffect(context.config), r1.getOverallEffect(context.config)))
.collect(Collectors.toList());
for (Report report : sorted) {
JsonObject reportJson = report.root.getJson();
reportJson.addProperty("LOCAL EFFECT", report.localEffect);
reportJson.addProperty("OVERALL EFFECT", report.getOverallEffect(context.config));
reportJson.addProperty(
"Upper Bound EFFECT", report.getUpperBoundEffectOnDownstreamDependencies());
reportJson.addProperty(
"Lower Bound EFFECT", report.getLowerBoundEffectOnDownstreamDependencies());
reportJson.addProperty("FINISHED", !report.requiresFurtherProcess(context.config));
JsonArray followUps = new JsonArray();
if (context.config.chain && report.localEffect < 1) {
followUps.addAll(report.tree.stream().map(Fix::getJson).collect(Collectors.toList()));
report.tree.stream().map(Fix::getJson).forEach(followUps::add);
}
reportJson.put("TREE", followUps);
reportJson.add("TREE", followUps);
reportsJson.add(reportJson);
}
// Sort by overall effect.
reportsJson.sort(
(o1, o2) -> {
int first = (Integer) ((JSONObject) o1).get("OVERALL EFFECT");
int second = (Integer) ((JSONObject) o2).get("OVERALL EFFECT");
return Integer.compare(second, first);
});
result.put("REPORTS", reportsJson);
result.add("REPORTS", reportsJson);
try (BufferedWriter writer =
Files.newBufferedWriter(reportsPath.toFile().toPath(), Charset.defaultCharset())) {
writer.write(result.toJSONString().replace("\\/", "/").replace("\\\\\\", "\\"));
writer.write(result.toString().replace("\\/", "/").replace("\\\\\\", "\\"));
writer.flush();
} catch (IOException e) {
throw new RuntimeException(
Expand Down
Loading

0 comments on commit 3bae68e

Please sign in to comment.