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

io.github.oliviercailloux.javagrade.graders.Branching 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.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.exceptions.CheckedStream;
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 Branching implements GitFsGrader {
  @SuppressWarnings("unused")
  private static final Logger LOGGER = LoggerFactory.getLogger(Branching.class);

  public static final String PREFIX = "branching";

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

  public static final double USER_WEIGHT = 0.05d;

  private static final boolean EXPAND = true;

  public static void main(String[] args) throws Exception {
    final BatchGitHistoryGrader grader =
        BatchGitHistoryGrader.given(() -> GitFileSystemWithHistoryFetcherByPrefix
            .getRetrievingByPrefixAndUsingCommitDates(PREFIX));
    grader.getAndWriteGrades(DEADLINE, Duration.ofMinutes(5), new Branching(), 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;

  Branching() {
    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 SubMarksTree subGrade(GitPathRootShaCached gitPathRoot,
      TFunction grader)
      throws IOException, NoSuchFileException {
    final String comment = "Using " + gitPathRoot.getCommit().id().getName();
    final MarksTree grade = grader.apply(gitPathRoot);
    final MarksTree newTree = addComment(grade, comment);

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

  private 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);
  }

  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.5d);
    builder.put(C_BR1, 1.5d);
    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);
    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.5d);
    builder.put(C_BR2, 1.5d);
    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);
    return builder;
  }

  private GradeAggregator dAggregatorFlat() {
    final GradeAggregator dAg;
    {
      final ImmutableMap.Builder builder = ImmutableMap.builder();
      builder.put(C_THREE, 0.5d);
      builder.put(C_BR3, 0.5d);
      builder.put(C_EXISTS_START, 0.15d);
      builder.put(C_CONTENTS_START, 0.15d);
      builder.put(C_EXISTS_FIRST, 0.4d);
      builder.put(C_CONTENTS_FIRST, 1d);
      builder.put(C_EXISTS_SOME, 0.15d);
      builder.put(C_CONTENTS_SOME, 0.15d);
      builder.put(C_PARENTS, 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, 18d);
      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, 16d);
      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, 12d);
      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, 8d);
      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, 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, 4d);
      builder.put(C_B, 4d);
      builder.put(C_C, 4d);
      builder.put(C_D, 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 subGrade(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));
  }

  /**
   * commit containing 1 file, start.txt, containing “A starting point”.
   */
  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("start.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 ImmutableSet getRefsTo(GitPathRootSha target) throws IOException {
    return CheckedStream.from(currentHistory.fs().refs())
        .filter(r -> r.getCommit().id().equals(target.getStaticCommitId()))
        .collect(ImmutableSet.toImmutableSet());
  }

  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 subGrade(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));
  }

  /**
   * Two files. start.txt, “A starting point” and first.txt, “Hello world”.
   */
  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(getRefsTo(commitA).stream(), IOException.class)
        .map(GitPathRoot::getGitRef).anyMatch(r -> Predicates.isBranch(r, "br1"));
    final SubMarksTree subBranch = SubMarksTree.given(C_BR1, Mark.binary(isBranch));

    final GitPath startFilePath = commitA.resolve("start.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.txt");
    final boolean firstExists = Files.exists(firstPath);
    final String firstContent = firstExists ? Files.readString(firstPath) : "";
    final Pattern patternFirst = Pattern.compile("Hello world\\v*");
    verify(patternFirst.matcher("Hello world").matches());
    verify(patternFirst.matcher("Hello world\n").matches());
    verify(!patternFirst.matcher("hello world").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 subGrade(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));
  }

  /**
   * Follows a commit that has a single parent. Has three files. start.txt, first.txt and a/some
   * file.txt.
   */
  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("start.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.txt");
    final boolean firstExists = Files.exists(firstPath);
    final String firstContent = firstExists ? Files.readString(firstPath) : "";
    final Pattern patternFirst = Pattern.compile("Hello world\\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("a/some file.txt");
    final boolean someExists = Files.exists(somePath);
    final String someContent = someExists ? Files.readString(somePath) : "";
    final Pattern somePattern = Pattern.compile("Hey\\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 subGrade(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(getRefsTo(commitC).stream(), IOException.class)
        .map(GitPathRoot::getGitRef).anyMatch(r -> Predicates.isBranch(r, "br2"));
    final SubMarksTree subBranch = SubMarksTree.given(C_BR2, Mark.binary(isBranch));

    final GitPath startFilePath = commitC.resolve("start.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.txt");
    final boolean firstExists = Files.exists(firstPath);
    final String firstContent = firstExists ? Files.readString(firstPath) : "";
    final Pattern pattern = Pattern.compile("Coucou monde\\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)
            .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(getRefsTo(commitD).stream(), IOException.class)
        .map(GitPathRoot::getGitRef).anyMatch(r -> Predicates.isBranch(r, "br3"));
    final SubMarksTree subBranch = SubMarksTree.given(C_BR3, Mark.binary(isBranch));

    final GitPath startFilePath = commitD.resolve("start.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.txt");
    final boolean firstExists = Files.exists(firstPath);
    final String firstContent = firstExists ? Files.readString(firstPath) : "";
    final Pattern patternFirst = Pattern.compile("Hello world\\v+Coucou monde\\v*");
    verify(patternFirst.matcher("Hello world\nCoucou monde").matches());
    verify(patternFirst.matcher("Hello world\n\nCoucou monde\n").matches());
    verify(!patternFirst.matcher("Hello world").matches());
    verify(!patternFirst.matcher("Hello world\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("a/some file.txt");
    final boolean someExists = Files.exists(somePath);
    final String someContent = someExists ? Files.readString(somePath) : "";
    final Pattern somePattern = Pattern.compile("Hey\\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));
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy