de.bund.bfr.knime.openkrise.views.tracingview.TracingChange.java Source code

Java tutorial

Introduction

Here is the source code for de.bund.bfr.knime.openkrise.views.tracingview.TracingChange.java

Source

/*******************************************************************************
 * Copyright (c) 2016 German Federal Institute for Risk Assessment (BfR)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Contributors:
 *     Department Biological Safety - BfR
 *******************************************************************************/
package de.bund.bfr.knime.openkrise.views.tracingview;

import java.awt.geom.Point2D;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.GregorianCalendar;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Sets;

import de.bund.bfr.knime.Pair;
import de.bund.bfr.knime.gis.GisType;
import de.bund.bfr.knime.gis.views.canvas.GraphCanvas;
import de.bund.bfr.knime.gis.views.canvas.highlighting.HighlightCondition;
import de.bund.bfr.knime.gis.views.canvas.highlighting.HighlightConditionList;
import de.bund.bfr.knime.gis.views.canvas.util.Transform;
import de.bund.bfr.knime.openkrise.views.canvas.ITracingCanvas;

public class TracingChange implements Serializable {

    private static final long serialVersionUID = 1L;

    public static class Builder implements Serializable {

        private static final long serialVersionUID = 1L;

        private ViewDiff viewDiff;

        private Transform transformDiff;

        private Set<String> nodesWithChangedSelection;
        private Set<String> edgesWithChangedSelection;

        private HighlightingDiff nodeHighlightingDiff;
        private HighlightingDiff edgeHighlightingDiff;

        private Set<Pair<String, Point2D>> changedNodePositions;
        private Set<Pair<String, Set<String>>> changedCollapsedNodes;

        private Set<Pair<String, Double>> changedNodeWeights;
        private Set<Pair<String, Double>> changedEdgeWeights;

        private Set<Pair<String, Boolean>> changedNodeCrossContams;
        private Set<Pair<String, Boolean>> changedEdgeCrossContams;

        private Set<Pair<String, Boolean>> changedNodeKillContams;
        private Set<Pair<String, Boolean>> changedEdgeKillContams;

        private Set<Pair<String, Boolean>> changedObservedNodes;
        private Set<Pair<String, Boolean>> changedObservedEdges;

        private boolean edgeJoinChanged;
        private boolean skipEdgelessChanged;
        private boolean showEdgesInMetaChanged;
        private boolean arrowInMiddleChanged;
        private boolean showLegendChanged;

        private boolean enforceTempChanged;
        private boolean showForwardChanged;
        private boolean showWithoutDateChanged;
        private Pair<GregorianCalendar, GregorianCalendar> showToDateDiff;

        private Pair<Integer, Integer> nodeSizeDiff;
        private Pair<Integer, Integer> nodeMaxSizeDiff;
        private Pair<Integer, Integer> edgeThicknessDiff;
        private Pair<Integer, Integer> edgeMaxThicknessDiff;

        private Pair<Integer, Integer> fontSizeDiff;
        private boolean fontBoldChanged;
        private Pair<String, String> labelDiff;

        private Pair<Integer, Integer> borderAlphaDiff;
        private boolean avoidOverlayChanged;

        public static TracingChange createViewChange(boolean showGisBefore, boolean showGisAfter,
                GisType gisTypeBefore, GisType gisTypeAfter) {
            Builder builder = new Builder();

            builder.viewDiff = new ViewDiff(showGisBefore, showGisAfter, gisTypeBefore, gisTypeAfter);

            return builder.build();
        }

        public Builder() {
            viewDiff = null;
            transformDiff = null;
            nodesWithChangedSelection = new LinkedHashSet<>();
            edgesWithChangedSelection = new LinkedHashSet<>();
            nodeHighlightingDiff = null;
            edgeHighlightingDiff = null;
            changedNodePositions = new LinkedHashSet<>();
            changedCollapsedNodes = new LinkedHashSet<>();
            changedNodeWeights = new LinkedHashSet<>();
            changedEdgeWeights = new LinkedHashSet<>();
            changedNodeCrossContams = new LinkedHashSet<>();
            changedEdgeCrossContams = new LinkedHashSet<>();
            changedNodeKillContams = new LinkedHashSet<>();
            changedEdgeKillContams = new LinkedHashSet<>();
            changedObservedNodes = new LinkedHashSet<>();
            changedObservedEdges = new LinkedHashSet<>();

            edgeJoinChanged = false;
            skipEdgelessChanged = false;
            showEdgesInMetaChanged = false;
            arrowInMiddleChanged = false;
            showLegendChanged = false;
            enforceTempChanged = false;
            showForwardChanged = false;
            showWithoutDateChanged = false;
            showToDateDiff = null;

            nodeSizeDiff = null;
            nodeMaxSizeDiff = null;
            edgeThicknessDiff = null;
            edgeMaxThicknessDiff = null;

            fontSizeDiff = null;
            fontBoldChanged = false;
            labelDiff = null;

            borderAlphaDiff = null;
            avoidOverlayChanged = false;
        }

