Skip to content

Commit

Permalink
Merge pull request #1189 from jplag/feature/rework-json
Browse files Browse the repository at this point in the history
Slight changes to the report json format
  • Loading branch information
sebinside authored Aug 18, 2023
2 parents 99a8a3f + 0ffb76a commit 5c39647
Show file tree
Hide file tree
Showing 34 changed files with 979 additions and 461 deletions.
2 changes: 1 addition & 1 deletion core/src/main/java/de/jplag/JPlagResult.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public class JPlagResult {
private final int[] similarityDistribution; // 10-element array representing the similarity distribution of the detected matches.

private List<ClusteringResult<Submission>> clusteringResult;
private final int SIMILARITY_DISTRIBUTION_SIZE = 10;
private final int SIMILARITY_DISTRIBUTION_SIZE = 100;

public JPlagResult(List<JPlagComparison> comparisons, SubmissionSet submissions, long durationInMillis, JPlagOptions options) {
// sort by similarity (descending)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import de.jplag.JPlagResult;
import de.jplag.Submission;
import de.jplag.Token;
import de.jplag.options.SimilarityMetric;
import de.jplag.reporting.FilePathUtil;
import de.jplag.reporting.reportobject.model.ComparisonReport;
import de.jplag.reporting.reportobject.model.Match;
Expand Down Expand Up @@ -55,7 +56,8 @@ private void writeComparisons(String path, List<JPlagComparison> comparisons) {
String secondSubmissionId = submissionToIdFunction.apply(comparison.secondSubmission());
String fileName = generateComparisonName(firstSubmissionId, secondSubmissionId);
addToLookUp(firstSubmissionId, secondSubmissionId, fileName);
var comparisonReport = new ComparisonReport(firstSubmissionId, secondSubmissionId, comparison.similarity(),
var comparisonReport = new ComparisonReport(firstSubmissionId, secondSubmissionId,
Map.of(SimilarityMetric.AVG.name(), comparison.similarity(), SimilarityMetric.MAX.name(), comparison.maximalSimilarity()),
convertMatchesToReportMatches(comparison));
fileWriter.saveAsJSON(comparisonReport, path, fileName);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
import de.jplag.reporting.jsonfactory.ToDiskWriter;
import de.jplag.reporting.reportobject.mapper.ClusteringResultMapper;
import de.jplag.reporting.reportobject.mapper.MetricMapper;
import de.jplag.reporting.reportobject.model.Metric;
import de.jplag.reporting.reportobject.model.OverviewReport;
import de.jplag.reporting.reportobject.model.SubmissionFileIndex;
import de.jplag.reporting.reportobject.model.Version;
Expand Down Expand Up @@ -175,7 +174,7 @@ private void writeOverview(JPlagResult result, String path) {

int totalComparisons = result.getAllComparisons().size();
int numberOfMaximumComparisons = result.getOptions().maximumNumberOfComparisons();
int shownComparisons = totalComparisons > numberOfMaximumComparisons ? numberOfMaximumComparisons : totalComparisons;
int shownComparisons = Math.min(totalComparisons, numberOfMaximumComparisons);
int missingComparisons = totalComparisons > numberOfMaximumComparisons ? (totalComparisons - numberOfMaximumComparisons) : 0;
logger.info("Total Comparisons: {}. Comparisons in Report: {}. Omitted Comparisons: {}.", totalComparisons, shownComparisons,
missingComparisons);
Expand All @@ -190,7 +189,8 @@ private void writeOverview(JPlagResult result, String path) {
result.getOptions().minimumTokenMatch(), // matchSensitivity
getDate(),// dateOfExecution
result.getDuration(), // executionTime
getMetrics(result),// metrics
MetricMapper.getDistributions(result), // distribution
new MetricMapper(submissionToIdFunction).getTopComparisons(result),// topComparisons
clusteringResultMapper.map(result), // clusters
totalComparisons); // totalComparisons

Expand Down Expand Up @@ -220,16 +220,6 @@ private Set<Submission> getSubmissions(List<JPlagComparison> comparisons) {
return submissions;
}

/**
* Gets the used metrics in a JPlag comparison. As Max Metric is included in every JPlag run, this always include Max
* Metric.
* @return A list contains Metric DTOs.
*/
private List<Metric> getMetrics(JPlagResult result) {
MetricMapper metricMapper = new MetricMapper(submissionToIdFunction);
return List.of(metricMapper.getAverageMetric(result), metricMapper.getMaxMetric(result));
}

private String getDate() {
SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yy");
Date date = new Date();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,14 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

import de.jplag.JPlagComparison;
import de.jplag.JPlagResult;
import de.jplag.Messages;
import de.jplag.Submission;
import de.jplag.options.SimilarityMetric;
import de.jplag.reporting.reportobject.model.Metric;
import de.jplag.reporting.reportobject.model.TopComparison;

/**
Expand All @@ -25,40 +23,35 @@ public MetricMapper(Function<Submission, String> submissionToIdFunction) {
this.submissionToIdFunction = submissionToIdFunction;
}

public Metric getAverageMetric(JPlagResult result) {
return new Metric(SimilarityMetric.AVG.name(), convertDistribution(result.getSimilarityDistribution()),
getTopComparisons(getComparisons(result)), Messages.getString("SimilarityMetric.Avg.Description"));
/**
* Generates a map of all distributions
* @param result Result containing distributions
* @return Map with key as name of metric and value as distribution
*/
public static Map<String, List<Integer>> getDistributions(JPlagResult result) {
return Map.of(SimilarityMetric.AVG.name(), convertDistribution(result.getSimilarityDistribution()), SimilarityMetric.MAX.name(),
convertDistribution(result.getMaxSimilarityDistribution()));
}

public Metric getMaxMetric(JPlagResult result) {
return new Metric(SimilarityMetric.MAX.name(), convertDistribution(result.getMaxSimilarityDistribution()),
getMaxSimilarityTopComparisons(getComparisons(result)), Messages.getString("SimilarityMetric.Max.Description"));
/**
* Generates a List of the top comparisons
* @param result Result containing comparisons
* @return List of top comparisons with similarities in all metrics
*/
public List<TopComparison> getTopComparisons(JPlagResult result) {
return result.getComparisons(result.getOptions().maximumNumberOfComparisons()).stream()
.map(comparison -> new TopComparison(submissionToIdFunction.apply(comparison.firstSubmission()),
submissionToIdFunction.apply(comparison.secondSubmission()), getComparisonMetricMap(comparison)))
.toList();
}

private List<JPlagComparison> getComparisons(JPlagResult result) {
int maxNumberOfComparisons = result.getOptions().maximumNumberOfComparisons();
return result.getComparisons(maxNumberOfComparisons);
private Map<String, Double> getComparisonMetricMap(JPlagComparison comparison) {
return Map.of(SimilarityMetric.AVG.name(), comparison.similarity(), SimilarityMetric.MAX.name(), comparison.maximalSimilarity());
}

private List<Integer> convertDistribution(int[] array) {
private static List<Integer> convertDistribution(int[] array) {
List<Integer> list = new ArrayList<>(Arrays.stream(array).boxed().toList());
Collections.reverse(list);
return list;
}

private List<TopComparison> getTopComparisons(List<JPlagComparison> comparisons, Function<JPlagComparison, Double> similarityExtractor) {
return comparisons.stream().sorted(Comparator.comparing(similarityExtractor).reversed())
.map(comparison -> new TopComparison(submissionToIdFunction.apply(comparison.firstSubmission()),
submissionToIdFunction.apply(comparison.secondSubmission()), similarityExtractor.apply(comparison)))
.toList();
}

private List<TopComparison> getTopComparisons(List<JPlagComparison> comparisons) {
return getTopComparisons(comparisons, JPlagComparison::similarity);
}

private List<TopComparison> getMaxSimilarityTopComparisons(List<JPlagComparison> comparisons) {
return getTopComparisons(comparisons, JPlagComparison::maximalSimilarity);
}

}
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
package de.jplag.reporting.reportobject.model;

import java.util.List;
import java.util.Map;

import com.fasterxml.jackson.annotation.JsonProperty;

/**
* ReportViewer DTO for the comparison of two submissions.
* @param firstSubmissionId id of the first submission
* @param secondSubmissionId id of the second submission
* @param similarity average similarity. between 0.0 and 1.0.
* @param similarities map of metric names and corresponding similarities. between 0.0 and 1.0.
* @param matches the list of matches found in the comparison of the two submissions
*/
public record ComparisonReport(@JsonProperty("id1") String firstSubmissionId, @JsonProperty("id2") String secondSubmissionId,
@JsonProperty("similarity") double similarity, @JsonProperty("matches") List<Match> matches) {
@JsonProperty("similarities") Map<String, Double> similarities, @JsonProperty("matches") List<Match> matches) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ public record OverviewReport(

@JsonProperty("execution_time") long executionTime,

@JsonProperty("metrics") List<Metric> metrics,
@JsonProperty("distributions") Map<String, List<Integer>> distributions,

@JsonProperty("top_comparisons") List<TopComparison> topComparisons,

@JsonProperty("clusters") List<Cluster> clusters,

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package de.jplag.reporting.reportobject.model;

import java.util.Map;

import com.fasterxml.jackson.annotation.JsonProperty;

public record TopComparison(@JsonProperty("first_submission") String firstSubmission, @JsonProperty("second_submission") String secondSubmission,
@JsonProperty("similarity") double similarity) {
@JsonProperty("similarities") Map<String, Double> similarities) {
}
4 changes: 2 additions & 2 deletions core/src/test/java/de/jplag/BaseCodeTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ protected void verifyResults(JPlagResult result) {
assertEquals(2, result.getNumberOfSubmissions());
assertEquals(1, result.getAllComparisons().size());
assertEquals(1, result.getAllComparisons().get(0).matches().size());
assertEquals(1, result.getSimilarityDistribution()[8]);
assertEquals(1, result.getSimilarityDistribution()[81]);
assertEquals(0.8125, result.getAllComparisons().get(0).similarity(), DELTA);
}

Expand Down Expand Up @@ -94,7 +94,7 @@ protected void verifySimpleSubdirectoryDuplicate(JPlagResult result, int submiss
assertEquals(submissions, result.getNumberOfSubmissions());
assertEquals(comparisons, result.getAllComparisons().size());
assertEquals(1, result.getAllComparisons().get(0).matches().size());
assertEquals(1, result.getSimilarityDistribution()[9]);
assertEquals(1, result.getSimilarityDistribution()[94]);
assertEquals(0.9473, result.getAllComparisons().get(0).similarity(), DELTA);
}

Expand Down
9 changes: 6 additions & 3 deletions core/src/test/java/de/jplag/BasicFunctionalityTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
*/
class BasicFunctionalityTest extends TestBase {

private static int DISTRIBUTION_INDEX = 66;

@Test
@DisplayName("test submissions that contain obvious plagiarism")
void testSimpleDuplicate() throws ExitException {
Expand All @@ -22,14 +24,15 @@ void testSimpleDuplicate() throws ExitException {
assertEquals(2, result.getNumberOfSubmissions());
assertEquals(1, result.getAllComparisons().size());
assertEquals(1, result.getAllComparisons().get(0).matches().size());
assertEquals(1, result.getSimilarityDistribution()[6]);
assertEquals(1, result.getSimilarityDistribution()[DISTRIBUTION_INDEX]);
assertEquals(0.666, result.getAllComparisons().get(0).similarity(), DELTA);
}

@Test
@DisplayName("test submissions with a custom minimum token match")
void testWithMinTokenMatch() throws ExitException {
var expectedDistribution = new int[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 1};
var expectedDistribution = new int[100];
expectedDistribution[96] = 1;
JPlagResult result = runJPlag("SimpleDuplicate", it -> it.withMinimumTokenMatch(4));

assertEquals(2, result.getNumberOfSubmissions());
Expand Down Expand Up @@ -90,7 +93,7 @@ void testSingleFileSubmisssions() throws ExitException {

assertEquals(2, result.getNumberOfSubmissions());
assertEquals(1, result.getAllComparisons().size());
assertEquals(1, result.getSimilarityDistribution()[6]);
assertEquals(1, result.getSimilarityDistribution()[DISTRIBUTION_INDEX]);
assertEquals(0.666, result.getAllComparisons().get(0).similarity(), DELTA);

var matches = result.getAllComparisons().get(0).matches();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
Expand All @@ -18,43 +19,42 @@
import de.jplag.reporting.reportobject.model.TopComparison;

public class MetricMapperTest {
private static final List<Integer> EXPECTED_DISTRIBUTION = List.of(29, 23, 19, 17, 13, 11, 7, 5, 3, 2);
private static final List<Integer> EXPECTED_AVG_DISTRIBUTION = List.of(1, 0, 0, 2, 3, 15, 5, 2, 16, 5, 2, 18, 3, 21, 2, 1, 5, 0, 14, 32, 25, 4, 2,
12, 3, 2, 5, 5, 0, 5, 1, 5, 2, 5, 4, 5, 3, 5, 18, 21, 30, 4, 3, 10, 2, 3, 17, 28, 4, 10, 2, 4, 3, 0, 2, 20, 4, 0, 19, 5, 25, 9, 4, 18, 1,
1, 1, 0, 31, 15, 35, 38, 40, 43, 45, 49, 50, 50, 50, 53, 60, 71, 73, 74, 80, 83, 87, 93, 95, 99, 102, 105, 106, 110, 113, 113, 117, 117,
122, 124);
private static final List<Integer> EXPECTED_MAX_DISTRIBUTION = List.of(130, 129, 124, 116, 114, 110, 110, 108, 103, 101, 99, 97, 96, 92, 82, 81,
70, 67, 64, 63, 59, 56, 52, 50, 50, 50, 49, 47, 43, 5, 6, 11, 4, 2, 3, 20, 37, 5, 0, 2, 33, 30, 19, 4, 5, 24, 40, 6, 3, 9, 2, 3, 18, 3, 5,
1, 4, 1, 0, 0, 5, 5, 14, 5, 42, 4, 18, 0, 0, 10, 4, 3, 17, 33, 4, 4, 3, 4, 39, 0, 20, 2, 4, 9, 0, 5, 0, 8, 23, 4, 2, 39, 3, 4, 1, 0, 3,
33, 2, 1);
private final MetricMapper metricMapper = new MetricMapper(Submission::getName);

@Test
public void test_getAverageMetric() {
public void test_getDistributions() {
// given
JPlagResult jPlagResult = createJPlagResult(MockMetric.AVG, distribution(EXPECTED_DISTRIBUTION),
comparison(submission("1"), submission("2"), .7), comparison(submission("3"), submission("4"), .3));
JPlagResult jPlagResult = createJPlagResult(distribution(EXPECTED_AVG_DISTRIBUTION), distribution(EXPECTED_MAX_DISTRIBUTION),
comparison(submission("1"), submission("2"), .7, .8), comparison(submission("3"), submission("4"), .3, .9));

// when
var result = metricMapper.getAverageMetric(jPlagResult);
Map<String, List<Integer>> result = MetricMapper.getDistributions(jPlagResult);

// then
Assertions.assertEquals("AVG", result.name());
Assertions.assertIterableEquals(EXPECTED_DISTRIBUTION, result.distribution());
Assertions.assertEquals(List.of(new TopComparison("1", "2", .7), new TopComparison("3", "4", .3)), result.topComparisons());
Assertions.assertEquals(
"Average of both program coverages. This is the default similarity which"
+ " works in most cases: Matches with a high average similarity indicate that the programs work " + "in a very similar way.",
result.description());
Assertions.assertEquals(Map.of("AVG", EXPECTED_AVG_DISTRIBUTION, "MAX", EXPECTED_MAX_DISTRIBUTION), result);
}

@Test
public void test_getMaxMetric() {
public void test_getTopComparisons() {
// given
JPlagResult jPlagResult = createJPlagResult(MockMetric.MAX, distribution(EXPECTED_DISTRIBUTION),
comparison(submission("00"), submission("01"), .7), comparison(submission("10"), submission("11"), .3));
JPlagResult jPlagResult = createJPlagResult(distribution(EXPECTED_AVG_DISTRIBUTION), distribution(EXPECTED_MAX_DISTRIBUTION),
comparison(submission("1"), submission("2"), .7, .8), comparison(submission("3"), submission("4"), .3, .9));

// when
var result = metricMapper.getMaxMetric(jPlagResult);
List<TopComparison> result = metricMapper.getTopComparisons(jPlagResult);

// then
Assertions.assertEquals("MAX", result.name());
Assertions.assertIterableEquals(EXPECTED_DISTRIBUTION, result.distribution());
Assertions.assertEquals(List.of(new TopComparison("00", "01", .7), new TopComparison("10", "11", .3)), result.topComparisons());
Assertions.assertEquals(
"Maximum of both program coverages. This ranking is especially useful if the programs are very "
+ "different in size. This can happen when dead code was inserted to disguise the origin of the plagiarized program.",
result.description());
List.of(new TopComparison("1", "2", Map.of("AVG", .7, "MAX", .8)), new TopComparison("3", "4", Map.of("AVG", .3, "MAX", .9))),
result);
}

private int[] distribution(List<Integer> expectedDistribution) {
Expand All @@ -67,19 +67,14 @@ private CreateSubmission submission(String name) {
return new CreateSubmission(name);
}

private Comparison comparison(CreateSubmission submission1, CreateSubmission submission2, double similarity) {
return new Comparison(submission1, submission2, similarity);
private Comparison comparison(CreateSubmission submission1, CreateSubmission submission2, double similarity, double maxSimilarity) {
return new Comparison(submission1, submission2, similarity, maxSimilarity);
}

private JPlagResult createJPlagResult(MockMetric metricToMock, int[] distribution, Comparison... createComparisonsDto) {
private JPlagResult createJPlagResult(int[] avgDistribution, int[] maxDistribution, Comparison... createComparisonsDto) {
JPlagResult jPlagResult = mock(JPlagResult.class);

if (metricToMock.equals(MockMetric.AVG)) {
doReturn(distribution).when(jPlagResult).getSimilarityDistribution();
} else if (metricToMock.equals(MockMetric.MAX)) {
doReturn(distribution).when(jPlagResult).getMaxSimilarityDistribution();

}
doReturn(avgDistribution).when(jPlagResult).getSimilarityDistribution();
doReturn(maxDistribution).when(jPlagResult).getMaxSimilarityDistribution();

JPlagOptions options = mock(JPlagOptions.class);
doReturn(createComparisonsDto.length).when(options).maximumNumberOfComparisons();
Expand All @@ -95,27 +90,19 @@ private JPlagResult createJPlagResult(MockMetric metricToMock, int[] distributio
JPlagComparison mockedComparison = mock(JPlagComparison.class);
doReturn(submission1).when(mockedComparison).firstSubmission();
doReturn(submission2).when(mockedComparison).secondSubmission();
if (metricToMock.equals(MockMetric.AVG)) {
doReturn(comparisonDto.similarity).when(mockedComparison).similarity();
} else if (metricToMock.equals(MockMetric.MAX)) {
doReturn(comparisonDto.similarity).when(mockedComparison).maximalSimilarity();
}
doReturn(comparisonDto.similarity).when(mockedComparison).similarity();
doReturn(comparisonDto.maxSimilarity).when(mockedComparison).maximalSimilarity();
comparisonList.add(mockedComparison);
}

doReturn(comparisonList).when(jPlagResult).getComparisons(anyInt());
return jPlagResult;
}

private enum MockMetric {
MAX,
AVG
}

private record Comparison(CreateSubmission submission1, CreateSubmission submission2, double similarity) {
private record Comparison(CreateSubmission submission1, CreateSubmission submission2, double similarity, double maxSimilarity) {
}

private record CreateSubmission(String name) {
}

}
}
Loading

0 comments on commit 5c39647

Please sign in to comment.