Skip to content

Commit

Permalink
add columns for annotations #158
Browse files Browse the repository at this point in the history
  • Loading branch information
Luro02 committed Jan 29, 2025
1 parent 692f4e7 commit bc4dff0
Show file tree
Hide file tree
Showing 8 changed files with 149 additions and 83 deletions.
70 changes: 27 additions & 43 deletions src/main/java/edu/kit/kastel/sdq/artemis4j/grading/Annotation.java
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* Licensed under EPL-2.0 2024. */
/* Licensed under EPL-2.0 2024-2025. */
package edu.kit.kastel.sdq.artemis4j.grading;

import java.util.ArrayList;
Expand All @@ -20,9 +20,7 @@
public final class Annotation {
private final String uuid;
private final MistakeType type;
private final ArtemisPath filePath;
private final int startLine;
private final int endLine;
private final Location location;
private final AnnotationSource source;
private String customMessage;
private Double customScore;
Expand All @@ -44,9 +42,10 @@ public final class Annotation {
public Annotation(AnnotationDTO dto, MistakeType mistakeType) {
this.uuid = dto.uuid();
this.type = mistakeType;
this.filePath = new ArtemisPath(dto.classFilePath());
this.startLine = dto.startLine();
this.endLine = dto.endLine();
this.location = new Location(
dto.classFilePath(),
new LineColumn(dto.startLine(), Optional.ofNullable(dto.startColumn())),
new LineColumn(dto.endLine(), Optional.ofNullable(dto.endColumn())));
this.source = dto.source() != null ? dto.source() : AnnotationSource.UNKNOWN;
this.customMessage = dto.customMessageForJSON();
this.customScore = dto.customPenaltyForJSON();
Expand All @@ -56,20 +55,16 @@ public Annotation(AnnotationDTO dto, MistakeType mistakeType) {

Annotation(
MistakeType mistakeType,
String filePath,
int startLine,
int endLine,
Location location,
String customMessage,
Double customScore,
AnnotationSource source) {
this(mistakeType, filePath, startLine, endLine, customMessage, customScore, source, List.of(), null);
this(mistakeType, location, customMessage, customScore, source, List.of(), null);
}

Annotation(
MistakeType mistakeType,
String filePath,
int startLine,
int endLine,
Location location,
String customMessage,
Double customScore,
AnnotationSource source,
Expand All @@ -89,9 +84,7 @@ public Annotation(AnnotationDTO dto, MistakeType mistakeType) {

this.uuid = generateUUID();
this.type = mistakeType;
this.filePath = new ArtemisPath(filePath);
this.startLine = startLine;
this.endLine = endLine;
this.location = location;
this.customMessage = customMessage;
this.customScore = customScore;
this.source = source;
Expand All @@ -110,42 +103,49 @@ public MistakeType getMistakeType() {
return type;
}

/**
* Returns the location of this annotation in the source code.
*/
public Location getLocation() {
return this.location;
}

/**
* The path of the file this annotation is associated with, including its file
* ending
*/
public String getFilePath() {
return this.filePath.toString();
return this.getLocation().filePath();
}

/**
* The path of the file this annotation is associated with, excluding its file
* ending
*/
public String getFilePathWithoutType() {
return this.filePath.toString().replace(".java", "");
return this.getFilePath().replace(".java", "");
}

/**
* The line in the file where this annotation starts (0-based)
*/
public int getStartLine() {
return startLine;
return this.getLocation().start().line();
}

/**
* The line in the file where this annotation starts (1-based, for display to
* the user e.g. in Artemis)
*/
public int getDisplayLine() {
return startLine + 1;
return this.getStartLine() + 1;
}

/**
* The line in the file where this annotation ends (0-based)
*/
public int getEndLine() {
return endLine;
return this.getLocation().end().line();
}

/**
Expand Down Expand Up @@ -214,9 +214,11 @@ public AnnotationDTO toDTO() {
return new AnnotationDTO(
uuid,
type.getId(),
startLine,
endLine,
filePath.toString(),
getStartLine(),
getLocation().start().column().orElse(null),
getEndLine(),
getLocation().end().column().orElse(null),
getFilePath(),
customMessage,
customScore,
source,
Expand All @@ -240,22 +242,4 @@ public int hashCode() {
private static String generateUUID() {
return UUID.randomUUID().toString();
}

/**
* A wrapper class that helps to ensure that paths are in the format that artemis expects.
* <p>
* In the past there were problems with paths that contained backslashes. This class ensures that this will never happen again.
*
* @param path the path
*/
private record ArtemisPath(String path) {
private ArtemisPath {
path = path.replace("\\", "/");
}

@Override
public String toString() {
return this.path;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* Licensed under EPL-2.0 2024. */
/* Licensed under EPL-2.0 2024-2025. */
package edu.kit.kastel.sdq.artemis4j.grading;

import java.text.MessageFormat;
Expand Down Expand Up @@ -126,9 +126,7 @@ private static List<Annotation> merge(List<Annotation> annotations, int limit, L

result.add(new Annotation(
firstAnnotation.getMistakeType(),
firstAnnotation.getFilePath(),
firstAnnotation.getStartLine(),
firstAnnotation.getEndLine(),
firstAnnotation.getLocation(),
customMessage,
firstAnnotation.getCustomScore().orElse(null),
firstAnnotation.getSource()));
Expand All @@ -150,7 +148,7 @@ private static String displayLocations(Annotation first, Collection<Annotation>
List<Annotation> filePositions = entry.getValue();

String lines = filePositions.stream()
.map(position -> "L%d".formatted(position.getStartLine()))
.map(position -> "L%d".formatted(position.getDisplayLine()))
.collect(Collectors.joining(", "));

if (filePositions.size() > 1 && !withoutFilename) {
Expand Down
36 changes: 27 additions & 9 deletions src/main/java/edu/kit/kastel/sdq/artemis4j/grading/Assessment.java
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* Licensed under EPL-2.0 2024. */
/* Licensed under EPL-2.0 2024-2025. */
package edu.kit.kastel.sdq.artemis4j.grading;

import java.text.MessageFormat;
Expand Down Expand Up @@ -147,13 +147,23 @@ public List<Annotation> getAnnotations(RatingGroup ratingGroup) {
*/
public Annotation addPredefinedAnnotation(
MistakeType mistakeType, String filePath, int startLine, int endLine, String customMessage) {
return this.addPredefinedAnnotation(mistakeType, new Location(filePath, startLine, endLine), customMessage);
}

/**
* Adds a non-custom manual annotation to the assessment.
*
* @param mistakeType Must not be a custom mistake type
* @param customMessage May be null if no custom message is provided
*/
public Annotation addPredefinedAnnotation(MistakeType mistakeType, Location location, String customMessage) {
if (mistakeType.isCustomAnnotation()) {
throw new IllegalArgumentException("Mistake type is a custom annotation");
}

var source =
this.correctionRound == 0 ? AnnotationSource.MANUAL_FIRST_ROUND : AnnotationSource.MANUAL_SECOND_ROUND;
var annotation = new Annotation(mistakeType, filePath, startLine, endLine, customMessage, null, source);
var annotation = new Annotation(mistakeType, location, customMessage, null, source);
this.annotations.add(annotation);
return annotation;
}
Expand All @@ -171,6 +181,18 @@ public Annotation addCustomAnnotation(
int endLine,
String customMessage,
double customScore) {
return this.addCustomAnnotation(
mistakeType, new Location(filePath, startLine, endLine), customMessage, customScore);
}

/**
* Adds a custom manual annotation to the assessment.
*
* @param mistakeType Must be a custom mistake type
* @param customMessage May not be null
*/
public Annotation addCustomAnnotation(
MistakeType mistakeType, Location location, String customMessage, double customScore) {
if (!mistakeType.isCustomAnnotation()) {
throw new IllegalArgumentException("Mistake type is not a custom annotation");
}
Expand All @@ -182,26 +204,22 @@ public Annotation addCustomAnnotation(

var source =
this.correctionRound == 0 ? AnnotationSource.MANUAL_FIRST_ROUND : AnnotationSource.MANUAL_SECOND_ROUND;
var annotation = new Annotation(mistakeType, filePath, startLine, endLine, customMessage, customScore, source);
var annotation = new Annotation(mistakeType, location, customMessage, customScore, source);
this.annotations.add(annotation);
return annotation;
}

public Annotation addAutograderAnnotation(
MistakeType mistakeType,
String filePath,
int startLine,
int endLine,
Location location,
String explanation,
String checkName,
String problemType,
Integer annotationLimit) {
Double customScore = mistakeType.isCustomAnnotation() ? 0.0 : null;
var annotation = new Annotation(
mistakeType,
filePath,
startLine,
endLine,
location,
explanation,
customScore,
AnnotationSource.AUTOGRADER,
Expand Down
56 changes: 56 additions & 0 deletions src/main/java/edu/kit/kastel/sdq/artemis4j/grading/LineColumn.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/* Licensed under EPL-2.0 2025. */
package edu.kit.kastel.sdq.artemis4j.grading;

import java.util.Comparator;
import java.util.Objects;
import java.util.Optional;

import org.jetbrains.annotations.NotNull;

/**
* A line-column pair representing the start or end of a {@link Location}.
*
* @param line the 0-indexed line in the source file on which the {@link Location} starts or ends (inclusive)
* @param column the 0-indexed column in the source file on which the {@link Location} starts or ends (inclusive). If empty, the entire line is spanned.
*/
public record LineColumn(int line, Optional<Integer> column) implements Comparable<LineColumn> {
/**
* Constructs a {@link LineColumn} spanning the entire line.
*
* @param line the line in the source file
* @return a {@link LineColumn} spanning the entire line
*/
public static LineColumn entireLine(int line) {
return new LineColumn(line, Optional.empty());
}

@Override
public boolean equals(Object object) {
if (!(object instanceof LineColumn that)) {
return false;
}

return this.line() == that.line() && this.column() == that.column();
}

@Override
public int hashCode() {
return Objects.hash(this.line, this.column);
}

@Override
public int compareTo(@NotNull LineColumn other) {
return Comparator.comparingInt(LineColumn::line)
.thenComparing(LineColumn::column, Comparator.comparingInt(value -> value.orElse(Integer.MAX_VALUE)))
.compare(this, other);
}

@Override
public String toString() {
if (this.column.isEmpty()) {
return "L%d".formatted(this.line + 1);
}

return "L%d:%d".formatted(this.line + 1, this.column.get() + 1);
}
}
18 changes: 18 additions & 0 deletions src/main/java/edu/kit/kastel/sdq/artemis4j/grading/Location.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/* Licensed under EPL-2.0 2025. */
package edu.kit.kastel.sdq.artemis4j.grading;

public record Location(String filePath, LineColumn start, LineColumn end) {
public Location {
if (start.compareTo(end) > 0) {
throw new IllegalArgumentException("start %s must be before end %s".formatted(start, end));
}

// In the past there were problems with paths that contained backslashes. This ensures that this will never
// happen again.
filePath = filePath.replace("\\", "/");
}

public Location(String filePath, int startLine, int endLine) {
this(filePath, LineColumn.entireLine(startLine), LineColumn.entireLine(endLine));
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* Licensed under EPL-2.0 2024. */
/* Licensed under EPL-2.0 2024-2025. */
package edu.kit.kastel.sdq.artemis4j.grading.autograder;

import java.io.IOException;
Expand All @@ -16,6 +16,8 @@
import de.firemage.autograder.api.loader.AutograderLoader;
import edu.kit.kastel.sdq.artemis4j.grading.Assessment;
import edu.kit.kastel.sdq.artemis4j.grading.ClonedProgrammingSubmission;
import edu.kit.kastel.sdq.artemis4j.grading.LineColumn;
import edu.kit.kastel.sdq.artemis4j.grading.Location;

public final class AutograderRunner {
private AutograderRunner() {}
Expand Down Expand Up @@ -71,9 +73,10 @@ public static AutograderStats runAutograder(
var position = problem.getPosition();
assessment.addAutograderAnnotation(
mistakeType,
"src/" + position.path().toString(),
position.startLine() - 1,
position.endLine() - 1,
new Location(
"src/" + position.path().toString(),
new LineColumn(position.startLine() - 1, position.startColumn() - 1),
new LineColumn(position.endLine() - 1, position.endColumn() - 1)),
autograder.translateMessage(problem.getExplanation()),
problem.getCheckName(),
problem.getType(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* Licensed under EPL-2.0 2024. */
/* Licensed under EPL-2.0 2024-2025. */
package edu.kit.kastel.sdq.artemis4j.grading.metajson;

import java.util.List;
Expand All @@ -14,7 +14,9 @@ public record AnnotationDTO(
@JsonProperty String uuid,
@JsonProperty String mistakeTypeId,
@JsonProperty int startLine,
@JsonProperty Integer startColumn,
@JsonProperty int endLine,
@JsonProperty Integer endColumn,
@JsonProperty String classFilePath,
@JsonProperty String customMessageForJSON,
@JsonProperty Double customPenaltyForJSON,
Expand Down
Loading

0 comments on commit bc4dff0

Please sign in to comment.