        public Builder transform(Transform transformBefore, Transform transformAfter) {
            transformDiff = transformAfter.concatenate(transformBefore.inverse());
            return this;
        }

        public Builder selectedNodes(Set<String> selectedNodesBefore, Set<String> selectedNodesAfter) {
            nodesWithChangedSelection = symDiff(selectedNodesBefore, selectedNodesAfter);
            return this;
        }

        public Builder selectedEdges(Set<String> selectedEdgesBefore, Set<String> selectedEdgesAfter) {
            edgesWithChangedSelection = symDiff(selectedEdgesBefore, selectedEdgesAfter);
            return this;
        }

        public Builder nodeHighlighting(HighlightConditionList nodeHighlightingBefore,
                HighlightConditionList nodeHighlightingAfter) {
            nodeHighlightingDiff = new HighlightingDiff(nodeHighlightingBefore, nodeHighlightingAfter);
            return this;
        }

        public Builder edgeHighlighting(HighlightConditionList edgeHighlightingBefore,
                HighlightConditionList edgeHighlightingAfter) {
            edgeHighlightingDiff = new HighlightingDiff(edgeHighlightingBefore, edgeHighlightingAfter);
            return this;
        }

        public Builder nodePositions(Map<String, Point2D> nodePositionsBefore,
                Map<String, Point2D> nodePositionsAfter) {
            changedNodePositions = symDiff(toSet(nodePositionsBefore), toSet(nodePositionsAfter));
            return this;
        }

        public Builder collapsedNodes(Map<String, Set<String>> collapsedNodesBefore,
                Map<String, Set<String>> collapsedNodesAfter) {
            changedCollapsedNodes = symDiff(toSet(collapsedNodesBefore), toSet(collapsedNodesAfter));
            return this;
        }

        public Builder nodeWeights(Map<String, Double> nodeWeightsBefore, Map<String, Double> nodeWeightsAfter) {
            changedNodeWeights = symDiff(toSet(nodeWeightsBefore), toSet(nodeWeightsAfter));
            return this;
        }

        public Builder edgeWeights(Map<String, Double> edgeWeightsBefore, Map<String, Double> edgeWeightsAfter) {
            changedEdgeWeights = symDiff(toSet(edgeWeightsBefore), toSet(edgeWeightsAfter));
            return this;
        }

        public Builder nodeCrossContaminations(Map<String, Boolean> nodeCrossContamsBefore,
                Map<String, Boolean> nodeCrossContamsAfter) {
            changedNodeCrossContams = symDiff(toSet(nodeCrossContamsBefore), toSet(nodeCrossContamsAfter));
            return this;
        }

        public Builder edgeCrossContaminations(Map<String, Boolean> edgeCrossContamsBefore,
                Map<String, Boolean> edgeCrossContamsAfter) {
            changedEdgeCrossContams = symDiff(toSet(edgeCrossContamsBefore), toSet(edgeCrossContamsAfter));
            return this;
        }

        public Builder nodeKillContaminations(Map<String, Boolean> nodeKillContamsBefore,
                Map<String, Boolean> nodeKillContamsAfter) {
            changedNodeKillContams = symDiff(toSet(nodeKillContamsBefore), toSet(nodeKillContamsAfter));
            return this;
        }

        public Builder edgeKillContaminations(Map<String, Boolean> edgeKillContamsBefore,
                Map<String, Boolean> edgeKillContamsAfter) {
            changedEdgeKillContams = symDiff(toSet(edgeKillContamsBefore), toSet(edgeKillContamsAfter));
            return this;
        }

        public Builder observedNodes(Map<String, Boolean> observedNodesBefore,
                Map<String, Boolean> observedNodesAfter) {
            changedObservedNodes = symDiff(toSet(observedNodesBefore), toSet(observedNodesAfter));
            return this;
        }

        public Builder observedEdges(Map<String, Boolean> observedEdgesBefore,
                Map<String, Boolean> observedEdgesAfter) {
            changedObservedEdges = symDiff(toSet(observedEdgesBefore), toSet(observedEdgesAfter));
            return this;
        }

        public Builder joinEdges(boolean joinEdgesBefore, boolean joinEdgesAfter) {
            edgeJoinChanged = joinEdgesBefore != joinEdgesAfter;
            return this;
        }

        public Builder skipEdgelessNodes(boolean skipEdgelessBefore, boolean skipEdgelessAfter) {
            skipEdgelessChanged = skipEdgelessBefore != skipEdgelessAfter;
            return this;
        }

        public Builder showEdgesInMetaNode(boolean showEdgesInMetaBefore, boolean showEdgesInMetaAfter) {
            showEdgesInMetaChanged = showEdgesInMetaBefore != showEdgesInMetaAfter;
            return this;
        }

        public Builder arrowInMiddle(boolean arrowInMiddleBefore, boolean arrowInMiddleAfter) {
            arrowInMiddleChanged = arrowInMiddleBefore != arrowInMiddleAfter;
            return this;
        }

        public Builder showLegend(boolean showLegendBefore, boolean showLegendAfter) {
            showLegendChanged = showLegendBefore != showLegendAfter;
            return this;
        }

        public Builder enforceTemporalOrder(boolean enforceTempBefore, boolean enforceTempAfter) {
            enforceTempChanged = enforceTempBefore != enforceTempAfter;
            return this;
        }

        public Builder showForwardChanged(boolean showForwardBefore, boolean showForwardAfter) {
            showForwardChanged = showForwardBefore != showForwardAfter;
            return this;
        }

        public Builder showWithoutDateChanged(boolean showWithoutDateBefore, boolean showWithoutDateAfter) {
            showWithoutDateChanged = showWithoutDateBefore != showWithoutDateAfter;
            return this;
        }

        public Builder showToDateChanged(GregorianCalendar showToDateBefore, GregorianCalendar showToDateAfter) {
            showToDateDiff = createDiff(showToDateBefore, showToDateAfter);
            return this;
        }

        public Builder nodeSize(int nodeSizeBefore, int nodeSizeAfter, Integer nodeMaxSizeBefore,
                Integer nodeMaxSizeAfter) {
            nodeSizeDiff = createDiff(nodeSizeBefore, nodeSizeAfter);
            nodeMaxSizeDiff = createDiff(nodeMaxSizeBefore, nodeMaxSizeAfter);
            return this;
        }

        public Builder edgeThickness(int edgeThicknessBefore, int edgeThicknessAfter,
                Integer edgeMaxThicknessBefore, Integer edgeMaxThicknessAfter) {
            edgeThicknessDiff = createDiff(edgeThicknessBefore, edgeThicknessAfter);
            edgeMaxThicknessDiff = createDiff(edgeMaxThicknessBefore, edgeMaxThicknessAfter);
            return this;
        }

        public Builder font(int fontSizeBefore, int fontSizeAfter, boolean fontBoldBefore, boolean fontBoldAfter) {
            fontSizeDiff = createDiff(fontSizeBefore, fontSizeAfter);
            fontBoldChanged = fontBoldBefore != fontBoldAfter;
            return this;
        }

        public Builder label(String labelBefore, String labelAfter) {
            labelDiff = createDiff(labelBefore, labelAfter);
            return this;
        }

        public Builder borderAlpha(int borderAlphaBefore, int borderAlphaAfter) {
            borderAlphaDiff = createDiff(borderAlphaBefore, borderAlphaAfter);
            return this;
        }

        public Builder avoidOverlay(boolean avoidOverlayBefore, boolean avoidOverlayAfter) {
            avoidOverlayChanged = avoidOverlayBefore != avoidOverlayAfter;
            return this;
        }

        public TracingChange build() {
            return new TracingChange(this);
        }
    }

    private Builder builder;

    private TracingChange(Builder builder) {
        this.builder = builder;
    }

    public boolean isViewChange() {
        return builder.viewDiff != null;
    }

    public void undo(ITracingCanvas<?> canvas) {
        undoRedo(canvas, true);
    }

    public void redo(ITracingCanvas<?> canvas) {
        undoRedo(canvas, false);
    }

    public void undo(TracingViewSettings set) {
        if (builder.viewDiff != null) {
            builder.viewDiff.undoRedo(set, true);
        }
    }

    public void redo(TracingViewSettings set) {
        if (builder.viewDiff != null) {
            builder.viewDiff.undoRedo(set, false);
        }
    }

    private void undoRedo(ITracingCanvas<?> canvas, boolean undo) {
        if (builder.transformDiff != null && !builder.transformDiff.equals(Transform.IDENTITY_TRANSFORM)) {
            if (undo) {
                canvas.setTransform(builder.transformDiff.inverse().concatenate(canvas.getTransform()));
            } else {
                canvas.setTransform(builder.transformDiff.concatenate(canvas.getTransform()));
            }
        }

        if (builder.edgeJoinChanged) {
            canvas.setJoinEdges(!canvas.isJoinEdges());
        }

        if (builder.skipEdgelessChanged) {
            canvas.setSkipEdgelessNodes(!canvas.isSkipEdgelessNodes());
        }

        if (builder.showEdgesInMetaChanged) {
            canvas.setShowEdgesInMetaNode(!canvas.isShowEdgesInMetaNode());
        }

        if (builder.arrowInMiddleChanged) {
            canvas.setArrowInMiddle(!canvas.isArrowInMiddle());
        }

        if (builder.showLegendChanged) {
            canvas.setShowLegend(!canvas.isShowLegend());
        }

        if (builder.enforceTempChanged) {
            canvas.setEnforceTemporalOrder(!canvas.isEnforceTemporalOrder());
        }

        if (builder.showForwardChanged) {
            canvas.setShowForward(!canvas.isShowForward());
        }

        if (builder.showWithoutDateChanged) {
            canvas.setShowDeliveriesWithoutDate(!canvas.isShowDeliveriesWithoutDate());
        }

        if (builder.showToDateDiff != null) {
            canvas.setShowToDate(undo ? builder.showToDateDiff.getFirst() : builder.showToDateDiff.getSecond());
        }

        if (!builder.changedCollapsedNodes.isEmpty()) {
            canvas.setCollapsedNodes(
                    toMap(symDiff(toSet(canvas.getCollapsedNodes()), builder.changedCollapsedNodes)));
        }

        if (builder.nodeHighlightingDiff != null && !builder.nodeHighlightingDiff.isIdentity()) {
            canvas.setNodeHighlightConditions(
                    builder.nodeHighlightingDiff.undoRedo(canvas.getNodeHighlightConditions(), undo));
        }

        if (builder.edgeHighlightingDiff != null && !builder.edgeHighlightingDiff.isIdentity()) {
            canvas.setEdgeHighlightConditions(
                    builder.edgeHighlightingDiff.undoRedo(canvas.getEdgeHighlightConditions(), undo));
        }

        if (!builder.nodesWithChangedSelection.isEmpty()) {
            canvas.setSelectedNodeIds(symDiff(canvas.getSelectedNodeIds(), builder.nodesWithChangedSelection));
        }

        if (!builder.edgesWithChangedSelection.isEmpty()) {
            canvas.setSelectedEdgeIds(symDiff(canvas.getSelectedEdgeIds(), builder.edgesWithChangedSelection));
        }

        if (canvas instanceof GraphCanvas && !builder.changedNodePositions.isEmpty()) {
            GraphCanvas c = (GraphCanvas) canvas;

            c.setNodePositions(toMap(symDiff(toSet(c.getNodePositions()), builder.changedNodePositions)));
        }

        if (!builder.changedNodeWeights.isEmpty()) {
            canvas.setNodeWeights(toMap(symDiff(toSet(canvas.getNodeWeights()), builder.changedNodeWeights)));
        }

        if (!builder.changedEdgeWeights.isEmpty()) {
            canvas.setEdgeWeights(toMap(symDiff(toSet(canvas.getEdgeWeights()), builder.changedEdgeWeights)));
        }

        if (!builder.changedNodeCrossContams.isEmpty()) {
            canvas.setNodeCrossContaminations(
                    toMap(symDiff(toSet(canvas.getNodeCrossContaminations()), builder.changedNodeCrossContams)));
        }

        if (!builder.changedEdgeCrossContams.isEmpty()) {
            canvas.setEdgeCrossContaminations(
                    toMap(symDiff(toSet(canvas.getEdgeCrossContaminations()), builder.changedEdgeCrossContams)));
        }

        if (!builder.changedNodeKillContams.isEmpty()) {
            canvas.setNodeKillContaminations(
                    toMap(symDiff(toSet(canvas.getNodeKillContaminations()), builder.changedNodeKillContams)));
        }

        if (!builder.changedEdgeKillContams.isEmpty()) {
            canvas.setEdgeKillContaminations(
                    toMap(symDiff(toSet(canvas.getEdgeKillContaminations()), builder.changedEdgeKillContams)));
        }

        if (!builder.changedObservedNodes.isEmpty()) {
            canvas.setObservedNodes(toMap(symDiff(toSet(canvas.getObservedNodes()), builder.changedObservedNodes)));
        }

        if (!builder.changedObservedEdges.isEmpty()) {
            canvas.setObservedEdges(toMap(symDiff(toSet(canvas.getObservedEdges()), builder.changedObservedEdges)));
        }

        if (builder.nodeSizeDiff != null) {
            canvas.setNodeSize(undo ? builder.nodeSizeDiff.getFirst() : builder.nodeSizeDiff.getSecond());
        }

        if (builder.nodeMaxSizeDiff != null) {
            canvas.setNodeMaxSize(undo ? builder.nodeMaxSizeDiff.getFirst() : builder.nodeMaxSizeDiff.getSecond());
        }

        if (builder.edgeThicknessDiff != null) {
            canvas.setEdgeThickness(
                    undo ? builder.edgeThicknessDiff.getFirst() : builder.edgeThicknessDiff.getSecond());
        }

        if (builder.edgeMaxThicknessDiff != null) {
            canvas.setEdgeMaxThickness(
                    undo ? builder.edgeMaxThicknessDiff.getFirst() : builder.edgeMaxThicknessDiff.getSecond());
        }

        if (builder.fontSizeDiff != null) {
            canvas.setFontSize(undo ? builder.fontSizeDiff.getFirst() : builder.fontSizeDiff.getSecond());
        }

        if (builder.fontBoldChanged) {
            canvas.setFontBold(!canvas.isFontBold());
        }

        if (builder.labelDiff != null) {
            canvas.setLabel(undo ? builder.labelDiff.getFirst() : builder.labelDiff.getSecond());
        }

        if (builder.borderAlphaDiff != null) {
            canvas.setBorderAlpha(undo ? builder.borderAlphaDiff.getFirst() : builder.borderAlphaDiff.getSecond());
        }

        if (builder.avoidOverlayChanged) {
            canvas.setAvoidOverlay(!canvas.isAvoidOverlay());
        }
    }

    public boolean isIdentity() {
        return (builder.viewDiff == null || builder.viewDiff.isIdentity())
                && (builder.transformDiff == null || builder.transformDiff.equals(Transform.IDENTITY_TRANSFORM))
                && builder.nodesWithChangedSelection.isEmpty() && builder.edgesWithChangedSelection.isEmpty()
                && (builder.nodeHighlightingDiff == null || builder.nodeHighlightingDiff.isIdentity())
                && (builder.edgeHighlightingDiff == null || builder.edgeHighlightingDiff.isIdentity())
                && builder.changedNodePositions.isEmpty() && builder.changedCollapsedNodes.isEmpty()
                && builder.changedNodeWeights.isEmpty() && builder.changedEdgeWeights.isEmpty()
                && builder.changedNodeCrossContams.isEmpty() && builder.changedEdgeCrossContams.isEmpty()
                && builder.changedNodeKillContams.isEmpty() && builder.changedEdgeKillContams.isEmpty()
                && builder.changedObservedNodes.isEmpty() && builder.changedObservedEdges.isEmpty()
                && !builder.edgeJoinChanged && !builder.skipEdgelessChanged && !builder.showEdgesInMetaChanged
                && !builder.arrowInMiddleChanged && !builder.showLegendChanged && !builder.enforceTempChanged
                && !builder.showForwardChanged && !builder.showWithoutDateChanged && builder.showToDateDiff == null
                && builder.nodeSizeDiff == null && builder.nodeMaxSizeDiff == null
                && builder.edgeThicknessDiff == null && builder.edgeMaxThicknessDiff == null
                && builder.fontSizeDiff == null && !builder.fontBoldChanged && builder.labelDiff == null
                && builder.borderAlphaDiff == null && !builder.avoidOverlayChanged;
    }

    private static <T> Set<T> symDiff(Set<T> before, Set<T> after) {
        return new LinkedHashSet<>(Sets.symmetricDifference(before, after));
    }

    private static <K, V> Set<Pair<K, V>> toSet(Map<K, V> map) {
        Set<Pair<K, V>> set = new LinkedHashSet<>();

        map.forEach((key, value) -> set.add(new Pair<>(key, value)));

        return set;
    }

    private static <K, V> Map<K, V> toMap(Set<Pair<K, V>> set) {
        Map<K, V> map = new LinkedHashMap<>();

        set.forEach(pair -> map.put(pair.getFirst(), pair.getSecond()));

        return map;
    }

    private static <T> Pair<T, T> createDiff(T before, T after) {
        return !Objects.equals(before, after) ? new Pair<>(before, after) : null;
    }

    private static class ViewDiff implements Serializable {

        private static final long serialVersionUID = 1L;

        private boolean showGisChanged;

        private GisType gisTypeBefore;
        private GisType gisTypeAfter;

        public ViewDiff(boolean showGisBefore, boolean showGisAfter, GisType gisTypeBefore, GisType gisTypeAfter) {
            showGisChanged = showGisBefore != showGisAfter;
            this.gisTypeBefore = gisTypeBefore;
            this.gisTypeAfter = gisTypeAfter;
        }

        public void undoRedo(TracingViewSettings set, boolean undo) {
            if (showGisChanged) {
                set.setShowGis(!set.isShowGis());
            }

            set.setGisType(undo ? gisTypeBefore : gisTypeAfter);
        }

        public boolean isIdentity() {
            return !showGisChanged && gisTypeBefore == gisTypeAfter;
        }
    }

    private static class HighlightingDiff implements Serializable {

        private static final long serialVersionUID = 1L;

        private BiMap<Integer, Integer> highlightingOrderChanges;
        private List<HighlightCondition> removedConditions;
        private List<HighlightCondition> addedConditions;
        private boolean prioritizeColorsChanged;

        public HighlightingDiff(HighlightConditionList highlightingBefore,
                HighlightConditionList highlightingAfter) {
            highlightingOrderChanges = HashBiMap.create();
            removedConditions = new ArrayList<>();
            addedConditions = new ArrayList<>();
            prioritizeColorsChanged = highlightingBefore.isPrioritizeColors() != highlightingAfter
                    .isPrioritizeColors();

            List<HighlightCondition> before = highlightingBefore.getConditions();
            List<HighlightCondition> after = highlightingAfter.getConditions();
            Set<HighlightCondition> intersect = Sets.intersection(new LinkedHashSet<>(before),
                    new LinkedHashSet<>(after));

            for (HighlightCondition c : intersect) {
                highlightingOrderChanges.put(before.indexOf(c), after.indexOf(c));
            }

            for (HighlightCondition c : before) {
                if (!intersect.contains(c)) {
                    removedConditions.add(c);
                }
            }

            for (HighlightCondition c : after) {
                if (!intersect.contains(c)) {
                    addedConditions.add(c);
                }
            }
        }

        public HighlightConditionList undoRedo(HighlightConditionList highlighting, boolean undo) {
            int n = highlightingOrderChanges.size() + (undo ? removedConditions.size() : addedConditions.size());
            List<HighlightCondition> oldConditions = highlighting.getConditions();
            List<HighlightCondition> conditions = new ArrayList<>(
                    Collections.nCopies(n, (HighlightCondition) null));

            (undo ? highlightingOrderChanges : highlightingOrderChanges.inverse())
                    .forEach((newIndex, oldIndex) -> conditions.set(newIndex, oldConditions.get(oldIndex)));
            (undo ? removedConditions : addedConditions).forEach(c -> conditions.set(conditions.indexOf(null), c));

            return new HighlightConditionList(conditions,
                    highlighting.isPrioritizeColors() != prioritizeColorsChanged);
        }

        public boolean isIdentity() {
            return removedConditions.isEmpty() && addedConditions.isEmpty() && !prioritizeColorsChanged
                    && highlightingOrderChanges.entrySet().stream().allMatch(e -> e.getKey().equals(e.getValue()));
        }
    }
}