All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.oliviercailloux.javagrade.graders.BranchingBis Maven / Gradle / Ivy

The newest version!
package io.github.oliviercailloux.javagrade.graders;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Verify.verify;

import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.MoreCollectors;
import com.google.common.graph.Graphs;
import io.github.oliviercailloux.git.filter.GitHistorySimple;
import io.github.oliviercailloux.gitjfs.GitPath;
import io.github.oliviercailloux.gitjfs.GitPathRoot;
import io.github.oliviercailloux.gitjfs.GitPathRootRef;
import io.github.oliviercailloux.gitjfs.GitPathRootSha;
import io.github.oliviercailloux.gitjfs.GitPathRootShaCached;
import io.github.oliviercailloux.grade.BatchGitHistoryGrader;
import io.github.oliviercailloux.grade.Criterion;
import io.github.oliviercailloux.grade.GitFileSystemWithHistoryFetcherByPrefix;
import io.github.oliviercailloux.grade.GitFsGrader;
import io.github.oliviercailloux.grade.GitGrader.Predicates;
import io.github.oliviercailloux.grade.Grade;
import io.github.oliviercailloux.grade.GradeAggregator;
import io.github.oliviercailloux.grade.GradeUtils;
import io.github.oliviercailloux.grade.IGrade.CriteriaPath;
import io.github.oliviercailloux.grade.Mark;
import io.github.oliviercailloux.grade.MarksTree;
import io.github.oliviercailloux.grade.SubMark;
import io.github.oliviercailloux.grade.SubMarksTree;
import io.github.oliviercailloux.jaris.throwing.TFunction;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.time.Duration;
import java.time.Instant;
import java.time.ZonedDateTime;
import java.util.LinkedHashMap;
import java.util.Set;
import java.util.regex.Pattern;
import name.falgout.jeffrey.throwing.stream.ThrowingStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class BranchingBis implements GitFsGrader {
  @Deprecated
  public static SubMarksTree subGrade(GitPathRoot gitPathRoot,
      TFunction grader)
      throws IOException, NoSuchFileException {
    final MarksTree grade = grader.apply(gitPathRoot);
    return subGrade(gitPathRoot, grade);
  }

  @Deprecated
  public static SubMarksTree subGradeC(GitPathRootShaCached gitPathRoot,
      TFunction grader)
      throws IOException, NoSuchFileException {
    final MarksTree grade = grader.apply(gitPathRoot);
    return subGrade(gitPathRoot, grade);
  }

  public static SubMarksTree subGrade(GitPathRoot gitPathRoot, MarksTree grade)
      throws IOException, NoSuchFileException {
    final String comment = "Using " + gitPathRoot.getCommit().id().getName();
    final MarksTree newTree = addComment(grade, comment);

    final Criterion criterion = Criterion.given(comment);
    return SubMarksTree.given(criterion, newTree);
  }

  public static SubMarksTree subGrade(GitPathRootShaCached gitPathRoot, MarksTree grade) {
    final String comment = "Using " + gitPathRoot.getCommit().id().getName();
    final MarksTree newTree = addComment(grade, comment);

    final Criterion criterion = Criterion.given(comment);
    return SubMarksTree.given(criterion, newTree);
  }

  public static MarksTree addComment(MarksTree grade, String comment) {
    final Criterion sub1 = grade.getCriteria().stream().findFirst().orElseThrow();
    final MarksTree tree1 = grade.getTree(sub1);
    verify(tree1.isMark());
    final Mark mark1 = tree1.getMark(CriteriaPath.ROOT);
    final String commentOriginal = mark1.getComment();
    final ImmutableMap subs = Maps.toMap(grade.getCriteria(), grade::getTree);
    final LinkedHashMap subsNew = new LinkedHashMap<>(subs);
    final String newComment =
        commentOriginal.isEmpty() ? comment : commentOriginal + " (" + comment + ")";
    subsNew.put(sub1, Mark.given(mark1.getPoints(), newComment));
    return MarksTree.composite(subsNew);
  }

  @SuppressWarnings("unused")
  private static final Logger LOGGER = LoggerFactory.getLogger(BranchingBis.class);

  public static final String PREFIX = "branching-bis";

  public static final ZonedDateTime DEADLINE =
      ZonedDateTime.parse("2022-03-21T14:05:00+01:00[Europe/Paris]");

  public static final double USER_WEIGHT = 0.01d;

  private static final boolean EXPAND = false;

  public static void main(String[] args) throws Exception {
    final BatchGitHistoryGrader grader = BatchGitHistoryGrader
        .given(() -> GitFileSystemWithHistoryFetcherByPrefix.getRetrievingByPrefix(PREFIX));
    grader.getAndWriteGrades(DEADLINE, Duration.ofMinutes(5), new BranchingBis(), USER_WEIGHT,
        Path.of("grades " + PREFIX), PREFIX + " " + Instant.now().atZone(DEADLINE.getZone()));
  }

  private static final Criterion C_ANY = Criterion.given("Anything committed");

  private static final Criterion C_START = Criterion.given("Commit Start");
  private static final Criterion C_ONE = Criterion.given("Exactly one file");
  private static final Criterion C_EXISTS_START = Criterion.given("Exists Start file");
  private static final Criterion C_CONTENTS_START = Criterion.given("Contents Start file");

  private static final Criterion C_A = Criterion.given("Commit A");
  private static final Criterion C_TWO = Criterion.given("Exactly two files");
  private static final Criterion C_BR1 = Criterion.given("Branch br1");
  private static final Criterion C_EXISTS_FIRST = Criterion.given("Exists First file");
  private static final Criterion C_CONTENTS_FIRST = Criterion.given("Contents First file");

  private static final Criterion C_B = Criterion.given("Commit B");
  private static final Criterion C_THREE = Criterion.given("Exactly three files");
  private static final Criterion C_EXISTS_SOME = Criterion.given("Exists Some file");
  private static final Criterion C_CONTENTS_SOME = Criterion.given("Contents Some file");

  private static final Criterion C_C = Criterion.given("Commit C");
  private static final Criterion C_BR2 = Criterion.given("Branch br2");

  private static final Criterion C_D = Criterion.given("Commit D");
  private static final Criterion C_BR3 = Criterion.given("Branch br3");
  private static final Criterion C_PARENTS = Criterion.given("Right parents");

  private GitHistorySimple currentHistory;

  private GitPathRootShaCached startPath;
  private GitPathRootShaCached aPath;
  private GitPathRootShaCached bPath;
  private GitPathRootShaCached cPath;

  BranchingBis() {
    currentHistory = null;
    startPath = null;
    aPath = null;
    bPath = null;
    cPath = null;
  }

  @Override
  public MarksTree grade(GitHistorySimple data) throws IOException {
    startPath = null;
    aPath = null;
    bPath = null;
    cPath = null;

    currentHistory = data;
    verify(!currentHistory.graph().nodes().isEmpty());

    final ImmutableSet commitsOrdered = currentHistory.roots().stream()
        .flatMap(r -> Graphs.reachableNodes(currentHistory.graph(), r).stream())
        .collect(ImmutableSet.toImmutableSet());
    final int nbCommits = commitsOrdered.size();

    final MarksTree anyCommitMark = Mark.binary(!commitsOrdered.isEmpty(), String.format(
        "Found %s commit%s, including the root ones", nbCommits, nbCommits == 1 ? "" : "s"), "");
    final SubMarksTree subAny = SubMarksTree.given(C_ANY, anyCommitMark);

    final SubMarksTree subStart = gradeStartFromCommits(commitsOrdered, EXPAND);
    if (EXPAND) {
      return MarksTree.composite(ImmutableSet.of(subAny, subStart));
    }

    final SubMarksTree subA = gradeAFromStart(startPath, false);
    final SubMarksTree subB = gradeBFromA(aPath, false);
    final SubMarksTree subC = gradeCFromB(bPath, false);
    final SubMarksTree subD = gradeDFromBAndC(bPath, cPath, false);
    return MarksTree.composite(ImmutableSet.of(subAny, subStart, subA, subB, subC, subD));
  }

  private MarksTree compositeOrZero(Set subs, String commentIfEmpty) {
    return subs.isEmpty() ? Mark.zero(commentIfEmpty) : MarksTree.composite(subs);
  }

  private ImmutableMap.Builder startWeightsBuilder() {
    final ImmutableMap.Builder builder = ImmutableMap.builder();
    builder.put(C_ONE, 1d);
    builder.put(C_EXISTS_START, 0.5d);
    builder.put(C_CONTENTS_START, 0.5d);
    return builder;
  }

  private ImmutableMap.Builder aWeightsBuilder() {
    final ImmutableMap.Builder builder = ImmutableMap.builder();
    builder.put(C_TWO, 0.2d);
    builder.put(C_BR1, 3.0d);
    builder.put(C_EXISTS_START, 0.15d);
    builder.put(C_CONTENTS_START, 0.35d);
    builder.put(C_EXISTS_FIRST, 0.15d);
    builder.put(C_CONTENTS_FIRST, 0.35d);
    return builder;
  }

  private ImmutableMap.Builder bWeightsBuilder() {
    final ImmutableMap.Builder builder = ImmutableMap.builder();
    builder.put(C_THREE, 1d);
    builder.put(C_EXISTS_START, 0.5d);
    builder.put(C_CONTENTS_START, 0.5d);
    builder.put(C_EXISTS_FIRST, 0.5d);
    builder.put(C_CONTENTS_FIRST, 0.5d);
    builder.put(C_EXISTS_SOME, 0.5d);
    builder.put(C_CONTENTS_SOME, 0.5d);
    return builder;
  }

  private ImmutableMap.Builder cWeightsBuilder() {
    final ImmutableMap.Builder builder = ImmutableMap.builder();
    builder.put(C_TWO, 0.2d);
    builder.put(C_BR2, 3.0d);
    builder.put(C_EXISTS_START, 0.15d);
    builder.put(C_CONTENTS_START, 0.35d);
    builder.put(C_EXISTS_FIRST, 0.15d);
    builder.put(C_CONTENTS_FIRST, 0.35d);
    return builder;
  }

  private GradeAggregator dAggregatorFlat() {
    final GradeAggregator dAg;
    {
      final ImmutableMap.Builder builder = ImmutableMap.builder();
      builder.put(C_THREE, 0.1d);
      builder.put(C_BR3, 3.0d);
      builder.put(C_EXISTS_START, 0.05d);
      builder.put(C_CONTENTS_START, 0.05d);
      builder.put(C_EXISTS_FIRST, 0.1d);
      builder.put(C_CONTENTS_FIRST, 0.9d);
      builder.put(C_EXISTS_SOME, 0.05d);
      builder.put(C_CONTENTS_SOME, 0.05d);
      builder.put(C_PARENTS, 0.1d);
      dAg = GradeAggregator.staticAggregator(builder.build(), ImmutableMap.of());
    }
    return dAg;
  }

  @Override
  public GradeAggregator getAggregator() {
    if (EXPAND) {
      return getAggregatorExpanded();
    }
    return getAggregatorFlattened();
  }

  private GradeAggregator getAggregatorExpanded() {
    final GradeAggregator startAg = startAggregatorExpanded();
    final GradeAggregator mainAg;
    {
      final ImmutableMap.Builder builder = ImmutableMap.builder();
      builder.put(C_ANY, 1d);
      builder.put(C_START, 18.8d);
      mainAg = GradeAggregator.staticAggregator(builder.build(), ImmutableMap.of(C_START, startAg));
    }
    return mainAg;
  }

  private GradeAggregator startAggregatorExpanded() {
    final GradeAggregator aAg = aAggregatorExpanded();
    final GradeAggregator startAg;
    {
      final ImmutableMap.Builder builder = startWeightsBuilder();
      builder.put(C_A, 16.8d);
      final GradeAggregator startAggregator =
          GradeAggregator.staticAggregator(builder.build(), ImmutableMap.of(C_A, aAg));
      startAg = GradeAggregator.max(startAggregator);
    }
    return startAg;
  }

  private GradeAggregator aAggregatorExpanded() {
    final GradeAggregator bAg = bAggregatorExpanded();
    final GradeAggregator aAg;
    {
      final ImmutableMap.Builder builder = aWeightsBuilder();
      builder.put(C_B, 12.6d);
      final GradeAggregator aAggregator =
          GradeAggregator.staticAggregator(builder.build(), ImmutableMap.of(C_B, bAg));
      aAg = GradeAggregator.max(aAggregator);
    }
    return aAg;
  }

  private GradeAggregator bAggregatorExpanded() {
    final GradeAggregator cAg = cAggregatorExpanded();
    final GradeAggregator bAg;
    {
      final ImmutableMap.Builder builder = bWeightsBuilder();
      builder.put(C_C, 8.6d);
      final GradeAggregator bAggregator =
          GradeAggregator.staticAggregator(builder.build(), ImmutableMap.of(C_C, cAg));
      bAg = GradeAggregator.max(bAggregator);
    }
    return bAg;
  }

  private GradeAggregator cAggregatorExpanded() {
    final GradeAggregator dAg = dAggregatorExpanded();
    final GradeAggregator cAg;
    {
      final ImmutableMap.Builder builder = cWeightsBuilder();
      builder.put(C_D, 4.4d);
      final GradeAggregator cAggregator =
          GradeAggregator.staticAggregator(builder.build(), ImmutableMap.of(C_D, dAg));
      cAg = GradeAggregator.max(cAggregator);
    }
    return cAg;
  }

  private GradeAggregator dAggregatorExpanded() {
    return GradeAggregator.max(dAggregatorFlat());
  }

  private GradeAggregator getAggregatorFlattened() {
    final GradeAggregator dAg = dAggregatorFlat();
    final GradeAggregator cAg;
    {
      final ImmutableMap.Builder builder = cWeightsBuilder();
      cAg = GradeAggregator.staticAggregator(builder.build(), ImmutableMap.of());
    }
    final GradeAggregator bAg;
    {
      final ImmutableMap.Builder builder = bWeightsBuilder();
      bAg = GradeAggregator.staticAggregator(builder.build(), ImmutableMap.of());
    }
    final GradeAggregator aAg;
    {
      final ImmutableMap.Builder builder = aWeightsBuilder();
      aAg = GradeAggregator.staticAggregator(builder.build(), ImmutableMap.of());
    }
    final GradeAggregator startAg;
    {
      final ImmutableMap.Builder builder = startWeightsBuilder();
      startAg = GradeAggregator.staticAggregator(builder.build(), ImmutableMap.of());
    }
    final GradeAggregator mainAg;
    {
      final ImmutableMap.Builder builder = ImmutableMap.builder();
      builder.put(C_ANY, 1d);
      builder.put(C_START, 2d);
      builder.put(C_A, 4.2d);
      builder.put(C_B, 4.0d);
      builder.put(C_C, 4.2d);
      builder.put(C_D, 4.4d);
      mainAg = GradeAggregator.staticAggregator(builder.build(),
          ImmutableMap.of(C_START, startAg, C_A, aAg, C_B, bAg, C_C, cAg, C_D, dAg));
    }
    return mainAg;
  }

  private SubMarksTree gradeStartFromCommits(ImmutableSet commitsOrdered,
      boolean expandLocally) throws IOException {
    final ImmutableBiMap subs = ThrowingStream.of(commitsOrdered.stream(), IOException.class)
            .filter(r -> currentHistory.graph().predecessors(r).size() <= 1)
            .collect(ImmutableBiMap.toImmutableBiMap(r -> r, r -> {
              try {
                return subGradeC(r, w -> gradeStart(w, true));
              } catch (NoSuchFileException e) {
                throw new IllegalStateException(e);
              } catch (IOException e) {
                throw new IllegalStateException(e);
              }
            }));
    final MarksTree tree = compositeOrZero(subs.values(), "No commit");
    if (expandLocally) {
      return SubMarksTree.given(C_START, tree);
    }
    final ImmutableMap weightedSubMarks =
        Grade.given(startAggregatorExpanded(), tree).getWeightedSubMarks();
    final Criterion bestPath = Maps.filterValues(weightedSubMarks, w -> w == 1d).keySet().stream()
        .map(SubMark::getCriterion).collect(MoreCollectors.onlyElement());
    startPath = Maps.filterValues(subs, s -> s.getCriterion().equals(bestPath)).keySet().stream()
        .collect(MoreCollectors.onlyElement());
    return SubMarksTree.given(C_START, gradeStart(startPath, false));
  }

  private MarksTree gradeStart(GitPathRootShaCached commitStart, boolean expandLocally)
      throws IOException {
    final long nbFiles =
        Files.find(commitStart, Integer.MAX_VALUE, (p, a) -> Files.isRegularFile(p)).count();
    final boolean rightNb = nbFiles == 1;
    final SubMarksTree subNb =
        SubMarksTree.given(C_ONE, Mark.binary(rightNb, "", "Found " + nbFiles + " files"));

    final GitPath startFilePath = commitStart.resolve("starting point.txt");
    final boolean startExists = Files.exists(startFilePath);
    final String startContent = startExists ? Files.readString(startFilePath) : "";
    final Pattern pattern = Pattern.compile("A starting point\\v*");
    final boolean startMatches = pattern.matcher(startContent).matches();
    final SubMarksTree subExists = SubMarksTree.given(C_EXISTS_START, Mark.binary(startExists));
    final SubMarksTree subMatches = SubMarksTree.given(C_CONTENTS_START, Mark.binary(startMatches));

    final ImmutableSet.Builder builder = ImmutableSet.builder();
    builder.add(subNb, subExists, subMatches);
    if (expandLocally) {
      final SubMarksTree subA = gradeAFromStart(commitStart, true);
      builder.add(subA);
    }
    return MarksTree.composite(builder.build());
  }

  private SubMarksTree gradeAFromStart(GitPathRootShaCached commitStart, boolean expandLocally)
      throws IOException {
    if (commitStart == null) {
      return SubMark.given(C_A, Mark.zero());
    }

    final Set successors = currentHistory.graph().successors(commitStart);
    final ImmutableBiMap subs = ThrowingStream.of(successors.stream(), IOException.class)
            .filter(r -> currentHistory.graph().predecessors(r).size() == 1)
            .collect(ImmutableBiMap.toImmutableBiMap(r -> r, r -> {
              try {
                return subGradeC(r, w -> gradeA(w, true));
              } catch (NoSuchFileException e) {
                throw new IllegalStateException(e);
              } catch (IOException e) {
                throw new IllegalStateException(e);
              }
            }));
    final MarksTree tree = compositeOrZero(subs.values(), "No successor of Start");
    if (expandLocally || tree.isMark()) {
      return SubMarksTree.given(C_A, tree);
    }
    final ImmutableMap weightedSubMarks =
        Grade.given(aAggregatorExpanded(), tree).getWeightedSubMarks();
    final Criterion bestPath = Maps.filterValues(weightedSubMarks, w -> w == 1d).keySet().stream()
        .map(SubMark::getCriterion).collect(MoreCollectors.onlyElement());
    aPath = Maps.filterValues(subs, s -> s.getCriterion().equals(bestPath)).keySet().stream()
        .collect(MoreCollectors.onlyElement());
    return SubMarksTree.given(C_A, gradeA(aPath, false));
  }

  private MarksTree gradeA(GitPathRootShaCached commitA, boolean expandLocally) throws IOException {
    checkArgument(currentHistory.graph().predecessors(commitA).size() == 1);

    final long nbFiles =
        Files.find(commitA, Integer.MAX_VALUE, (p, a) -> Files.isRegularFile(p)).count();
    final boolean rightNb = nbFiles == 2;
    final SubMarksTree subNb =
        SubMarksTree.given(C_TWO, Mark.binary(rightNb, "", "Found " + nbFiles + " files"));

    final boolean isBranch = ThrowingStream.of(refsTo(commitA.toSha()).stream(), IOException.class)
        .filter(GitPathRoot::isRef).map(GitPathRoot::getGitRef)
        .anyMatch(r -> Predicates.isBranch(r, "br1"));
    final SubMarksTree subBranch = SubMarksTree.given(C_BR1, Mark.binary(isBranch));

    final GitPath startFilePath = commitA.resolve("starting point.txt");
    final boolean startExists = Files.exists(startFilePath);
    final String startContent = startExists ? Files.readString(startFilePath) : "";
    final Pattern patternStart = Pattern.compile("A starting point\\v*");
    final boolean startMatches = patternStart.matcher(startContent).matches();
    final SubMarksTree subStartExists =
        SubMarksTree.given(C_EXISTS_START, Mark.binary(startExists));
    final SubMarksTree subStartMatches =
        SubMarksTree.given(C_CONTENTS_START, Mark.binary(startMatches));

    final GitPath firstPath = commitA.resolve("first file.txt");
    final boolean firstExists = Files.exists(firstPath);
    final String firstContent = firstExists ? Files.readString(firstPath) : "";
    final Pattern patternFirst = Pattern.compile("Hello\\h?!\\v*");
    verify(patternFirst.matcher("Hello!").matches());
    verify(patternFirst.matcher("Hello !\n").matches());
    verify(!patternFirst.matcher("Hello").matches());
    final boolean firstMatches = patternFirst.matcher(firstContent).matches();
    final SubMarksTree subExists = SubMarksTree.given(C_EXISTS_FIRST, Mark.binary(firstExists));
    final SubMarksTree subMatches = SubMarksTree.given(C_CONTENTS_FIRST, Mark.binary(firstMatches));

    final ImmutableSet.Builder builder = ImmutableSet.builder();
    builder.add(subNb, subBranch, subStartExists, subStartMatches, subExists, subMatches);
    if (expandLocally) {
      final SubMarksTree subB = gradeBFromA(commitA, true);
      builder.add(subB);
    }
    return MarksTree.composite(builder.build());
  }

  private SubMarksTree gradeBFromA(GitPathRootShaCached commitA, boolean expandLocally)
      throws IOException {
    if (commitA == null) {
      return SubMark.given(C_B, Mark.zero());
    }

    final Set successors = currentHistory.graph().successors(commitA);
    final ImmutableBiMap subs = ThrowingStream.of(successors.stream(), IOException.class)
            .filter(r -> currentHistory.graph().predecessors(r).size() == 1)
            .collect(ImmutableBiMap.toImmutableBiMap(r -> r, r -> {
              try {
                return subGradeC(r, w -> gradeB(w, true));
              } catch (NoSuchFileException e) {
                throw new IllegalStateException(e);
              } catch (IOException e) {
                throw new IllegalStateException(e);
              }
            }));
    final MarksTree tree = compositeOrZero(subs.values(), "No successor of A");
    if (expandLocally || tree.isMark()) {
      return SubMarksTree.given(C_B, tree);
    }
    final ImmutableMap weightedSubMarks =
        Grade.given(bAggregatorExpanded(), tree).getWeightedSubMarks();
    final Criterion bestPath = Maps.filterValues(weightedSubMarks, w -> w == 1d).keySet().stream()
        .map(SubMark::getCriterion).collect(MoreCollectors.onlyElement());
    bPath = Maps.filterValues(subs, s -> s.getCriterion().equals(bestPath)).keySet().stream()
        .collect(MoreCollectors.onlyElement());
    return SubMarksTree.given(C_B, gradeB(bPath, false));
  }

  private MarksTree gradeB(GitPathRootShaCached commitB, boolean expandLocally) throws IOException {
    checkArgument(currentHistory.graph().predecessors(commitB).size() == 1);
    {
      final GitPathRootShaCached commitA = currentHistory.graph().predecessors(commitB).stream()
          .collect(MoreCollectors.onlyElement());
      checkArgument(currentHistory.graph().predecessors(commitA).size() == 1);
    }

    final long nbFiles =
        Files.find(commitB, Integer.MAX_VALUE, (p, a) -> Files.isRegularFile(p)).count();
    final boolean rightNb = nbFiles == 3;
    final SubMarksTree subNb =
        SubMarksTree.given(C_THREE, Mark.binary(rightNb, "", "Found " + nbFiles + " files"));

    final GitPath startFilePath = commitB.resolve("starting point.txt");
    final boolean startExists = Files.exists(startFilePath);
    final String startContent = startExists ? Files.readString(startFilePath) : "";
    final Pattern patternStart = Pattern.compile("A starting point\\v*");
    final boolean startMatches = patternStart.matcher(startContent).matches();
    final SubMarksTree subStartExists =
        SubMarksTree.given(C_EXISTS_START, Mark.binary(startExists));
    final SubMarksTree subStartMatches =
        SubMarksTree.given(C_CONTENTS_START, Mark.binary(startMatches));

    final GitPath firstPath = commitB.resolve("first file.txt");
    final boolean firstExists = Files.exists(firstPath);
    final String firstContent = firstExists ? Files.readString(firstPath) : "";
    final Pattern patternFirst = Pattern.compile("Hello\\h?!\\v*");
    final boolean firstMatches = patternFirst.matcher(firstContent).matches();
    final SubMarksTree subExists = SubMarksTree.given(C_EXISTS_FIRST, Mark.binary(firstExists));
    final SubMarksTree subMatches = SubMarksTree.given(C_CONTENTS_FIRST, Mark.binary(firstMatches));

    final GitPath somePath = commitB.resolve("My folder/second file.txt");
    final boolean someExists = Files.exists(somePath);
    final String someContent = someExists ? Files.readString(somePath) : "";
    final Pattern somePattern = Pattern.compile("Second\\v*");
    final boolean someMatches = somePattern.matcher(someContent).matches();
    final SubMarksTree subSomeExists = SubMarksTree.given(C_EXISTS_SOME, Mark.binary(someExists));
    final SubMarksTree subSomeMatches =
        SubMarksTree.given(C_CONTENTS_SOME, Mark.binary(someMatches));

    final ImmutableSet.Builder builder = ImmutableSet.builder();
    builder.add(subNb, subStartExists, subStartMatches, subExists, subMatches, subSomeExists,
        subSomeMatches);
    if (expandLocally) {
      final SubMarksTree subC = gradeCFromB(commitB, true);
      builder.add(subC);
    }
    return MarksTree.composite(builder.build());
  }

  private SubMarksTree gradeCFromB(GitPathRootShaCached commitB, boolean expandLocally)
      throws IOException {
    if (commitB == null) {
      return SubMark.given(C_C, Mark.zero());
    }

    final GitPathRootShaCached commitA =
        currentHistory.graph().predecessors(commitB).stream().collect(MoreCollectors.onlyElement());
    final GitPathRootShaCached start =
        currentHistory.graph().predecessors(commitA).stream().collect(MoreCollectors.onlyElement());
    final Set siblingsOfA = currentHistory.graph().successors(start).stream()
        .filter(r -> !r.equals(commitA)).collect(ImmutableSet.toImmutableSet());
    final ImmutableBiMap subs =
        ThrowingStream.of(siblingsOfA.stream(), IOException.class)
            .collect(ImmutableBiMap.toImmutableBiMap(r -> r, r -> {
              try {
                return subGradeC(r, w -> gradeC(commitB, w, true));
              } catch (NoSuchFileException e) {
                throw new IllegalStateException(e);
              } catch (IOException e) {
                throw new IllegalStateException(e);
              }
            }));
    final MarksTree tree = compositeOrZero(subs.values(), "No sibling of A");
    if (expandLocally || tree.isMark()) {
      return SubMarksTree.given(C_C, tree);
    }
    final ImmutableMap weightedSubMarks =
        Grade.given(cAggregatorExpanded(), tree).getWeightedSubMarks();
    final Criterion bestPath = Maps.filterValues(weightedSubMarks, w -> w == 1d).keySet().stream()
        .map(SubMark::getCriterion).collect(MoreCollectors.onlyElement());
    cPath = Maps.filterValues(subs, s -> s.getCriterion().equals(bestPath)).keySet().stream()
        .collect(MoreCollectors.onlyElement());
    return SubMarksTree.given(C_C, gradeC(commitB, cPath, false));
  }

  private MarksTree gradeC(GitPathRootShaCached commitB, GitPathRootShaCached commitC,
      boolean expandLocally) throws IOException {
    final long nbFiles =
        Files.find(commitC, Integer.MAX_VALUE, (p, a) -> Files.isRegularFile(p)).count();
    final boolean rightNb = nbFiles == 2;
    final SubMarksTree subNb =
        SubMarksTree.given(C_TWO, Mark.binary(rightNb, "", "Found " + nbFiles + " files"));

    final boolean isBranch = ThrowingStream.of(refsTo(commitC.toSha()).stream(), IOException.class)
        .filter(GitPathRoot::isRef).map(GitPathRoot::getGitRef)
        .anyMatch(r -> Predicates.isBranch(r, "br2"));
    final SubMarksTree subBranch = SubMarksTree.given(C_BR2, Mark.binary(isBranch));

    final GitPath startFilePath = commitC.resolve("starting point.txt");
    final boolean startExists = Files.exists(startFilePath);
    final String startContent = startExists ? Files.readString(startFilePath) : "";
    final Pattern patternStart = Pattern.compile("A starting point\\v*");
    final boolean startMatches = patternStart.matcher(startContent).matches();
    final SubMarksTree subStartExists =
        SubMarksTree.given(C_EXISTS_START, Mark.binary(startExists));
    final SubMarksTree subStartMatches =
        SubMarksTree.given(C_CONTENTS_START, Mark.binary(startMatches));

    final GitPath firstPath = commitC.resolve("first file.txt");
    final boolean firstExists = Files.exists(firstPath);
    final String firstContent = firstExists ? Files.readString(firstPath) : "";
    final Pattern pattern = Pattern.compile("Coucou\\h?!\\v*");
    final boolean firstMatches = pattern.matcher(firstContent).matches();
    final SubMarksTree subExists = SubMarksTree.given(C_EXISTS_FIRST, Mark.binary(firstExists));
    final SubMarksTree subMatches = SubMarksTree.given(C_CONTENTS_FIRST, Mark.binary(firstMatches));

    final ImmutableSet.Builder builder = ImmutableSet.builder();
    builder.add(subNb, subBranch, subStartExists, subStartMatches, subExists, subMatches);
    if (expandLocally) {
      final SubMarksTree subD = gradeDFromBAndC(commitB, commitC, true);
      builder.add(subD);
    }
    return MarksTree.composite(builder.build());
  }

  private SubMarksTree gradeDFromBAndC(GitPathRootShaCached commitB, GitPathRootShaCached commitC,
      boolean expandLocally) throws IOException {
    if (commitB == null || commitC == null) {
      return SubMark.given(C_D, Mark.zero());
    }

    final Set successors = currentHistory.graph().successors(commitC);
    final ImmutableBiMap subs = ThrowingStream.of(successors.stream(), IOException.class)
            .filter(r -> currentHistory.graph().predecessors(r).size() == 2)
            .map(r -> new RootedMarksTree(r.toShaCached(), gradeD(commitB, r))).collect(
                ImmutableBiMap.toImmutableBiMap(RootedMarksTree::root, RootedMarksTree::commented));
    // final ImmutableBiMap subs =
    // ThrowingStream.of(successors.stream(), IOException.class)
    // .filter(r -> currentHistory.graph().predecessors(r).size() == 2)
    // .collect(ImmutableBiMap.toImmutableBiMap(r -> r, r -> {
    // try {
    // return subGrade(r, w -> gradeD(commitB, w));
    // } catch (NoSuchFileException e) {
    // throw new IllegalStateException(e);
    // } catch (IOException e) {
    // throw new IllegalStateException(e);
    // }
    // }));
    final MarksTree tree = compositeOrZero(subs.values(), "No successor of C merging two commits");
    if (expandLocally || tree.isMark()) {
      return SubMarksTree.given(C_D, tree);
    }
    final ImmutableMap weightedSubMarks =
        Grade.given(dAggregatorExpanded(), tree).getWeightedSubMarks();
    final Criterion bestPath = Maps.filterValues(weightedSubMarks, w -> w == 1d).keySet().stream()
        .map(SubMark::getCriterion).collect(MoreCollectors.onlyElement());
    final GitPathRootShaCached bestSubMark =
        Maps.filterValues(subs, s -> s.getCriterion().equals(bestPath)).keySet().stream()
            .collect(MoreCollectors.onlyElement());
    return SubMarksTree.given(C_D, gradeD(commitB, bestSubMark));
  }

  private MarksTree gradeD(GitPathRootShaCached commitC, GitPathRootShaCached commitD)
      throws IOException {
    final long nbFiles =
        Files.find(commitD, Integer.MAX_VALUE, (p, a) -> Files.isRegularFile(p)).count();
    final boolean rightNb = nbFiles == 3;
    final SubMarksTree subNb =
        SubMarksTree.given(C_THREE, Mark.binary(rightNb, "", "Found " + nbFiles + " files"));

    final boolean isBranch = ThrowingStream.of(refsTo(commitD.toSha()).stream(), IOException.class)
        .filter(GitPathRoot::isRef).map(GitPathRoot::getGitRef)
        .anyMatch(r -> Predicates.isBranch(r, "br3"));
    final SubMarksTree subBranch = SubMarksTree.given(C_BR3, Mark.binary(isBranch));

    final GitPath startFilePath = commitD.resolve("starting point.txt");
    final boolean startExists = Files.exists(startFilePath);
    final String startContent = startExists ? Files.readString(startFilePath) : "";
    final Pattern patternStart = Pattern.compile("A starting point\\v*");
    final boolean startMatches = patternStart.matcher(startContent).matches();
    final SubMarksTree subStartExists =
        SubMarksTree.given(C_EXISTS_START, Mark.binary(startExists));
    final SubMarksTree subStartMatches =
        SubMarksTree.given(C_CONTENTS_START, Mark.binary(startMatches));

    final GitPath firstPath = commitD.resolve("first file.txt");
    final boolean firstExists = Files.exists(firstPath);
    final String firstContent = firstExists ? Files.readString(firstPath) : "";
    final Pattern patternFirst = Pattern.compile("Hello\\h?!\\v+Coucou\\h?!\\v*");
    verify(patternFirst.matcher("Hello!\nCoucou !").matches());
    verify(patternFirst.matcher("Hello !\n\nCoucou!\n").matches());
    verify(!patternFirst.matcher("Hello!").matches());
    verify(!patternFirst.matcher("Hello!\nCoucou").matches());
    final boolean firstMatches = patternFirst.matcher(firstContent).matches();
    final SubMarksTree subFirstExists =
        SubMarksTree.given(C_EXISTS_FIRST, Mark.binary(firstExists));
    final SubMarksTree subFirstMatches =
        SubMarksTree.given(C_CONTENTS_FIRST, Mark.binary(firstMatches));

    final GitPath somePath = commitD.resolve("My folder/second file.txt");
    final boolean someExists = Files.exists(somePath);
    final String someContent = someExists ? Files.readString(somePath) : "";
    final Pattern somePattern = Pattern.compile("Second\\v*");
    final boolean someMatches = somePattern.matcher(someContent).matches();
    final SubMarksTree subSomeExists = SubMarksTree.given(C_EXISTS_SOME, Mark.binary(someExists));
    final SubMarksTree subSomeMatches =
        SubMarksTree.given(C_CONTENTS_SOME, Mark.binary(someMatches));

    final Set parents = currentHistory.graph().predecessors(commitD);
    final boolean rightParents = parents.contains(commitC) && parents.size() == 2;
    final SubMarksTree subParents = SubMarksTree.given(C_PARENTS, Mark.binary(rightParents));

    return MarksTree.composite(ImmutableSet.of(subNb, subBranch, subStartExists, subStartMatches,
        subFirstExists, subFirstMatches, subSomeExists, subSomeMatches, subParents));
  }

  private ImmutableSet refsTo(GitPathRootSha target) throws IOException {
    return GradeUtils.getRefsTo(currentHistory.fs().refs(), target);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy