com.topologi.diffx.algorithm.ElementState Maven / Gradle / Ivy
Show all versions of docx4j-diffx Show documentation
/*
* This file is part of the DiffX library.
*
* For licensing information please see the file license.txt included in the release.
* A copy of this licence can also be found at
* http://www.opensource.org/licenses/artistic-license-2.0.php
*/
package com.topologi.diffx.algorithm;
import com.topologi.diffx.event.AttributeEvent;
import com.topologi.diffx.event.CloseElementEvent;
import com.topologi.diffx.event.DiffXEvent;
import com.topologi.diffx.event.OpenElementEvent;
/**
* Maintains the state of open and closed elements during the processing the Diff-X
* algorithm.
*
* This class has two purposes, firstly to provide an object that is more specialised
* than the generic lists and stack for use by the DiffX algorithms. Second, to delegate
* some of the complexity of algorithm.
*
*
This class has several methods that are similar to List
interface
* but does not implement it.
*
*
This class is not synchronised and is not meant to be serializable.
*
* @author Christophe Lauret
* @version 12 May 2005
*/
public final class ElementState {
/**
* The stack of open elements.
*/
private transient OpenElementEvent[] openElements;
/**
* The stack open elements changes.
*/
private transient char[] openChanges;
/**
* The size of both lists (the number of elements they contains).
*/
private transient int size;
/**
* Constructs an stack of elements with the specified initial capacity.
*
* @param initialCapacity The initial capacity of the list.
* @throws IllegalArgumentException if the specified initial capacity is negative.
*/
public ElementState(int initialCapacity) throws IllegalArgumentException {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
this.openElements = new OpenElementEvent[initialCapacity];
this.openChanges = new char[initialCapacity];
}
/**
* Constructs an empty stack with an initial capacity of 12.
*/
public ElementState() {
this(12);
}
/**
* Increases the capacity of this class instance, if necessary, to ensure
* that it can hold at least the number of elements specified by the
* minimum capacity argument.
*
* @param minCapacity The desired minimum capacity.
*/
public void ensureCapacity(int minCapacity) {
int oldCapacity = this.openElements.length;
if (minCapacity > oldCapacity) {
// make a copy of the old arrays.
OpenElementEvent[] oldElements = this.openElements;
char[] oldChanges = this.openChanges;
int newCapacity = oldCapacity * 3 / 2 + 1;
if (newCapacity < minCapacity) {
newCapacity = minCapacity;
}
// create new arrays
this.openElements = new OpenElementEvent[newCapacity];
this.openChanges = new char[newCapacity];
// copy the values of the old arrays into the new ones
System.arraycopy(oldElements, 0, this.openElements, 0, this.size);
System.arraycopy(oldChanges, 0, this.openChanges, 0, this.size);
}
}
/**
* Returns the number of elements in this stack.
*
* @return the number of elements in this stack.
*/
public int size() {
return this.size;
}
/**
* Tests if this list has no elements.
*
* @return true
if this list has no elements;
* false
otherwise.
*/
public boolean isEmpty() {
return this.size == 0;
}
/**
* Returns true
if this list contains the specified element.
*
* @param element Element whose presence is to be tested.
*
* @return true
if the specified element is present;
* false
otherwise.
*/
public boolean contains(OpenElementEvent element) {
return indexOf(element) >= 0;
}
/**
* Searches for the first occurrence of the given argument, testing
* for equality using the equals
method.
*
* @param element The open elemnt to find.
*
* @return The index of the first occurrence of the argument in this list;
* returns -1
= 0; i--)
if (this.openElements[i] == null)
return i;
} else {
for (int i = this.size - 1; i >= 0; i--)
if (element.equals(this.openElements[i]))
return i;
}
return -1;
}
// Maintenance methods ------------------------------------------------------------------------
/**
* Returns the current open element.
*
* @return The current open element; or null
if none.
*/
public OpenElementEvent current() {
if (!isEmpty())
return this.openElements[this.size - 1];
else
return null;
}
/**
* Returns the change of the current open element.
*
* @return The change of the current open element; or ' ' if none.
*/
public char currentChange() {
if (!isEmpty())
return this.openChanges[this.size - 1];
else
return ' ';
}
/**
* Indicates whether the specified event is a close element that
* matches the name and URI of the current open element.
*
* @param e The event to check.
*
* @return true
if it matches the current element;
* false
otherwise.
*/
public boolean matchCurrent(DiffXEvent e) {
// cannot match if empty
if (isEmpty()) return false;
// cannot match if not a close element event
if (!(e instanceof CloseElementEvent)) return false;
// check if they match
return ((CloseElementEvent)e).match(current());
}
/**
* Updates the state from the inserted event.
*
* @param e The inserted event.
*/
public void insert(DiffXEvent e) {
if (e instanceof OpenElementEvent) {
push((OpenElementEvent)e, '+');
} else if (e instanceof CloseElementEvent) {
pop();
}
}
/**
* Updates the state from the formatted event.
*
* @param e The formatted event.
*/
public void format(DiffXEvent e) {
if (e instanceof OpenElementEvent) {
push((OpenElementEvent)e, '=');
} else if (e instanceof CloseElementEvent) {
pop();
}
}
/**
* Updates the state from the deleted event.
*
* @param e The deleted event.
*/
public void delete(DiffXEvent e) {
if (e instanceof OpenElementEvent) {
push((OpenElementEvent)e, '-');
} else if (e instanceof CloseElementEvent) {
pop();
}
}
/**
* Indicates whether the specified event is a close element that
* matches the name and URI of the current open element.
*
* @param e The event to check.
*
* @return true
if it matches the current element;
* false
otherwise.
*/
public boolean okFormat(DiffXEvent e) {
// cannot match if not a close element event
if (!(e instanceof CloseElementEvent)) return true;
// cannot match if empty
if (isEmpty()) return false;
// check if they match
return ((CloseElementEvent)e).match(current())
&& this.openChanges[this.size - 1] == '=';
}
/**
* Indicates whether the specified event is a close element that
* matches the name and URI of the current open element.
*
* @param e The event to check.
*
* @return true
if it matches the current element;
* false
otherwise.
*/
public boolean okInsert(DiffXEvent e) {
// cannot match if not a close element event
if (!(e instanceof CloseElementEvent)) return true;
// cannot match if empty
if (isEmpty()) return false;
// check if they match
return ((CloseElementEvent)e).match(current())
&& this.openChanges[this.size - 1] == '+';
}
/**
* Indicates whether the specified event is a close element that
* matches the name and URI of the current open element.
*
* @param e The event to check.
*
* @return true
if it matches the current element;
* false
otherwise.
*/
public boolean okDelete(DiffXEvent e) {
// cannot match if not a close element event
if (!(e instanceof CloseElementEvent)) return true;
// cannot match if empty
if (isEmpty()) return false;
// check if they match
return ((CloseElementEvent)e).match(current())
&& this.openChanges[this.size - 1] == '-';
}
/**
* Indicates whether the first specified event has priority over the second element.
*
* It only seem to be the case when the algorithm has the choice between an attribute and another
* element.
*
* @param e1 The element assumed to have priority.
* @param e2 The other element.
*
* @return true
if first specified event has priority over the second element;
* false
otherwise.
*/
public boolean hasPriorityOver(DiffXEvent e1, DiffXEvent e2) {
if (e1 instanceof AttributeEvent
&& !(e2 instanceof AttributeEvent)
&& !isEmpty())
return true;
return false;
}
// Stack methods ------------------------------------------------------------------------
/**
* Push the specified open element and flags it with the specified change.
*
* @param e The open element to push.
* @param c The character corresponding to change.
*/
private void push(OpenElementEvent e, char c) {
ensureCapacity(this.size + 1);
this.openElements[this.size] = e;
this.openChanges[this.size] = c;
this.size++;
}
/**
* Removes the last element from the top of the stack.
*
* @return The last element from the top of the stack.
*/
public OpenElementEvent pop() {
if (this.size > 0) {
this.size--;
return this.openElements[this.size];
}
return null;
}
// Positional Access Operations ---------------------------------------------------------
/**
* Returns the open element at the specified position in this list.
*
* @param index index of element to return.
*
* @return The element at the specified position in this list.
*
* @throws IndexOutOfBoundsException if index is out of range
* (index < 0 || index >= size())
.
*/
public OpenElementEvent get(int index) throws IndexOutOfBoundsException {
checkRange(index);
return this.openElements[index];
}
/**
* Removes the element at the specified position in this list.
* Shifts any subsequent elements to the left (subtracts one from their
* indices).
*
* @param index The index of the element to removed.
*
* @return The element that was removed from the list.
*
* @throws IndexOutOfBoundsException if index is out of range
* (index < 0 || index >= size())
.
*/
public OpenElementEvent remove(int index) throws IndexOutOfBoundsException {
checkRange(index);
OpenElementEvent oldValue = this.openElements[index];
int numMoved = this.size - index - 1;
if (numMoved > 0) {
System.arraycopy(this.openElements, index+1, this.openElements, index, numMoved);
}
this.openElements[--this.size] = null; // Let gc do its work
return oldValue;
}
/**
* Removes all of the elements from this list. The list will
* be empty after this call returns.
*/
public void clear() {
// Let gc do its work
for (int i = 0; i < this.size; i++) {
this.openElements[i] = null;
}
this.size = 0;
}
/**
* Checks if the given index is in range. If not, throw an appropriate
* runtime exception. This method does *not* check if the index is
* negative: It is always used immediately prior to an array access,
* which throws an ArrayIndexOutOfBoundsException if index is negative.
*
* @param index The index to check.
*
* @throws IndexOutOfBoundsException if index is out of range
* (index < 0 || index >= size())
.
*/
private void checkRange(int index) throws IndexOutOfBoundsException {
if (index >= this.size)
throw new IndexOutOfBoundsException("Index: "+index+", Size: "+this.size);
}
}