![JAR search and dependency download from the Maven repository](/logo.png)
io.github.oliviercailloux.grade.DoubleGrader Maven / Gradle / Ivy
The newest version!
package io.github.oliviercailloux.grade;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Verify.verify;
import static io.github.oliviercailloux.jaris.exceptions.Unchecker.IO_UNCHECKER;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import io.github.oliviercailloux.git.filter.GitHistorySimple;
import io.github.oliviercailloux.git.github.model.GitHubUsername;
import io.github.oliviercailloux.gitjfs.GitPathRootSha;
import io.github.oliviercailloux.grade.DeadlineGrader.LinearPenalizer;
import io.github.oliviercailloux.jaris.throwing.TOptional;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.time.Instant;
import java.time.ZoneId;
import java.util.Optional;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DoubleGrader implements Grader {
private static record Cappings (Instant cappedO, Instant cappedS) {
}
@SuppressWarnings("unused")
private static final Logger LOGGER = LoggerFactory.getLogger(DoubleGrader.class);
private static final Criterion C_DIFF = Criterion.given("diff");
private static final Criterion C_OLD = Criterion.given("old");
private static final Criterion C_SECOND = Criterion.given("second");
private final ZoneId zone;
private final ComplexGrader gO;
private final ComplexGrader gS;
private Instant deadlineO;
private Instant deadlineS;
private Instant capO;
public DoubleGrader(PathGrader m, Instant deadlineO, Instant deadlineS,
ZoneId zone, Instant capO, double userWeight) {
this.deadlineO = deadlineO;
this.deadlineS = deadlineS;
this.capO = capO;
final GitFsGraderUsingLast gitG = GitFsGraderUsingLast.using(m);
final LinearPenalizer l = LinearPenalizer.proportionalToLateness(Duration.ofMinutes(5));
{
final GradePenalizer pO = GradePenalizer.using(l, deadlineO);
gO = ComplexGrader.using(gitG, pO, userWeight);
}
{
final GradePenalizer pS = GradePenalizer.using(l, deadlineS);
gS = ComplexGrader.using(gitG, pS, userWeight);
}
this.zone = zone;
}
@Override
public MarksTree grade(GitHubUsername author, GitHistorySimple data) throws IOException {
final Optional earliestTimeCommitByGitHub;
final GitHistorySimple beforeCommitByGitHub;
try {
earliestTimeCommitByGitHub = ByTimeGrader.earliestTimeCommitByGitHub(data);
LOGGER.debug("Earliest: {}.", earliestTimeCommitByGitHub);
beforeCommitByGitHub = TOptional.wrapping(earliestTimeCommitByGitHub)
.map(t -> data.filtered(i -> i.isBefore(t))).orElse(data);
} catch (IOException e) {
throw new IllegalStateException(e);
}
final String commentGeneralCapped = earliestTimeCommitByGitHub
.map(t -> "; ignored commits after " + t.atZone(zone).toString() + ", sent by GitHub")
.orElse("");
final ImmutableSet cappedsOriginal =
ByTimeGrader.getCapped(beforeCommitByGitHub, deadlineO, capO);
final ImmutableSet cappedsSecond =
ByTimeGrader.getCapped(beforeCommitByGitHub, deadlineS, Instant.MAX);
/*
* To check while building that criteria are unique (for failing faster), as this is required
* later.
*/
final Set criteria = Sets.newLinkedHashSet();
/*
* Probably possible to get rid of this with a more elegant construction, but as a workaround we
* use this to avoid two identical criteria.
*/
final Set allCappings = Sets.newLinkedHashSet();
final ImmutableSet.Builder cappedBuilder = ImmutableSet.builder();
for (GitHistorySimple cappedO : cappedsOriginal) {
/*
* Need to consider also the identical “second chance”, which may give more points (if others
* have diffs, then only this one will have the original grade minus the penalty). Otherwise
* attempt may be detrimental.
*
* Improvement: consider summing the latenesses.
*/
final ImmutableSet.Builder cappedOAndS = ImmutableSet.builder();
cappedOAndS.add(cappedO);
cappedOAndS.addAll(cappedsSecond);
for (GitHistorySimple cappedS : cappedOAndS.build()) {
final MarksTree old = gO.grade(author, cappedO);
final MarksTree second = gS.grade(author, cappedS);
final GitPathRootSha lastO = ByTimeGrader.last(cappedO);
final GitPathRootSha lastS = ByTimeGrader.last(cappedS);
final Instant cappedIO = ByTimeGrader.cappedAt(cappedO);
final Instant cappedIS = ByTimeGrader.cappedAt(cappedS);
final Cappings cappings = new Cappings(cappedIO, cappedIS);
if (allCappings.contains(cappings)) {
LOGGER.debug("Be there, done that already: {}.", cappings);
continue;
}
verify(!cappedIO.isAfter(capO), cappedIO.toString());
final ImmutableSet javasOld =
Files.find(lastO, Integer.MAX_VALUE, (p, a) -> matches(p))
.collect(ImmutableSet.toImmutableSet());
final ImmutableSet javasSecond =
Files.find(lastS, Integer.MAX_VALUE, (p, a) -> matches(p))
.collect(ImmutableSet.toImmutableSet());
final Mark diffMark;
checkState(javasOld.size() <= 1, javasOld);
if (javasOld.size() == 1 && javasSecond.size() == 1) {
final Path javaOld = Iterables.getOnlyElement(javasOld);
final Path javaSecond = Iterables.getOnlyElement(javasSecond);
final int diff = diff(javaOld, javaSecond);
final double propOld = Double.min(15d, diff) / 15d;
final String commentDiff =
"Diff between '%s' and '%s' (%s ≠ lines / 20)".formatted(javaOld, javaSecond, diff);
diffMark = Mark.given(propOld, commentDiff);
} else {
diffMark = Mark.one("Found multiple (or no) files to compare, could not compute diff: "
+ "original %s and second %s".formatted(javasOld, javasSecond));
}
final MarksTree merged =
MarksTree.composite(ImmutableMap.of(C_DIFF, diffMark, C_OLD, old, C_SECOND, second));
final String cappingAtO = "Capping original at " + cappedIO.atZone(zone).toString() + "; ";
// final String cappingAtS = cappedsSecond.size() == 1 ? "no capping (second)"
// : ("capping second at " + cappedIS.atZone(zone).toString());
final String cappingAtS = "capping second at " + cappedIS.atZone(zone).toString();
final String comment = cappingAtO + cappingAtS + commentGeneralCapped;
final Criterion crit = Criterion.given(comment);
LOGGER.debug("Adding crit {}.", crit);
final boolean isNew = criteria.add(crit);
verify(isNew, crit.toString());
allCappings.add(cappings);
cappedBuilder.add(SubMarksTree.given(crit, merged));
}
}
final ImmutableSet cappedGrades = cappedBuilder.build();
final MarksTree byTimeGrade;
if (cappedGrades.isEmpty()) {
byTimeGrade = Mark.zero(String.format("No commit found%s", commentGeneralCapped));
} else {
byTimeGrade = MarksTree.composite(cappedGrades);
}
return byTimeGrade;
}
private boolean matches(Path p) {
// final Pattern pattern = Pattern.compile("StringManiper ");
// return p.getFileName() != null && p.getFileName().toString().endsWith(".java")
// && pattern.matcher(IO_UNCHECKER.getUsing(() -> Files.readString(p))).find();
return String.valueOf(p.getFileName()).equals("StringManiper.java");
}
private int diff(Path javaOld, Path javaSecond) {
final ImmutableSet contentOld =
ImmutableSet.copyOf(IO_UNCHECKER.getUsing(() -> Files.readAllLines(javaOld)));
final ImmutableSet contentSecond =
ImmutableSet.copyOf(IO_UNCHECKER.getUsing(() -> Files.readAllLines(javaSecond)));
final ImmutableSet diff =
Sets.symmetricDifference(contentOld, contentSecond).immutableCopy();
final int size = diff.size();
LOGGER.debug("Content old: {}, content second: {}, diff: {}, size: {}.", contentOld,
contentSecond, diff, size);
return size;
}
@Override
public GradeAggregator getAggregator() {
final GradeAggregator oldAg = gO.getAggregator();
final GradeAggregator secondAg = gS.getAggregator();
verify(oldAg.equals(secondAg));
final GradeAggregator oldDiff = GradeAggregator.parametric(C_OLD, C_DIFF,
ImmutableMap.of(C_OLD, oldAg, C_DIFF, GradeAggregator.TRIVIAL), secondAg);
return GradeAggregator.max(oldDiff);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy