/* * Copyright 2000-2014 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.vcs.log.graph.impl.print; import com.intellij.openapi.diagnostic.Logger; import com.intellij.util.NullableFunction; import com.intellij.util.SmartList; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.SLRUMap; import com.intellij.vcs.log.graph.api.EdgeFilter; import com.intellij.vcs.log.graph.api.LinearGraph; import com.intellij.vcs.log.graph.api.elements.GraphEdge; import com.intellij.vcs.log.graph.api.elements.GraphEdgeType; import com.intellij.vcs.log.graph.api.elements.GraphElement; import com.intellij.vcs.log.graph.api.elements.GraphNode; import com.intellij.vcs.log.graph.api.printer.PrintElementManager; import com.intellij.vcs.log.graph.utils.NormalEdge; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; import java.util.*; import static com.intellij.vcs.log.graph.utils.LinearGraphUtils.*; public class PrintElementGeneratorImpl extends AbstractPrintElementGenerator { @NotNull private static final Logger LOG = Logger.getInstance(PrintElementGeneratorImpl.class); public static final int LONG_EDGE_SIZE = 30; private static final int LONG_EDGE_PART_SIZE = 1; private static final int VERY_LONG_EDGE_SIZE = 1000; private static final int VERY_LONG_EDGE_PART_SIZE = 250; private static final int CACHE_SIZE = 100; private static final boolean SHOW_ARROW_WHEN_SHOW_LONG_EDGES = true; private static final int SAMPLE_SIZE = 20000; private static final double K = 0.1; @NotNull private final SLRUMap<Integer, List<GraphElement>> myCache = new SLRUMap<>(CACHE_SIZE, CACHE_SIZE * 2); @NotNull private final EdgesInRowGenerator myEdgesInRowGenerator; @NotNull private final Comparator<GraphElement> myGraphElementComparator; private final int myLongEdgeSize; private final int myVisiblePartSize; private final int myEdgeWithArrowSize; private int myRecommendedWidth = 0; public PrintElementGeneratorImpl(@NotNull LinearGraph graph, @NotNull PrintElementManager printElementManager, boolean showLongEdges) { super(graph, printElementManager); myEdgesInRowGenerator = new EdgesInRowGenerator(graph); myGraphElementComparator = printElementManager.getGraphElementComparator(); if (showLongEdges) { myLongEdgeSize = VERY_LONG_EDGE_SIZE; myVisiblePartSize = VERY_LONG_EDGE_PART_SIZE; if (SHOW_ARROW_WHEN_SHOW_LONG_EDGES) { myEdgeWithArrowSize = LONG_EDGE_SIZE; } else { myEdgeWithArrowSize = Integer.MAX_VALUE; } } else { myLongEdgeSize = LONG_EDGE_SIZE; myVisiblePartSize = LONG_EDGE_PART_SIZE; myEdgeWithArrowSize = Integer.MAX_VALUE; } } @TestOnly public PrintElementGeneratorImpl(@NotNull LinearGraph graph, @NotNull PrintElementManager printElementManager, int longEdgeSize, int visiblePartSize, int edgeWithArrowSize) { super(graph, printElementManager); myEdgesInRowGenerator = new EdgesInRowGenerator(graph); myGraphElementComparator = printElementManager.getGraphElementComparator(); myLongEdgeSize = longEdgeSize; myVisiblePartSize = visiblePartSize; myEdgeWithArrowSize = edgeWithArrowSize; } public int getRecommendedWidth() { if (myRecommendedWidth <= 0) { int n = Math.min(SAMPLE_SIZE, myLinearGraph.nodesCount()); double sum = 0; double sumSquares = 0; int edgesCount = 0; Set<NormalEdge> currentNormalEdges = ContainerUtil.newHashSet(); for (int i = 0; i < n; i++) { List<GraphEdge> adjacentEdges = myLinearGraph.getAdjacentEdges(i, EdgeFilter.ALL); int upArrows = 0; int downArrows = 0; for (GraphEdge e : adjacentEdges) { NormalEdge normalEdge = asNormalEdge(e); if (normalEdge != null) { if (isEdgeUp(e, i)) { currentNormalEdges.remove(normalEdge); } else { currentNormalEdges.add(normalEdge); } } else { if (e.getType() == GraphEdgeType.DOTTED_ARROW_UP) { upArrows++; } else { downArrows++; } } } int newEdgesCount = 0; for (NormalEdge e : currentNormalEdges) { if (isEdgeVisibleInRow(e, i)) { newEdgesCount++; } else { RowElementType arrow = getArrowType(e, i); if (arrow == RowElementType.DOWN_ARROW) { downArrows++; } else if (arrow == RowElementType.UP_ARROW) { upArrows++; } } } int width = Math.max(edgesCount + upArrows, newEdgesCount + downArrows); /* * 0 <= K < 1; weight is an arithmetic progression, starting at 2 / ( n * (k + 1)) ending at k * 2 / ( n * (k + 1)) * this formula ensures that sum of all weights is 1 */ double weight = 2 / (n * (K + 1)) * (1 + (K - 1) * i / (n - 1)); sum += width * weight; sumSquares += width * width * weight; edgesCount = newEdgesCount; } /* weighted variance calculation described here: http://stackoverflow.com/questions/30383270/how-do-i-calculate-the-standard-deviation-between-weighted-measurements */ double average = sum; double deviation = Math.sqrt(sumSquares - average * average); myRecommendedWidth = (int)Math.round(average + deviation); } return myRecommendedWidth; } @NotNull @Override protected List<ShortEdge> getDownShortEdges(int rowIndex) { NullableFunction<GraphEdge, Integer> endPosition = createEndPositionFunction(rowIndex); List<ShortEdge> result = new ArrayList<>(); List<GraphElement> visibleElements = getSortedVisibleElementsInRow(rowIndex); for (int startPosition = 0; startPosition < visibleElements.size(); startPosition++) { GraphElement element = visibleElements.get(startPosition); if (element instanceof GraphNode) { int nodeIndex = ((GraphNode)element).getNodeIndex(); for (GraphEdge edge : myLinearGraph.getAdjacentEdges(nodeIndex, EdgeFilter.ALL)) { if (isEdgeDown(edge, nodeIndex)) { Integer endPos = endPosition.fun(edge); if (endPos != null) result.add(new ShortEdge(edge, startPosition, endPos)); } } } if (element instanceof GraphEdge) { GraphEdge edge = (GraphEdge)element; Integer endPos = endPosition.fun(edge); if (endPos != null) result.add(new ShortEdge(edge, startPosition, endPos)); } } return result; } @NotNull private NullableFunction<GraphEdge, Integer> createEndPositionFunction(int visibleRowIndex) { List<GraphElement> visibleElementsInNextRow = getSortedVisibleElementsInRow(visibleRowIndex + 1); final Map<GraphElement, Integer> toPosition = new HashMap<>(); for (int position = 0; position < visibleElementsInNextRow.size(); position++) { toPosition.put(visibleElementsInNextRow.get(position), position); } return edge -> { Integer position = toPosition.get(edge); if (position == null) { Integer downNodeIndex = edge.getDownNodeIndex(); if (downNodeIndex != null) position = toPosition.get(myLinearGraph.getGraphNode(downNodeIndex)); } return position; }; } @NotNull @Override protected List<SimpleRowElement> getSimpleRowElements(int visibleRowIndex) { List<SimpleRowElement> result = new SmartList<>(); List<GraphElement> sortedVisibleElementsInRow = getSortedVisibleElementsInRow(visibleRowIndex); for (int position = 0; position < sortedVisibleElementsInRow.size(); position++) { GraphElement element = sortedVisibleElementsInRow.get(position); if (element instanceof GraphNode) { result.add(new SimpleRowElement(element, RowElementType.NODE, position)); } if (element instanceof GraphEdge) { GraphEdge edge = (GraphEdge)element; RowElementType arrowType = getArrowType(edge, visibleRowIndex); if (arrowType != null) { result.add(new SimpleRowElement(edge, arrowType, position)); } } } return result; } @Nullable private RowElementType getArrowType(@NotNull GraphEdge edge, int rowIndex) { NormalEdge normalEdge = asNormalEdge(edge); if (normalEdge != null) { return getArrowType(normalEdge, rowIndex); } else { // special edges switch (edge.getType()) { case DOTTED_ARROW_DOWN: case NOT_LOAD_COMMIT: if (intEqual(edge.getUpNodeIndex(), rowIndex - 1)) { return RowElementType.DOWN_ARROW; } break; case DOTTED_ARROW_UP: // todo case 0-row arrow if (intEqual(edge.getDownNodeIndex(), rowIndex + 1)) { return RowElementType.UP_ARROW; } break; default: LOG.error("Unknown special edge type " + edge.getType() + " at row " + rowIndex); } } return null; } @Nullable private RowElementType getArrowType(@NotNull NormalEdge normalEdge, int rowIndex) { int edgeSize = normalEdge.down - normalEdge.up; int upOffset = rowIndex - normalEdge.up; int downOffset = normalEdge.down - rowIndex; if (edgeSize >= myLongEdgeSize) { if (upOffset == myVisiblePartSize) { LOG.assertTrue(downOffset != myVisiblePartSize, "Both up and down arrow at row " + rowIndex); // this can not happen due to how constants are picked out, but just in case return RowElementType.DOWN_ARROW; } if (downOffset == myVisiblePartSize) return RowElementType.UP_ARROW; } if (edgeSize >= myEdgeWithArrowSize) { if (upOffset == 1) { LOG.assertTrue(downOffset != 1, "Both up and down arrow at row " + rowIndex); return RowElementType.DOWN_ARROW; } if (downOffset == 1) return RowElementType.UP_ARROW; } return null; } private boolean isEdgeVisibleInRow(@NotNull GraphEdge edge, int visibleRowIndex) { NormalEdge normalEdge = asNormalEdge(edge); if (normalEdge == null) { // e.d. edge is special. See addSpecialEdges return false; } return isEdgeVisibleInRow(normalEdge, visibleRowIndex); } private boolean isEdgeVisibleInRow(@NotNull NormalEdge normalEdge, int visibleRowIndex) { return normalEdge.down - normalEdge.up < myLongEdgeSize || getAttachmentDistance(normalEdge, visibleRowIndex) <= myVisiblePartSize; } private void addSpecialEdges(@NotNull List<GraphElement> result, int rowIndex) { if (rowIndex > 0) { for (GraphEdge edge : myLinearGraph.getAdjacentEdges(rowIndex - 1, EdgeFilter.SPECIAL)) { assert !edge.getType().isNormalEdge(); if (isEdgeDown(edge, rowIndex - 1)) result.add(edge); } } if (rowIndex < myLinearGraph.nodesCount() - 1) { for (GraphEdge edge : myLinearGraph.getAdjacentEdges(rowIndex + 1, EdgeFilter.SPECIAL)) { assert !edge.getType().isNormalEdge(); if (isEdgeUp(edge, rowIndex + 1)) result.add(edge); } } } @NotNull private List<GraphElement> getSortedVisibleElementsInRow(int rowIndex) { List<GraphElement> graphElements = myCache.get(rowIndex); if (graphElements != null) { return graphElements; } List<GraphElement> result = new ArrayList<>(); result.add(myLinearGraph.getGraphNode(rowIndex)); for (GraphEdge edge : myEdgesInRowGenerator.getEdgesInRow(rowIndex)) { if (isEdgeVisibleInRow(edge, rowIndex)) result.add(edge); } addSpecialEdges(result, rowIndex); Collections.sort(result, myGraphElementComparator); myCache.put(rowIndex, result); return result; } private static int getAttachmentDistance(@NotNull NormalEdge e1, int rowIndex) { return Math.min(rowIndex - e1.up, e1.down - rowIndex); } }