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

ca.odell.glazedlists.event.Block Maven / Gradle / Ivy

There is a newer version: 1.9.1
Show newest version
/* Glazed Lists                                                 (c) 2003-2006 */
/* http://publicobject.com/glazedlists/                      publicobject.com,*/
/*                                                     O'Dell Engineering Ltd.*/
package ca.odell.glazedlists.event;

import java.util.List;

/**
 * Models a change to a list that may require a GUI object
 * to be repainted or another object to be updated.
 *
 * @author Jesse Wilson
 */
final class Block {
    
    /** the range of indices that have changed, inclusive */
    private int startIndex;
    private int endIndex;
    
    /** the type of change, INSERT, UPDATE or DELETE */
    private int type;
    
    /**
     * Create a new single-entry list change of the specified index and type.
     */
    Block(int index, int type) {
        this(index, index, type);
    }
    
    /**
     * Create a new single-entry list change of the specified start index, end
     * index and type.
     */
    Block(int startIndex, int endIndex, int type) {
        setData(startIndex, endIndex, type);
    }

    /**
     * Set this list change block to use the specified data.
     */
    void setData(int index, int type) {
        setData(index, index, type);
    }

    /**
     * Set this list change block to use the specified data.
     */
    void setData(int startIndex, int endIndex, int type) {
        this.startIndex = startIndex;
        this.endIndex = endIndex;
        this.type = type;
        if(startIndex < 0 || endIndex < startIndex) throw new IndexOutOfBoundsException("Illegal range: " + startIndex + ", " + endIndex);
        if(type != ListEvent.INSERT && type != ListEvent.UPDATE && type != ListEvent.DELETE) throw new IllegalArgumentException();
    }
    
    /**
     * Get the first index in the range of this change.
     */
    public int getStartIndex() {
        return startIndex;
    }

    /**
     * Get the last index in the range of this change.
     */
    public int getEndIndex() {
        return endIndex;
    }

    /**
     * Get the type of this change.
     */
    public int getType() {
        return type;
    }
    
