Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change json dependency from json-simple to GSON #264

Merged
merged 11 commits into from
Jan 10, 2025
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
Loading