001/** 002 * GRANITE DATA SERVICES 003 * Copyright (C) 2006-2013 GRANITE DATA SERVICES S.A.S. 004 * 005 * This file is part of the Granite Data Services Platform. 006 * 007 * Granite Data Services is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * Granite Data Services is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 015 * General Public License for more details. 016 * 017 * You should have received a copy of the GNU Lesser General Public 018 * License along with this library; if not, write to the Free Software 019 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 020 * USA, or see <http://www.gnu.org/licenses/>. 021 */ 022package org.granite.tide.data; 023 024import java.util.ArrayList; 025import java.util.HashMap; 026import java.util.List; 027import java.util.Map; 028 029public class DataUtils { 030 031 public static List<Object[]> diffLists(List<?> oldList, List<?> newList) { 032 ListDiff listDiff = new ListDiff(oldList, newList); 033 listDiff.diff(); 034 return listDiff.getOps(); 035 } 036 037 private static class ListDiff { 038 039 private final List<?> oldList; 040 private final List<?> newList; 041 042 private int oldi = 0, newi = 0; 043 044 private List<Object[]> ops = new ArrayList<Object[]>(); 045 046 private List<Integer> skipOld = new ArrayList<Integer>(); 047 private Map<Integer, Object[]> delayedNew = new HashMap<Integer, Object[]>(); 048 049 public ListDiff(List<?> oldList, List<?> newList) { 050 this.oldList = oldList; 051 this.newList = newList; 052 } 053 054 public List<Object[]> getOps() { 055 return ops; 056 } 057 058 private void moveNew() { 059 newi++; 060 while (delayedNew.containsKey(newi)) { 061 for (Object op : delayedNew.get(newi)) 062 ops.add((Object[])op); 063 newi++; 064 } 065 } 066 067 private int nextOld() { 068 int i = oldi+1; 069 while (skipOld.contains(i) && i < oldList.size()) 070 i++; 071 return i; 072 } 073 074 private int nextNew() { 075 return newi+1; 076 } 077 078 private int getIndex(int index) { 079 for (Object[] op : ops) { 080 if (op[0].equals(-1) && (Integer)op[1] <= index) 081 index--; 082 else if (op[0].equals(1) && (Integer)op[1] <= index) 083 index++; 084 } 085 return index; 086 } 087 088 public void diff() { 089 for (oldi = 0; oldi < oldList.size(); oldi++) { 090 if (skipOld.contains(oldi)) 091 continue; 092 093 // Same value for current indices on old and new : next and reset current offset 094 if (oldi < oldList.size() && newi < newList.size() && oldList.get(oldi).equals(newList.get(newi))) { 095 moveNew(); 096 continue; 097 } 098 099 // Lookup same element in new list 100 int foundNext = -1; 101 if (newi < newList.size()-1) { 102 for (int i = newi+1; i < newList.size(); i++) { 103 if (newList.get(i).equals(oldList.get(oldi)) && !delayedNew.containsKey(i)) { 104 foundNext = i; 105 break; 106 } 107 } 108 } 109 if (foundNext == -1) { 110 int oi = nextOld(); 111 int ni = nextNew(); 112 if (oi < oldList.size() && ni < newList.size() && oldList.get(oi).equals(newList.get(ni))) { 113 // Element not found in new list but next one matches: update 114 ops.add(new Object[] { 0, getIndex(oldi), newList.get(newi) }); 115 moveNew(); 116 } 117 else { 118 // Element not found in new list: remove 119 ops.add(new Object[] { -1, getIndex(oldi), oldList.get(oldi) }); 120 } 121 } 122// else if (foundNext == newi+1 && oldi+1 < oldList.size() && oldList.get(oldi+1).equals(newList.get(newi))) { 123// // Immediate permutation 124// int index = getIndex(oldi); 125// ops.add(new Object[] { -1, index, oldList.get(oldi) }); 126// ops.add(new Object[] { 1, index+1, oldList.get(oldi) }); 127// nextNew(); 128// nextNew(); 129// oldi++; 130// } 131// else if (foundNext > newi+1 && oldi+foundNext-newi < oldList.size() && oldList.get(oldi+foundNext-newi).equals(newList.get(newi))) { 132// // Distant permutation 133// int index = getIndex(oldi); 134// Object p1 = oldList.get(oldi); 135// Object p2 = newList.get(newi); 136// ops.add(new Object[] { -1, index, p1 }); 137// ops.add(new Object[] { 1, index, p2 }); 138// skipOld.add(oldi+foundNext-newi); 139// nextNew(); 140// delayedNew.put(foundNext, new Object[] { new Object[] { -1, foundNext, p2 }, new Object[] { 1, foundNext, p1 } }); 141// } 142 else if (oldi < oldList.size()-1 && oldList.get(oldi+1).equals(newList.get(newi))) { 143 // Move of element to another position, remove here and schedule an add for final position 144 ops.add(new Object[] { -1, getIndex(oldi), oldList.get(oldi) }); 145 delayedNew.put(foundNext, new Object[] { new Object[] { 1, foundNext, oldList.get(oldi) } }); 146 } 147 else { 148 // Add elements of new list from current index to found index 149 while (newi < foundNext) { 150 int foundOld = -1; 151 // Lookup if the element is present later in the old list 152 if (oldi < oldList.size()-1) { 153 for (int i = oldi+1; i < oldList.size(); i++) { 154 if (newList.get(newi).equals(oldList.get(i)) && !skipOld.contains(i)) { 155 foundOld = i; 156 break; 157 } 158 } 159 } 160 if (foundOld >= 0) { 161 // Found later, push a remove 162 ops.add(new Object[] { -1, getIndex(foundOld), oldList.get(foundOld) }); 163 skipOld.add(foundOld); 164 } 165 166 ops.add(new Object[] { 1, newi, newList.get(newi) }); 167 moveNew(); 168 } 169 oldi--; 170 } 171 } 172 // Add missing elements from new list 173 while (newi < newList.size()) { 174 ops.add(new Object[] { 1, newi, newList.get(newi) }); 175 moveNew(); 176 } 177 } 178 } 179}