    /**
     * Attempts to append the specified event information to the current list
     * change. If the two events are similar enough (same type and contiguous),
     * then the append can proceed. If the events are not similar enough, a new
     * list change will be created.
     *
     * 

The two events must occur sequentially in the change, and the append * must be called with an index that reflects the list after the first * change. * * @return true if the append was successful or false if the user must * use a different list change block for the appended change. */ boolean append(int appendStartIndex, int appendEndIndex, int type) { // bail if the types are different if(type != this.type) return false; // insert events: join if the ends touch if(type == ListEvent.INSERT && (appendStartIndex > endIndex + 1 || appendStartIndex < startIndex)) return false; // delete events: same if deleted from start index or one before start index else if(type == ListEvent.DELETE && (appendEndIndex < startIndex - 1 || appendStartIndex > startIndex)) return false; // update events: same if update is one from beginning or end else if(type == ListEvent.UPDATE && (appendEndIndex < startIndex - 1 || appendStartIndex > endIndex + 1)) return false; // on insert and delete, merge the current change by concatenating the lengths to the earliest start if(type == ListEvent.INSERT || type == ListEvent.DELETE) { int length = (endIndex - startIndex + 1) + (appendEndIndex - appendStartIndex + 1); startIndex = Math.min(appendStartIndex, startIndex); endIndex = startIndex + length - 1; // on update, simply take the lowest start and the biggest stop to eliminate common area } else if(type == ListEvent.UPDATE) { startIndex = Math.min(appendStartIndex, startIndex); endIndex = Math.max(appendEndIndex, endIndex); } if(startIndex < 0 || endIndex < startIndex) throw new IllegalStateException(); return true; } /** * Gets the length of this block. */ public int getLength() { return endIndex - startIndex + 1; } /** * Sorts the blocks of the specified list of changes. This ensures that * the user iterating the list of change blocks can view changes in * increasing order. This ensures that indices will not be shifted. * *

This performs a bubble sort, swapping adjacent change blocks if * they should be swapped. The bubble sort is used instead of a more * efficient sort because it is necessary to adjust offsets when swapping * and therefore it is preferred to only swap adjacent elements. Bubble * sort is a sort that only swaps adjacent elements. * * @param allowContradictingEvents whether a pair of contradicting events shall * be allowed. This would typically be an insert that is wiped away with a * delete, an update that is wiped away with a delete, or an insert that * is later updated. */ static void sortListEventBlocks(List changes, boolean allowContradictingEvents) { // bubblesort the changes while(true) { // count the number of swaps made on this repetition int swapCount = 0; // for each adjacent pair, make any swaps necessary int j = 0; while(j < changes.size() - 1) { Block first = changes.get(j); Block second = changes.get(j+1); if(blocksContradict(first, second)) { if(!allowContradictingEvents) throw new IllegalStateException("Change blocks " + first + " and " + second + " intersect"); simplifyContradiction(changes.subList(j, j+2)); swapCount++; } else if(requiresSplit(first, second)) { Block third = split(first, second); changes.add(j+2, third); } else if(canBeCombined(first, second)) { combine(first, second); changes.remove(j+1); if(j > 0) j--; } else if(requiresSwap(first, second)) { swapCount++; shift(first, second); changes.set(j, second); changes.set(j+1, first); j++; } else { j++; } } // we're done if there were no changes this iteration if(swapCount == 0) break; } } /** * When one block undoes part of another, we have a contradiction. * Types of contradictions: *

    *
  • an insert is later updated *
  • an insert is later deleted *
  • an update is later deleted *
  • an update is later updated *
* * @return true if the blocks contradict. */ private static boolean blocksContradict(Block first, Block second) { // if the ranges intersect boolean rangesIntersect = (first.endIndex >= second.startIndex && first.startIndex <= second.endIndex); if(!rangesIntersect) return false; // (insert or update) is later (deleted or updated) if(first.type != ListEvent.DELETE && second.type != ListEvent.INSERT) { return true; } // can't be a contradiction return false; } /** * Removes the contradiction contained within the specified list of two blocks. */ private static void simplifyContradiction(List contradictingPair) { if(contradictingPair.size() != 2) throw new IllegalStateException(); Block first = contradictingPair.get(0); Block second = contradictingPair.get(1); // get the overlap range int commonStart = Math.max(first.startIndex, second.startIndex); int commonEnd = Math.min(first.endIndex, second.endIndex); int commonLength = (commonEnd - commonStart + 1); // insert then delete kill each other if(first.type == ListEvent.INSERT && second.type == ListEvent.DELETE) { first.endIndex -= commonLength; second.endIndex -= commonLength; if(second.getLength() == 0) contradictingPair.remove(1); if(first.getLength() == 0) contradictingPair.remove(0); // insert then update shortens update and reorders the two changes } else if(first.type == ListEvent.INSERT && second.type == ListEvent.UPDATE) { // remove the update and shorten it contradictingPair.remove(1); second.endIndex -= commonLength; int secondLength = second.getLength(); if(secondLength == 0) return; // shift the update and make it first, chronologically second.startIndex = Math.min(first.startIndex, second.startIndex); second.endIndex = second.startIndex + secondLength - 1; contradictingPair.add(0, second); // update then delete shortens update and reorders the two changes } else if(first.type == ListEvent.UPDATE && second.type == ListEvent.DELETE) { // remove the update and shorten it contradictingPair.remove(0); first.endIndex -= commonLength; int firstLength = first.getLength(); if(firstLength == 0) return; // shift the update and make it second, chronologically first.startIndex = Math.min(first.startIndex, second.startIndex); first.endIndex = first.startIndex + firstLength - 1; contradictingPair.add(1, first); // update then update is the span of both } else if(first.type == ListEvent.UPDATE && second.type == ListEvent.UPDATE) { first.startIndex = Math.min(first.startIndex, second.startIndex); first.endIndex = Math.max(first.endIndex, second.endIndex); contradictingPair.remove(1); } } /** * When there is a sequence of list changes, sometimes these changes are not * created in increasing order. Users usually need to receive changes in * increasing order, so it becomes necessary to reorder list change blocks. * * @return true if two adjacent blocks are out of order. */ private static boolean requiresSwap(Block first, Block second) { // verify no intersection /*if(first.type == ListEvent.INSERT && second.type != ListEvent.INSERT) { if(first.endIndex >= second.startIndex && first.startIndex <= second.endIndex) { throw new IllegalStateException("Change blocks " + first + " and " + second + " intersect"); } }*/ // test if these two require an swap if(second.type == ListEvent.INSERT && first.type != ListEvent.DELETE) { return second.startIndex <= first.startIndex; } else { return second.startIndex < first.startIndex; } } /** * Tests if the specified pair of list change blocks can be combined into * a single larget list change block. */ private static boolean canBeCombined(Block first, Block second) { if(first.type != second.type) return false; if(first.type == ListEvent.INSERT) { return (second.startIndex >= first.startIndex && second.startIndex <= first.endIndex + 1); } else if(first.type == ListEvent.DELETE) { return (second.startIndex <= first.startIndex && second.endIndex >= first.startIndex - 1); } else if(first.type == ListEvent.UPDATE) { return (second.startIndex <= first.endIndex && second.endIndex >= first.startIndex); } else { throw new IllegalStateException(); } } /** * Combines the specified pair of list change blocks. After the change blocks * have been combined, the first block contains all data from the first and second * blocks. */ private static void combine(Block first, Block second) { if(first.type == ListEvent.INSERT || first.type == ListEvent.DELETE) { int startIndex = Math.min(first.startIndex, second.startIndex); int length = first.getLength() + second.getLength(); first.startIndex = startIndex; first.endIndex = startIndex + length - 1; } else if(first.type == ListEvent.UPDATE) { int startIndex = Math.min(first.startIndex, second.startIndex); int endIndex = Math.max(first.endIndex, second.endIndex); first.startIndex = startIndex; first.endIndex = endIndex; } } /** * When reordering blocks, the indices within each block should be shifted * so that they represent the same change. This shifts this ListEventBlock * as a consequence of the specified list change block being inserted in front * of it in a sequence. */ private static void shift(Block alpha, Block beta) { int movedLength = beta.getLength(); if(beta.type == ListEvent.INSERT) { alpha.startIndex += movedLength; alpha.endIndex += movedLength; } else if(beta.type == ListEvent.UPDATE) { // no shift } else if(beta.type == ListEvent.DELETE) { alpha.startIndex -= movedLength; alpha.endIndex -= movedLength; if(alpha.startIndex < 0) throw new IllegalStateException(); } } /** * Tests if the second block must be split in two before the blocks can * be sorted. This is only the case when the second block is an UPDATE * event that spans a DELETE OR INSERT event of the first block. */ private static boolean requiresSplit(Block first, Block second) { // verify we have one update, and one non-update if(first.type != ListEvent.UPDATE && second.type != ListEvent.UPDATE) return false; if(first.type == second.type) return false; // find which block is update, which one is INSERT/DELETE boolean updateIsFirst = (first.type == ListEvent.UPDATE); final Block updateBlock; final Block otherBlock; if(updateIsFirst) { updateBlock = first; otherBlock = second; } else { updateBlock = second; otherBlock = first; } // verify we have a span for a split if(updateBlock.startIndex >= otherBlock.startIndex) return false; if(updateBlock.endIndex < otherBlock.startIndex) return false; return true; } /** * Breaks an update block into two smaller update blocks. One part is stored * in the update block parameter and the other part is returned. */ private static Block split(Block first, Block second) { // find which block is update, which one is INSERT/DELETE boolean updateIsFirst = (first.type == ListEvent.UPDATE); final Block updateBlock; final Block otherBlock; if(updateIsFirst) { updateBlock = first; otherBlock = second; } else { updateBlock = second; otherBlock = first; } // find the split location & offset index int splitLocation = otherBlock.startIndex; int part2Offset = -1; if(otherBlock.type == ListEvent.DELETE) { part2Offset = 0; } else if(otherBlock.type == ListEvent.INSERT) { part2Offset = otherBlock.getLength(); } // apply the changes Block updateBlockPart2 = new Block(splitLocation + part2Offset, updateBlock.endIndex + part2Offset, ListEvent.UPDATE); updateBlock.endIndex = splitLocation - 1; // return the new part return updateBlockPart2; } /** * Gets this ListEventBlock represented as a String */ public String toString() { String result = ""; if(type == ListEvent.DELETE) result = result + "D."; else if(type == ListEvent.UPDATE) result = result + "U."; else if(type == ListEvent.INSERT) result = result + "I."; if(startIndex == endIndex) result = result + startIndex; else result = result + startIndex + "-" + endIndex; return result; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy