PlotCommitList.java

  1. /*
  2.  * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>,
  3.  * Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com>
  4.  * Copyright (C) 2014, Konrad Kügler and others
  5.  *
  6.  * This program and the accompanying materials are made available under the
  7.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  8.  * https://www.eclipse.org/org/documents/edl-v10.php.
  9.  *
  10.  * SPDX-License-Identifier: BSD-3-Clause
  11.  */

  12. package org.eclipse.jgit.revplot;

  13. import java.text.MessageFormat;
  14. import java.util.BitSet;
  15. import java.util.Collection;
  16. import java.util.HashMap;
  17. import java.util.HashSet;
  18. import java.util.TreeSet;

  19. import org.eclipse.jgit.internal.JGitText;
  20. import org.eclipse.jgit.revwalk.RevCommitList;
  21. import org.eclipse.jgit.revwalk.RevWalk;

  22. /**
  23.  * An ordered list of {@link org.eclipse.jgit.revplot.PlotCommit} subclasses.
  24.  * <p>
  25.  * Commits are allocated into lanes as they enter the list, based upon their
  26.  * connections between descendant (child) commits and ancestor (parent) commits.
  27.  * <p>
  28.  * The source of the list must be a {@link org.eclipse.jgit.revplot.PlotWalk}
  29.  * and {@link #fillTo(int)} must be used to populate the list.
  30.  *
  31.  * @param <L>
  32.  *            type of lane used by the application.
  33.  */
  34. public class PlotCommitList<L extends PlotLane> extends
  35.         RevCommitList<PlotCommit<L>> {
  36.     static final int MAX_LENGTH = 25;

  37.     private int positionsAllocated;

  38.     private final TreeSet<Integer> freePositions = new TreeSet<>();

  39.     private final HashSet<PlotLane> activeLanes = new HashSet<>(32);

  40.     /** number of (child) commits on a lane */
  41.     private final HashMap<PlotLane, Integer> laneLength = new HashMap<>(
  42.             32);

  43.     /** {@inheritDoc} */
  44.     @Override
  45.     public void clear() {
  46.         super.clear();
  47.         positionsAllocated = 0;
  48.         freePositions.clear();
  49.         activeLanes.clear();
  50.         laneLength.clear();
  51.     }

  52.     /** {@inheritDoc} */
  53.     @Override
  54.     public void source(RevWalk w) {
  55.         if (!(w instanceof PlotWalk))
  56.             throw new ClassCastException(MessageFormat.format(JGitText.get().classCastNotA, PlotWalk.class.getName()));
  57.         super.source(w);
  58.     }

  59.     /**
  60.      * Find the set of lanes passing through a commit's row.
  61.      * <p>
  62.      * Lanes passing through a commit are lanes that the commit is not directly
  63.      * on, but that need to travel through this commit to connect a descendant
  64.      * (child) commit to an ancestor (parent) commit. Typically these lanes will
  65.      * be drawn as lines in the passed commit's box, and the passed commit won't
  66.      * appear to be connected to those lines.
  67.      * <p>
  68.      * This method modifies the passed collection by adding the lanes in any
  69.      * order.
  70.      *
  71.      * @param currCommit
  72.      *            the commit the caller needs to get the lanes from.
  73.      * @param result
  74.      *            collection to add the passing lanes into.
  75.      */
  76.     @SuppressWarnings("unchecked")
  77.     public void findPassingThrough(final PlotCommit<L> currCommit,
  78.             final Collection<L> result) {
  79.         for (PlotLane p : currCommit.passingLanes)
  80.             result.add((L) p);
  81.     }

  82.     /** {@inheritDoc} */
  83.     @Override
  84.     protected void enter(int index, PlotCommit<L> currCommit) {
  85.         setupChildren(currCommit);

  86.         final int nChildren = currCommit.getChildCount();
  87.         if (nChildren == 0) {
  88.             currCommit.lane = nextFreeLane();
  89.         } else if (nChildren == 1
  90.                 && currCommit.children[0].getParentCount() < 2) {
  91.             // Only one child, child has only us as their parent.
  92.             // Stay in the same lane as the child.

  93.             @SuppressWarnings("unchecked")
  94.             final PlotCommit<L> c = currCommit.children[0];
  95.             currCommit.lane = c.lane;
  96.             Integer len = laneLength.get(currCommit.lane);
  97.             len = len != null ? Integer.valueOf(len.intValue() + 1)
  98.                     : Integer.valueOf(0);
  99.             laneLength.put(currCommit.lane, len);
  100.         } else {
  101.             // More than one child, or our child is a merge.

  102.             // We look for the child lane the current commit should continue.
  103.             // Candidate lanes for this are those with children, that have the
  104.             // current commit as their first parent.
  105.             // There can be multiple candidate lanes. In that case the longest
  106.             // lane is chosen, as this is usually the lane representing the
  107.             // branch the commit actually was made on.

  108.             // When there are no candidate lanes (i.e. the current commit has
  109.             // only children whose non-first parent it is) we place the current
  110.             // commit on a new lane.

  111.             // The lane the current commit will be placed on:
  112.             PlotLane reservedLane = null;
  113.             PlotCommit childOnReservedLane = null;
  114.             int lengthOfReservedLane = -1;

  115.             for (int i = 0; i < nChildren; i++) {
  116.                 @SuppressWarnings("unchecked")
  117.                 final PlotCommit<L> c = currCommit.children[i];
  118.                 if (c.getParent(0) == currCommit) {
  119.                     Integer len = laneLength.get(c.lane);
  120.                     // we may be the first parent for multiple lines of
  121.                     // development, try to continue the longest one
  122.                     if (len.intValue() > lengthOfReservedLane) {
  123.                         reservedLane = c.lane;
  124.                         childOnReservedLane = c;
  125.                         lengthOfReservedLane = len.intValue();
  126.                     }
  127.                 }
  128.             }

  129.             if (reservedLane != null) {
  130.                 currCommit.lane = reservedLane;
  131.                 laneLength.put(reservedLane,
  132.                         Integer.valueOf(lengthOfReservedLane + 1));
  133.                 handleBlockedLanes(index, currCommit, childOnReservedLane);
  134.             } else {
  135.                 currCommit.lane = nextFreeLane();
  136.                 handleBlockedLanes(index, currCommit, null);
  137.             }

  138.             // close lanes of children, if there are no first parents that might
  139.             // want to continue the child lanes
  140.             for (int i = 0; i < nChildren; i++) {
  141.                 final PlotCommit c = currCommit.children[i];
  142.                 PlotCommit firstParent = (PlotCommit) c.getParent(0);
  143.                 if (firstParent.lane != null && firstParent.lane != c.lane)
  144.                     closeLane(c.lane);
  145.             }
  146.         }

  147.         continueActiveLanes(currCommit);
  148.         if (currCommit.getParentCount() == 0)
  149.             closeLane(currCommit.lane);
  150.     }

  151.     private void continueActiveLanes(PlotCommit currCommit) {
  152.         for (PlotLane lane : activeLanes)
  153.             if (lane != currCommit.lane)
  154.                 currCommit.addPassingLane(lane);
  155.     }

  156.     /**
  157.      * Sets up fork and merge information in the involved PlotCommits.
  158.      * Recognizes and handles blockades that involve forking or merging arcs.
  159.      *
  160.      * @param index
  161.      *            the index of <code>currCommit</code> in the list
  162.      * @param currCommit
  163.      * @param childOnLane
  164.      *            the direct child on the same lane as <code>currCommit</code>,
  165.      *            may be null if <code>currCommit</code> is the first commit on
  166.      *            the lane
  167.      */
  168.     private void handleBlockedLanes(final int index, final PlotCommit currCommit,
  169.             final PlotCommit childOnLane) {
  170.         for (PlotCommit child : currCommit.children) {
  171.             if (child == childOnLane)
  172.                 continue; // simple continuations of lanes are handled by
  173.                             // continueActiveLanes() calls in enter()

  174.             // Is the child a merge or is it forking off?
  175.             boolean childIsMerge = child.getParent(0) != currCommit;
  176.             if (childIsMerge) {
  177.                 PlotLane laneToUse = currCommit.lane;
  178.                 laneToUse = handleMerge(index, currCommit, childOnLane, child,
  179.                         laneToUse);
  180.                 child.addMergingLane(laneToUse);
  181.             } else {
  182.                 // We want to draw a forking arc in the child's lane.
  183.                 // As an active lane, the child lane already continues
  184.                 // (unblocked) up to this commit, we only need to mark it as
  185.                 // forking off from the current commit.
  186.                 PlotLane laneToUse = child.lane;
  187.                 currCommit.addForkingOffLane(laneToUse);
  188.             }
  189.         }
  190.     }

  191.     // Handles the case where currCommit is a non-first parent of the child
  192.     private PlotLane handleMerge(final int index, final PlotCommit currCommit,
  193.             final PlotCommit childOnLane, PlotCommit child, PlotLane laneToUse) {

  194.         // find all blocked positions between currCommit and this child

  195.         int childIndex = index; // useless initialization, should
  196.                                 // always be set in the loop below
  197.         BitSet blockedPositions = new BitSet();
  198.         for (int r = index - 1; r >= 0; r--) {
  199.             final PlotCommit rObj = get(r);
  200.             if (rObj == child) {
  201.                 childIndex = r;
  202.                 break;
  203.             }
  204.             addBlockedPosition(blockedPositions, rObj);
  205.         }

  206.         // handle blockades

  207.         if (blockedPositions.get(laneToUse.getPosition())) {
  208.             // We want to draw a merging arc in our lane to the child,
  209.             // which is on another lane, but our lane is blocked.

  210.             // Check if childOnLane is beetween commit and the child we
  211.             // are currently processing
  212.             boolean needDetour = false;
  213.             if (childOnLane != null) {
  214.                 for (int r = index - 1; r > childIndex; r--) {
  215.                     final PlotCommit rObj = get(r);
  216.                     if (rObj == childOnLane) {
  217.                         needDetour = true;
  218.                         break;
  219.                     }
  220.                 }
  221.             }

  222.             if (needDetour) {
  223.                 // It is childOnLane which is blocking us. Repositioning
  224.                 // our lane would not help, because this repositions the
  225.                 // child too, keeping the blockade.
  226.                 // Instead, we create a "detour lane" which gets us
  227.                 // around the blockade. That lane has no commits on it.
  228.                 laneToUse = nextFreeLane(blockedPositions);
  229.                 currCommit.addForkingOffLane(laneToUse);
  230.                 closeLane(laneToUse);
  231.             } else {
  232.                 // The blockade is (only) due to other (already closed)
  233.                 // lanes at the current lane's position. In this case we
  234.                 // reposition the current lane.
  235.                 // We are the first commit on this lane, because
  236.                 // otherwise the child commit on this lane would have
  237.                 // kept other lanes from blocking us. Since we are the
  238.                 // first commit, we can freely reposition.
  239.                 int newPos = getFreePosition(blockedPositions);
  240.                 freePositions.add(Integer.valueOf(laneToUse
  241.                         .getPosition()));
  242.                 laneToUse.position = newPos;
  243.             }
  244.         }

  245.         // Actually connect currCommit to the merge child
  246.         drawLaneToChild(index, child, laneToUse);
  247.         return laneToUse;
  248.     }

  249.     /**
  250.      * Connects the commit at commitIndex to the child, using the given lane.
  251.      * All blockades on the lane must be resolved before calling this method.
  252.      *
  253.      * @param commitIndex
  254.      * @param child
  255.      * @param laneToContinue
  256.      */
  257.     private void drawLaneToChild(final int commitIndex, PlotCommit child,
  258.             PlotLane laneToContinue) {
  259.         for (int r = commitIndex - 1; r >= 0; r--) {
  260.             final PlotCommit rObj = get(r);
  261.             if (rObj == child)
  262.                 break;
  263.             if (rObj != null)
  264.                 rObj.addPassingLane(laneToContinue);
  265.         }
  266.     }

  267.     private static void addBlockedPosition(BitSet blockedPositions,
  268.             final PlotCommit rObj) {
  269.         if (rObj != null) {
  270.             PlotLane lane = rObj.getLane();
  271.             // Positions may be blocked by a commit on a lane.
  272.             if (lane != null)
  273.                 blockedPositions.set(lane.getPosition());
  274.             // Positions may also be blocked by forking off and merging lanes.
  275.             // We don't consider passing lanes, because every passing lane forks
  276.             // off and merges at it ends.
  277.             for (PlotLane l : rObj.forkingOffLanes)
  278.                 blockedPositions.set(l.getPosition());
  279.             for (PlotLane l : rObj.mergingLanes)
  280.                 blockedPositions.set(l.getPosition());
  281.         }
  282.     }

  283.     @SuppressWarnings("unchecked")
  284.     private void closeLane(PlotLane lane) {
  285.         if (activeLanes.remove(lane)) {
  286.             recycleLane((L) lane);
  287.             laneLength.remove(lane);
  288.             freePositions.add(Integer.valueOf(lane.getPosition()));
  289.         }
  290.     }

  291.     private void setupChildren(PlotCommit<L> currCommit) {
  292.         final int nParents = currCommit.getParentCount();
  293.         for (int i = 0; i < nParents; i++)
  294.             ((PlotCommit) currCommit.getParent(i)).addChild(currCommit);
  295.     }

  296.     private PlotLane nextFreeLane() {
  297.         return nextFreeLane(null);
  298.     }

  299.     private PlotLane nextFreeLane(BitSet blockedPositions) {
  300.         final PlotLane p = createLane();
  301.         p.position = getFreePosition(blockedPositions);
  302.         activeLanes.add(p);
  303.         laneLength.put(p, Integer.valueOf(1));
  304.         return p;
  305.     }

  306.     /**
  307.      * @param blockedPositions
  308.      *            may be null
  309.      * @return a free lane position
  310.      */
  311.     private int getFreePosition(BitSet blockedPositions) {
  312.         if (freePositions.isEmpty())
  313.             return positionsAllocated++;

  314.         if (blockedPositions != null) {
  315.             for (Integer pos : freePositions)
  316.                 if (!blockedPositions.get(pos.intValue())) {
  317.                     freePositions.remove(pos);
  318.                     return pos.intValue();
  319.                 }
  320.             return positionsAllocated++;
  321.         }
  322.         final Integer min = freePositions.first();
  323.         freePositions.remove(min);
  324.         return min.intValue();
  325.     }

  326.     /**
  327.      * Create a new {@link PlotLane} appropriate for this particular
  328.      * {@link PlotCommitList}.
  329.      *
  330.      * @return a new {@link PlotLane} appropriate for this particular
  331.      *         {@link PlotCommitList}.
  332.      */
  333.     @SuppressWarnings("unchecked")
  334.     protected L createLane() {
  335.         return (L) new PlotLane();
  336.     }

  337.     /**
  338.      * Return colors and other reusable information to the plotter when a lane
  339.      * is no longer needed.
  340.      *
  341.      * @param lane
  342.      *            a lane
  343.      */
  344.     protected void recycleLane(L lane) {
  345.         // Nothing.
  346.     }
  347. }