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}