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