001/*
002 * Copyright (c) 2014 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the Eclipse Public License v1.0
006 * which accompanies this distribution, and is available at
007 * http://www.eclipse.org/legal/epl-v10.html
008 *
009 * Contributors:
010 *     Florent Guillaume
011 */
012package org.nuxeo.ecm.core.storage;
013
014import static org.nuxeo.ecm.core.storage.State.NOP;
015
016import java.io.Serializable;
017import java.util.ArrayList;
018import java.util.Arrays;
019import java.util.Calendar;
020import java.util.Iterator;
021import java.util.List;
022import java.util.Map.Entry;
023import java.util.concurrent.CopyOnWriteArrayList;
024
025import org.nuxeo.ecm.core.api.model.Delta;
026import org.nuxeo.ecm.core.storage.State.ListDiff;
027import org.nuxeo.ecm.core.storage.State.StateDiff;
028
029/**
030 * Helpers for deep copy and deep diff of {@link State} objects.
031 */
032public class StateHelper {
033
034    /** Utility class. */
035    private StateHelper() {
036    }
037
038    /**
039     * Checks if we have a base type compatible with {@link State} helper processing.
040     */
041    public static boolean isScalar(Object value) {
042        return value instanceof String //
043                || value instanceof Boolean //
044                || value instanceof Long //
045                || value instanceof Double //
046                || value instanceof Calendar //
047                || value instanceof Delta;
048    }
049
050    /**
051     * Compares two values.
052     */
053    public static boolean equalsStrict(Object a, Object b) {
054        if (a == b) {
055            return true;
056        } else if (a == null || b == null) {
057            return false;
058        } else if (a instanceof State && b instanceof State) {
059            return equalsStrict((State) a, (State) b);
060        } else if (a instanceof List && b instanceof List) {
061            @SuppressWarnings("unchecked")
062            List<Serializable> la = (List<Serializable>) a;
063            @SuppressWarnings("unchecked")
064            List<Serializable> lb = (List<Serializable>) b;
065            return equalsStrict(la, lb);
066        } else if (a instanceof Object[] && b instanceof Object[]) {
067            return equalsStrict((Object[]) a, (Object[]) b);
068        } else if (a instanceof ListDiff && b instanceof ListDiff) {
069            ListDiff lda = (ListDiff) a;
070            ListDiff ldb = (ListDiff) b;
071            return lda.isArray == ldb.isArray && equalsStrict(lda.diff, ldb.diff) && equalsStrict(lda.rpush, ldb.rpush);
072        } else if (isScalar(a) && isScalar(b)) {
073            return a.equals(b);
074        } else {
075            return false;
076        }
077    }
078
079    /**
080     * Compares two {@link State}s.
081     */
082    public static boolean equalsStrict(State a, State b) {
083        if (a == b) {
084            return true;
085        }
086        if (a == null || b == null) {
087            return false;
088        }
089        if (a.size() != b.size()) {
090            return false;
091        }
092        if (!a.keySet().equals(b.keySet())) {
093            return false;
094        }
095        for (Entry<String, Serializable> en : a.entrySet()) {
096            String key = en.getKey();
097            Serializable va = en.getValue();
098            Serializable vb = b.get(key);
099            if (!equalsStrict(va, vb)) {
100                return false;
101            }
102        }
103        return true;
104    }
105
106    /**
107     * Compares two arrays of scalars.
108     */
109    public static boolean equalsStrict(Object[] a, Object[] b) {
110        // we have scalars, Arrays.equals() is enough
111        return Arrays.equals(a, b);
112    }
113
114    /**
115     * Compares two {@link List}s.
116     */
117    public static boolean equalsStrict(List<Serializable> a, List<Serializable> b) {
118        if (a == b) {
119            return true;
120        }
121        if (a == null || b == null) {
122            return false;
123        }
124        if (a.size() != b.size()) {
125            return false;
126        }
127        for (Iterator<Serializable> ita = a.iterator(), itb = b.iterator(); ita.hasNext();) {
128            if (!equalsStrict(ita.next(), itb.next())) {
129                return false;
130            }
131        }
132        return true;
133    }
134
135    /**
136     * Compares two values.
137     * <p>
138     * A {@code null} value or an empty array or {@code List} is equivalent to an absent value. A {@code null}
139     * {@link State} is equivalent to an empty {@link State} (or a {@link State} containing only absent values).
140     */
141    public static boolean equalsLoose(Object a, Object b) {
142        if (a == b) {
143            return true;
144        } else if (a instanceof State && b instanceof State //
145                || a instanceof State && b == null //
146                || a == null && b instanceof State) {
147            return equalsLoose((State) a, (State) b);
148        } else if (a instanceof List && b instanceof List //
149                || a instanceof List && b == null //
150                || a == null && b instanceof List) {
151            @SuppressWarnings("unchecked")
152            List<Serializable> la = (List<Serializable>) a;
153            @SuppressWarnings("unchecked")
154            List<Serializable> lb = (List<Serializable>) b;
155            return equalsLoose(la, lb);
156        } else if (a instanceof Object[] && b instanceof Object[] //
157                || a instanceof Object[] && b == null //
158                || a == null && b instanceof Object[]) {
159            return equalsLoose((Object[]) a, (Object[]) b);
160        } else if (a instanceof ListDiff && b instanceof ListDiff) {
161            ListDiff lda = (ListDiff) a;
162            ListDiff ldb = (ListDiff) b;
163            return lda.isArray == ldb.isArray && equalsLoose(lda.diff, ldb.diff) && equalsLoose(lda.rpush, ldb.rpush);
164        } else if (isScalar(a) && isScalar(b)) {
165            return a.equals(b);
166        } else {
167            return false;
168        }
169    }
170
171    /**
172     * Compares two {@link State}s.
173     * <p>
174     * A {@code null} value or an empty array or {@code List} is equivalent to an absent value. A {@code null}
175     * {@link State} is equivalent to an empty {@link State} (or a {@link State} containing only absent values).
176     */
177    public static boolean equalsLoose(State a, State b) {
178        if (a == null) {
179            a = State.EMPTY;
180        }
181        if (b == null) {
182            b = State.EMPTY;
183        }
184        for (Entry<String, Serializable> en : a.entrySet()) {
185            Serializable va = en.getValue();
186            if (va == null) {
187                // checked by loop on b
188                continue;
189            }
190            String key = en.getKey();
191            Serializable vb = b.get(key);
192            if (!equalsLoose(va, vb)) {
193                return false;
194            }
195        }
196        for (Entry<String, Serializable> en : b.entrySet()) {
197            String key = en.getKey();
198            Serializable va = a.get(key);
199            if (va != null) {
200                // already checked by loop on a
201                continue;
202            }
203            Serializable vb = en.getValue();
204            if (!equalsLoose(null, vb)) {
205                return false;
206            }
207        }
208        return true;
209    }
210
211    /**
212     * Compares two arrays of scalars.
213     * <p>
214     * {@code null} values are equivalent to empty arrays.
215     */
216    public static boolean equalsLoose(Object[] a, Object[] b) {
217        if (a != null && a.length == 0) {
218            a = null;
219        }
220        if (b != null && b.length == 0) {
221            b = null;
222        }
223        // we have scalars, Arrays.equals() is enough
224        return Arrays.equals(a, b);
225    }
226
227    /**
228     * Compares two {@link List}s.
229     * <p>
230     * {@code null} values are equivalent to empty lists.
231     */
232    public static boolean equalsLoose(List<Serializable> a, List<Serializable> b) {
233        if (a != null && a.isEmpty()) {
234            a = null;
235        }
236        if (b != null && b.isEmpty()) {
237            b = null;
238        }
239        if (a == b) {
240            return true;
241        }
242        if (a == null || b == null) {
243            return false;
244        }
245        if (a.size() != b.size()) {
246            return false;
247        }
248        for (Iterator<Serializable> ita = a.iterator(), itb = b.iterator(); ita.hasNext();) {
249            if (!equalsLoose(ita.next(), itb.next())) {
250                return false;
251            }
252        }
253        return true;
254    }
255
256    /**
257     * Makes a deep copy of a value.
258     */
259    public static Serializable deepCopy(Object value) {
260        return deepCopy(value, false);
261    }
262
263    /**
264     * Makes a deep copy of a value, optionally thread-safe.
265     *
266     * @param threadSafe if {@code true}, then thread-safe datastructures are used
267     */
268    public static Serializable deepCopy(Object value, boolean threadSafe) {
269        if (value == null) {
270            return (Serializable) value;
271        } else if (value instanceof State) {
272            return deepCopy((State) value, threadSafe);
273        } else if (value instanceof List) {
274            @SuppressWarnings("unchecked")
275            List<Serializable> list = (List<Serializable>) value;
276            return (Serializable) deepCopy(list, threadSafe);
277        } else if (value instanceof Object[]) {
278            // array values are supposed to be scalars
279            return ((Object[]) value).clone();
280        }
281        // else scalar value -- check anyway (debug)
282        else if (!isScalar(value)) {
283            throw new UnsupportedOperationException("Cannot deep copy: " + value.getClass().getName());
284        }
285        return (Serializable) value;
286    }
287
288    /**
289     * Makes a deep copy of a {@link State} map.
290     */
291    public static State deepCopy(State state) {
292        return deepCopy(state, false);
293    }
294
295    /**
296     * Makes a deep copy of a {@link State} map, optionally thread-safe.
297     *
298     * @param threadSafe if {@code true}, then thread-safe datastructures are used
299     */
300    public static State deepCopy(State state, boolean threadSafe) {
301        State copy = new State(state.size(), threadSafe);
302        for (Entry<String, Serializable> en : state.entrySet()) {
303            copy.put(en.getKey(), deepCopy(en.getValue(), threadSafe));
304        }
305        return copy;
306    }
307
308    /**
309     * Makes a deep copy of a {@link List}.
310     */
311    public static List<Serializable> deepCopy(List<Serializable> list) {
312        return deepCopy(list, false);
313    }
314
315    /**
316     * Makes a deep copy of a {@link List}, optionally thread-safe.
317     *
318     * @param threadSafe if {@code true}, then thread-safe datastructures are used
319     */
320    public static List<Serializable> deepCopy(List<Serializable> list, boolean threadSafe) {
321        List<Serializable> copy = threadSafe ? new CopyOnWriteArrayList<Serializable>() : new ArrayList<Serializable>(
322                list.size());
323        for (Serializable v : list) {
324            copy.add(deepCopy(v, threadSafe));
325        }
326        return copy;
327    }
328
329    /**
330     * Does a diff of two values.
331     *
332     * @return a {@link StateDiff}, a {@link ListDiff}, {@link #NOP}, or an actual value (including {@code null})
333     */
334    public static Serializable diff(Object a, Object b) {
335        if (equalsLoose(a, b)) {
336            return NOP;
337        }
338        if (a instanceof Object[] && b instanceof Object[]) {
339            return diff((Object[]) a, (Object[]) b);
340        }
341        if (a instanceof List && b instanceof List) {
342            @SuppressWarnings("unchecked")
343            List<Object> la = (List<Object>) a;
344            @SuppressWarnings("unchecked")
345            List<Object> lb = (List<Object>) b;
346            return (Serializable) diff(la, lb);
347        }
348        if (a instanceof State && b instanceof State) {
349            StateDiff diff = diff((State) a, (State) b);
350            return diff.isEmpty() ? NOP : diff;
351        }
352        return (Serializable) b;
353    }
354
355    public static Serializable diff(Object[] a, Object[] b) {
356        List<Object> la = Arrays.asList(a);
357        List<Object> lb = Arrays.asList(b);
358        Serializable diff = diff(la, lb);
359        if (diff instanceof List) {
360            return b;
361        }
362        ListDiff listDiff = (ListDiff) diff;
363        listDiff.isArray = true;
364        return listDiff;
365    }
366
367    public static Serializable diff(List<Object> a, List<Object> b) {
368        ListDiff listDiff = new ListDiff();
369        listDiff.isArray = false;
370        int aSize = a.size();
371        int bSize = b.size();
372        // TODO configure zero-length "a" case
373        boolean doRPush = aSize > 0 && aSize < bSize;
374        // we can use a list diff if lists are the same size,
375        // or we have a rpush
376        boolean doDiff = aSize == bSize || doRPush;
377        if (!doDiff) {
378            return (Serializable) b;
379        }
380        int len = Math.min(aSize, bSize);
381        List<Object> diff = new ArrayList<>(len);
382        int nops = 0;
383        int diffs = 0;
384        for (int i = 0; i < len; i++) {
385            Serializable elemDiff = diff(a.get(i), b.get(i));
386            if (elemDiff == NOP) {
387                nops++;
388            } else if (elemDiff instanceof StateDiff) {
389                diffs++;
390            }
391            // TODO if the individual element diffs are big StateDiffs,
392            // do a full State replacement instead
393            diff.add(elemDiff);
394        }
395        if (nops == len) {
396            // only nops
397            diff = null;
398        } else if (diffs == 0) {
399            // only setting elements or nops
400            // TODO use a higher ratio than 0% of diffs
401            return (Serializable) b;
402        }
403        listDiff.diff = diff;
404        if (doRPush) {
405            List<Object> rpush = new ArrayList<>(bSize - aSize);
406            for (int i = aSize; i < bSize; i++) {
407                rpush.add(b.get(i));
408            }
409            listDiff.rpush = rpush;
410        }
411        return listDiff;
412    }
413
414    /**
415     * Makes a diff copy of two {@link State} maps.
416     * <p>
417     * The returned diff state contains only the key/values that changed. {@code null} values are equivalent to absent
418     * values.
419     * <p>
420     * For values set to null or removed, the value is null.
421     * <p>
422     * For sub-documents, a recursive diff is returned.
423     *
424     * @return a {@link StateDiff} which, when applied to a, gives b.
425     */
426    public static StateDiff diff(State a, State b) {
427        StateDiff diff = new StateDiff();
428        for (Entry<String, Serializable> en : a.entrySet()) {
429            Serializable va = en.getValue();
430            if (va == null) {
431                // checked by loop on b
432                continue;
433            }
434            String key = en.getKey();
435            Serializable vb = b.get(key);
436            if (vb == null) {
437                // value must be cleared
438                diff.put(key, null);
439            } else {
440                // compare values
441                Serializable elemDiff = diff(va, vb);
442                if (elemDiff != NOP) {
443                    diff.put(key, elemDiff);
444                }
445            }
446        }
447        for (Entry<String, Serializable> en : b.entrySet()) {
448            String key = en.getKey();
449            Serializable va = a.get(key);
450            if (va != null) {
451                // already checked by loop on a
452                continue;
453            }
454            Serializable vb = en.getValue();
455            if (!equalsLoose(null, vb)) {
456                // value must be added
457                diff.put(key, vb);
458            }
459        }
460        return diff;
461    }
462
463    /**
464     * Changes the deltas stored into actual full values.
465     *
466     * @since 6.0
467     */
468    public static void resetDeltas(State state) {
469        for (Entry<String, Serializable> en : state.entrySet()) {
470            Serializable value = en.getValue();
471            if (value instanceof State) {
472                resetDeltas((State) value);
473            } else if (value instanceof Delta) {
474                state.putInternal(en.getKey(), ((Delta) value).getFullValue());
475            }
476        }
477    }
478
479}