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

Support columns in annotations and expose path merging algorithm #167

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -8,6 +8,8 @@
import java.util.UUID;

import edu.kit.kastel.sdq.artemis4j.client.AnnotationSource;
import edu.kit.kastel.sdq.artemis4j.grading.location.LineColumn;
import edu.kit.kastel.sdq.artemis4j.grading.location.Location;
import edu.kit.kastel.sdq.artemis4j.grading.metajson.AnnotationDTO;
import edu.kit.kastel.sdq.artemis4j.grading.penalty.MistakeType;

Expand All @@ -20,9 +22,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 +44,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 +57,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 +86,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 +105,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 +216,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 +244,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 All @@ -8,9 +8,9 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.StringJoiner;
import java.util.stream.Collectors;

import edu.kit.kastel.sdq.artemis4j.grading.location.LocationFormatter;
import edu.kit.kastel.sdq.artemis4j.i18n.FormatString;

/**
Expand Down Expand Up @@ -126,52 +126,23 @@ 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()));

return result;
}

private static String displayLocations(Annotation first, Collection<Annotation> others) {
Map<String, List<Annotation>> positionsByFile = others.stream()
.collect(Collectors.groupingBy(Annotation::getFilePath, LinkedHashMap::new, Collectors.toList()));

// if all annotations are in the same file, we don't need to display the filename
boolean withoutFilename = positionsByFile.size() == 1 && positionsByFile.containsKey(first.getFilePath());

StringJoiner joiner = new StringJoiner(", ");
// Format should look like this: File:(L1, L2, L3), File2:(L4, L5), File3:L5
for (Map.Entry<String, List<Annotation>> entry : positionsByFile.entrySet()) {
String path = entry.getKey();
List<Annotation> filePositions = entry.getValue();

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

if (filePositions.size() > 1 && !withoutFilename) {
lines = "(%s)".formatted(lines);
}

if (withoutFilename) {
joiner.add(lines);
continue;
}

joiner.add("%s:%s".formatted(getFilenameWithoutExtension(path), lines));
private static String displayLocations(Annotation first, Iterable<Annotation> others) {
LocationFormatter formatter = new LocationFormatter();
for (var annotation : others) {
formatter.addLocation(annotation.getLocation());
}

return joiner.toString();
}

private static String getFilenameWithoutExtension(String path) {
String[] parts = path.split("[\\\\\\/]");
String file = parts[parts.length - 1];

return file.split("\\.")[0];
return formatter
.removeSharedPrefix(prefix -> first.getFilePath().startsWith(prefix))
.removeExtension(true)
.format();
}
}
Loading
Loading