/**
* GRANITE DATA SERVICES
* Copyright (C) 2006-2015 GRANITE DATA SERVICES S.A.S.
*
* This file is part of the Granite Data Services Platform.
*
* Granite Data Services 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.
*
* Granite Data Services 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 library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA, or see <http://www.gnu.org/licenses/>.
*/
package org.granite.tide.data;
import java.util.*;
public class Utils {
public static List<Object[]> diffLists(List<?> oldList, List<?> newList) {
ListDiff listDiff = new ListDiff(oldList, newList);
listDiff.diff();
return listDiff.getOps();
}
public static List<Object[]> diffColls(Collection<?> oldColl, Collection<?> newColl) {
List<Object[]> ops = new ArrayList<Object[]>();
for (Object newElt : newColl) {
if (!oldColl.contains(newElt))
ops.add(new Object[] { 1, null, newElt });
}
for (Object oldElt : oldColl) {
if (!newColl.contains(oldElt))
ops.add(new Object[] { -1, null, oldElt });
}
return ops;
}
public static List<Object[]> diffMaps(Map<?, ?> oldMap, Map<?, ?> newMap) {
List<Object[]> ops = new ArrayList<Object[]>();
for (Object newKey : newMap.keySet()) {
if (!oldMap.containsKey(newKey))
ops.add(new Object[] { 1, newKey, newMap.get(newKey) });
else {
Object oldValue = oldMap.get(newKey);
if (oldValue != newMap.get(newKey))
ops.add(new Object[] { 0, newKey, newMap.get(newKey) });
}
}
for (Object oldKey : oldMap.keySet()) {
if (!newMap.containsKey(oldKey))
ops.add(new Object[] { -1, oldKey, oldMap.get(oldKey) });
}
return ops;
}
public static Map<?, ?> convertMapSnapshot(List<Object[]> snapshot) {
Map<Object, Object> map = new HashMap<Object, Object>();
for (Object[] s : snapshot)
map.put(s[0], s[1]);
return map;
}
private static class ListDiff {
private final List<?> oldList;
private final List<?> newList;
private int oldi = 0, newi = 0;
private List<Object[]> ops = new ArrayList<Object[]>();
private List<Integer> skipOld = new ArrayList<Integer>();
private Map<Integer, Object[]> delayedNew = new HashMap<Integer, Object[]>();
public ListDiff(List<?> oldList, List<?> newList) {
this.oldList = oldList;
this.newList = newList;
}
public List<Object[]> getOps() {
return ops;
}
private void moveNew() {
newi++;
while (delayedNew.containsKey(newi)) {
for (Object op : delayedNew.get(newi))
ops.add((Object[])op);
newi++;
}
}
private int nextOld() {
int i = oldi+1;
while (skipOld.contains(i) && i < oldList.size())
i++;
return i;
}
private int nextNew() {
return newi+1;
}
private int getIndex(int index) {
for (Object[] op : ops) {
if (op[0].equals(-1) && (Integer)op[1] <= index)
index--;
else if (op[0].equals(1) && (Integer)op[1] <= index)
index++;
}
return index;
}
public void diff() {
for (oldi = 0; oldi < oldList.size(); oldi++) {
if (skipOld.contains(oldi))
continue;
// Same value for current indices on old and new : next and reset current offset
if (oldi < oldList.size() && newi < newList.size() && oldList.get(oldi).equals(newList.get(newi))) {
moveNew();
continue;
}
// Lookup same element in new list
int foundNext = -1;
if (newi < newList.size()-1) {
for (int i = newi+1; i < newList.size(); i++) {
if (newList.get(i).equals(oldList.get(oldi)) && !delayedNew.containsKey(i)) {
foundNext = i;
break;
}
}
}
if (foundNext == -1) {
int oi = nextOld();
int ni = nextNew();
if (oi < oldList.size() && ni < newList.size() && oldList.get(oi).equals(newList.get(ni))) {
// Element not found in new list but next one matches: update
ops.add(new Object[] { 0, getIndex(oldi), newList.get(newi) });
moveNew();
}
else {
// Element not found in new list: remove
ops.add(new Object[] { -1, getIndex(oldi), oldList.get(oldi) });
}
}
// else if (foundNext == newi+1 && oldi+1 < oldList.size() && oldList.get(oldi+1).equals(newList.get(newi))) {
// // Immediate permutation
// int index = getIndex(oldi);
// ops.add(new Object[] { -1, index, oldList.get(oldi) });
// ops.add(new Object[] { 1, index+1, oldList.get(oldi) });
// nextNew();
// nextNew();
// oldi++;
// }
// else if (foundNext > newi+1 && oldi+foundNext-newi < oldList.size() && oldList.get(oldi+foundNext-newi).equals(newList.get(newi))) {
// // Distant permutation
// int index = getIndex(oldi);
// Object p1 = oldList.get(oldi);
// Object p2 = newList.get(newi);
// ops.add(new Object[] { -1, index, p1 });
// ops.add(new Object[] { 1, index, p2 });
// skipOld.add(oldi+foundNext-newi);
// nextNew();
// delayedNew.put(foundNext, new Object[] { new Object[] { -1, foundNext, p2 }, new Object[] { 1, foundNext, p1 } });
// }
else if (oldi < oldList.size()-1 && oldList.get(oldi+1).equals(newList.get(newi))) {
// Move of element to another position, remove here and schedule an add for final position
ops.add(new Object[] { -1, getIndex(oldi), oldList.get(oldi) });
delayedNew.put(foundNext, new Object[] { new Object[] { 1, foundNext, oldList.get(oldi) } });
}
else {
// Add elements of new list from current index to found index
while (newi < foundNext) {
int foundOld = -1;
// Lookup if the element is present later in the old list
if (oldi < oldList.size()-1) {
for (int i = oldi+1; i < oldList.size(); i++) {
if (newList.get(newi).equals(oldList.get(i)) && !skipOld.contains(i)) {
foundOld = i;
break;
}
}
}
if (foundOld >= 0) {
// Found later, push a remove
ops.add(new Object[] { -1, getIndex(foundOld), oldList.get(foundOld) });
skipOld.add(foundOld);
}
ops.add(new Object[] { 1, newi, newList.get(newi) });
moveNew();
}
oldi--;
}
}
// Add missing elements from new list
while (newi < newList.size()) {
ops.add(new Object[] { 1, newi, newList.get(newi) });
moveNew();
}
}
}
}