/*
* Copyright (C) 2011 eXo Platform SAS.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.exoplatform.portal.tree.diff;
/**
* @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a>
*/
import java.util.Iterator;
import java.util.NoSuchElementException;
/**
* Iterates over a list of {@link ListChangeType} computed from two list of objects. The implementation is optimized to use the
* LCS algorithm only when needed, for trivial list no LCS computation should be required.
*
* @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a>
*/
public class ListChangeIterator<L1, L2, E> implements Iterator<ListChangeType> {
/** . */
private static final int[] EMPTY = new int[0];
/** . */
private static final int TRIVIAL_MODE = 0;
/** . */
private static final int LCS_MODE = 1;
/** . */
ListDiff<L1, L2, E> diff;
/** . */
private final L1 elements1;
/** . */
private final L2 elements2;
/** . */
private final Iterator<E> it1;
/** . */
private final Iterator<E> it2;
/** . */
private int index1;
/** . */
private int index2;
/** . */
private E next1;
/** . */
private E next2;
/** . */
private E element;
/** . */
private ListChangeType type;
/** . */
private int mode;
/** . */
private boolean buffered;
// LCS state
/** . */
private int[] matrix;
/** . */
private int m;
/** . */
private int n;
ListChangeIterator(ListDiff<L1, L2, E> diff, L1 elements1, L2 elements2) {
this.diff = diff;
this.elements1 = elements1;
this.elements2 = elements2;
this.it1 = elements1 != null ? diff.adapter1.iterator(elements1, false) : null;
this.it2 = elements2 != null ? diff.adapter2.iterator(elements2, false) : null;
this.mode = TRIVIAL_MODE;
//
this.index1 = 0;
this.index2 = 0;
this.buffered = false;
this.next1 = null;
this.next2 = null;
this.type = null;
this.element = null;
//
if (it1 != null && it1.hasNext()) {
next1 = it1.next();
}
if (it2 != null && it2.hasNext()) {
next2 = it2.next();
}
//
this.m = 0;
this.n = 0;
this.matrix = EMPTY;
}
private void next1() {
index1++;
if (it1 != null && it1.hasNext()) {
next1 = it1.next();
} else {
next1 = null;
}
}
private void next2() {
index2++;
if (it2 != null && it2.hasNext()) {
next2 = it2.next();
} else {
next2 = null;
}
}
public boolean hasNext() {
while (!buffered) {
if (mode == TRIVIAL_MODE) {
if (next1 != null) {
if (next2 != null) {
if (diff.equals(next1, next2)) {
type = ListChangeType.SAME;
element = next1;
buffered = true;
next1();
next2();
} else {
lcs(index1, elements1, elements2);
mode = LCS_MODE;
}
} else {
type = ListChangeType.REMOVE;
element = next1;
buffered = true;
next1();
}
} else {
if (next2 != null) {
type = ListChangeType.ADD;
element = next2;
buffered = true;
next2();
} else {
// Force a break with buffered to false
break;
}
}
} else if (mode == LCS_MODE) {
E elt1 = null;
E elt2 = null;
int i = diff.adapter1.size(elements1) - index1;
int j = diff.adapter2.size(elements2) - index2;
if (i > 0 && j > 0 && diff.equals(elt1 = next1, elt2 = next2)) {
type = ListChangeType.SAME;
element = elt1;
next1();
next2();
buffered = true;
} else {
int index1 = i + (j - 1) * m;
int index2 = i - 1 + j * m;
if (j > 0 && (i == 0 || matrix[index1] >= matrix[index2])) {
type = ListChangeType.ADD;
element = elt2 == null ? next2 : elt2;
next2();
buffered = true;
} else if (i > 0 && (j == 0 || matrix[index1] < matrix[index2])) {
type = ListChangeType.REMOVE;
element = elt1 == null ? next1 : elt1;
next1();
buffered = true;
} else {
// Force a break with buffered to false
break;
}
}
} else {
throw new AssertionError();
}
}
//
return buffered;
}
public ListChangeType next() {
if (!hasNext()) {
throw new NoSuchElementException();
} else {
buffered = false;
return type;
}
}
public void remove() {
throw new UnsupportedOperationException();
}
public E getElement() {
return element;
}
public int getIndex1() {
return index1;
}
public int getIndex2() {
return index2;
}
/**
* Compute the LCS matrix from the specified offset. It updates the state of this object with the relevant state. The LCS
* matrix is computed using the LCS algorithm (see http://en.wikipedia.org/wiki/Longest_common_subsequence_problem).
*
* @param offset the offset
* @param elements1 the elements 1
* @param elements2 the elements 2
*/
private void lcs(int offset, L1 elements1, L2 elements2) {
m = 1 + diff.adapter1.size(elements1) - offset;
n = 1 + diff.adapter2.size(elements2) - offset;
//
int s = m * n;
matrix = new int[s];
// Compute the lcs matrix
Iterator<E> itI = diff.adapter1.iterator(elements1, true);
for (int i = 1; i < m; i++) {
E abc = itI.next();
Iterator<E> itJ = diff.adapter2.iterator(elements2, true);
for (int j = 1; j < n; j++) {
int index = i + j * m;
int v;
E def = itJ.next();
if (diff.equals(abc, def)) {
v = matrix[index - m - 1] + 1;
} else {
int v1 = matrix[index - 1];
int v2 = matrix[index - m];
v = v1 < v2 ? v2 : v1;
}
matrix[index] = v;
}
}
}
// For unit testing purpose
String getMatrix() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < m; i++) {
sb.append('[');
for (int j = 0; j < n; j++) {
if (j > 0) {
sb.append(',');
}
sb.append(matrix[i + j * m]);
}
sb.append("]\n");
}
return sb.toString();
}
}