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     */
022    package org.granite.tide.data;
023    
024    import java.util.ArrayList;
025    import java.util.HashMap;
026    import java.util.List;
027    import java.util.Map;
028    
029    public 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    }