001/*
002 * (C) Copyright 2014-2019 Nuxeo (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.Collections;
028import java.util.HashSet;
029import java.util.Iterator;
030import java.util.List;
031import java.util.Objects;
032import java.util.Set;
033import java.util.Map.Entry;
034import java.util.concurrent.CopyOnWriteArrayList;
035
036import org.nuxeo.ecm.core.api.model.Delta;
037import org.nuxeo.ecm.core.storage.State.ListDiff;
038import org.nuxeo.ecm.core.storage.State.StateDiff;
039import org.nuxeo.runtime.api.Framework;
040
041/**
042 * Helpers for deep copy and deep diff of {@link State} objects.
043 */
044public class StateHelper {
045
046    private static final String DISABLED_DELTA_PROP = "org.nuxeo.core.delta.disabled";
047
048    /** Utility class. */
049    private StateHelper() {
050    }
051
052    /**
053     * Checks if we have a base type compatible with {@link State} helper processing.
054     */
055    public static boolean isScalar(Object value) {
056        return value instanceof String //
057                || value instanceof Boolean //
058                || value instanceof Long //
059                || value instanceof Double //
060                || value instanceof Calendar //
061                || value instanceof Delta;
062    }
063
064    /**
065     * Compares two values.
066     */
067    public static boolean equalsStrict(Object a, Object b) {
068        if (a == b) {
069            return true;
070        } else if (a == null || b == null) {
071            return false;
072        } else if (a instanceof State && b instanceof State) {
073            return equalsStrict((State) a, (State) b);
074        } else if (a instanceof List && b instanceof List) {
075            @SuppressWarnings("unchecked")
076            List<Serializable> la = (List<Serializable>) a;
077            @SuppressWarnings("unchecked")
078            List<Serializable> lb = (List<Serializable>) b;
079            return equalsStrict(la, lb);
080        } else if (a instanceof Object[] && b instanceof Object[]) {
081            return equalsStrict((Object[]) a, (Object[]) b);
082        } else if (a instanceof ListDiff && b instanceof ListDiff) {
083            ListDiff lda = (ListDiff) a;
084            ListDiff ldb = (ListDiff) b;
085            return lda.isArray == ldb.isArray //
086                    && equalsStrict(lda.diff, ldb.diff) //
087                    && equalsStrict(lda.rpush, ldb.rpush) //
088                    && equalsStrict(lda.pull, ldb.pull);
089        } else if (isScalar(a) && isScalar(b)) {
090            return a.equals(b);
091        } else {
092            return false;
093        }
094    }
095
096    /**
097     * Compares two {@link State}s.
098     */
099    public static boolean equalsStrict(State a, State b) {
100        if (a == b) {
101            return true;
102        }
103        if (a == null || b == null) {
104            return false;
105        }
106        if (a.size() != b.size()) {
107            return false;
108        }
109        if (!a.keySet().equals(b.keySet())) {
110            return false;
111        }
112        for (Entry<String, Serializable> en : a.entrySet()) {
113            String key = en.getKey();
114            Serializable va = en.getValue();
115            Serializable vb = b.get(key);
116            if (!equalsStrict(va, vb)) {
117                return false;
118            }
119        }
120        return true;
121    }
122
123    /**
124     * Compares two arrays of scalars.
125     */
126    public static boolean equalsStrict(Object[] a, Object[] b) {
127        // we have scalars, Arrays.equals() is enough
128        return Arrays.equals(a, b);
129    }
130
131    /**
132     * Compares two {@link List}s.
133     */
134    public static boolean equalsStrict(List<Serializable> a, List<Serializable> b) {
135        if (a == b) {
136            return true;
137        }
138        if (a == null || b == null) {
139            return false;
140        }
141        if (a.size() != b.size()) {
142            return false;
143        }
144        for (Iterator<Serializable> ita = a.iterator(), itb = b.iterator(); ita.hasNext();) {
145            if (!equalsStrict(ita.next(), itb.next())) {
146                return false;
147            }
148        }
149        return true;
150    }
151
152    /**
153     * Compares two values.
154     * <p>
155     * A {@code null} value or an empty array or {@code List} is equivalent to an absent value. A {@code null}
156     * {@link State} is equivalent to an empty {@link State} (or a {@link State} containing only absent values).
157     */
158    public static boolean equalsLoose(Object a, Object b) {
159        if (a == b) {
160            return true;
161        } else if (a instanceof State && b instanceof State //
162                || a instanceof State && b == null //
163                || a == null && b instanceof State) {
164            return equalsLoose((State) a, (State) b);
165        } else if (a instanceof List && b instanceof List //
166                || a instanceof List && b == null //
167                || a == null && b instanceof List) {
168            @SuppressWarnings("unchecked")
169            List<Serializable> la = (List<Serializable>) a;
170            @SuppressWarnings("unchecked")
171            List<Serializable> lb = (List<Serializable>) b;
172            return equalsLoose(la, lb);
173        } else if (a instanceof Object[] && b instanceof Object[] //
174                || a instanceof Object[] && b == null //
175                || a == null && b instanceof Object[]) {
176            return equalsLoose((Object[]) a, (Object[]) b);
177        } else if (a instanceof ListDiff && b instanceof ListDiff) {
178            ListDiff lda = (ListDiff) a;
179            ListDiff ldb = (ListDiff) b;
180            return lda.isArray == ldb.isArray && equalsLoose(lda.diff, ldb.diff) && equalsLoose(lda.rpush, ldb.rpush);
181        } else if (isScalar(a) && isScalar(b)) {
182            return a.equals(b); // NOSONAR
183        } else {
184            return false;
185        }
186    }
187
188    /**
189     * Compares two {@link State}s.
190     * <p>
191     * A {@code null} value or an empty array or {@code List} is equivalent to an absent value. A {@code null}
192     * {@link State} is equivalent to an empty {@link State} (or a {@link State} containing only absent values).
193     */
194    public static boolean equalsLoose(State a, State b) {
195        if (a == null) {
196            a = State.EMPTY;
197        }
198        if (b == null) {
199            b = State.EMPTY;
200        }
201        for (Entry<String, Serializable> en : a.entrySet()) {
202            Serializable va = en.getValue();
203            if (va == null) {
204                // checked by loop on b
205                continue;
206            }
207            String key = en.getKey();
208            Serializable vb = b.get(key);
209            if (!equalsLoose(va, vb)) {
210                return false;
211            }
212        }
213        for (Entry<String, Serializable> en : b.entrySet()) {
214            String key = en.getKey();
215            Serializable va = a.get(key);
216            if (va != null) {
217                // already checked by loop on a
218                continue;
219            }
220            Serializable vb = en.getValue();
221            if (!equalsLoose(null, vb)) {
222                return false;
223            }
224        }
225        return true;
226    }
227
228    /**
229     * Compares two arrays of scalars.
230     * <p>
231     * {@code null} values are equivalent to empty arrays.
232     */
233    public static boolean equalsLoose(Object[] a, Object[] b) {
234        if (a != null && a.length == 0) {
235            a = null;
236        }
237        if (b != null && b.length == 0) {
238            b = null;
239        }
240        // we have scalars, Arrays.equals() is enough
241        return Arrays.equals(a, b);
242    }
243
244    /**
245     * Compares two {@link List}s.
246     * <p>
247     * {@code null} values are equivalent to empty lists.
248     */
249    public static boolean equalsLoose(List<Serializable> a, List<Serializable> b) {
250        if (a != null && a.isEmpty()) {
251            a = null;
252        }
253        if (b != null && b.isEmpty()) {
254            b = null;
255        }
256        if (a == b) {
257            return true;
258        }
259        if (a == null || b == null) {
260            return false;
261        }
262        if (a.size() != b.size()) {
263            return false;
264        }
265        for (Iterator<Serializable> ita = a.iterator(), itb = b.iterator(); ita.hasNext();) {
266            if (!equalsLoose(ita.next(), itb.next())) {
267                return false;
268            }
269        }
270        return true;
271    }
272
273    /**
274     * Makes a deep copy of a value.
275     */
276    public static Serializable deepCopy(Object value) {
277        return deepCopy(value, false);
278    }
279
280    /**
281     * Makes a deep copy of a value, optionally thread-safe.
282     *
283     * @param threadSafe if {@code true}, then thread-safe datastructures are used
284     */
285    public static Serializable deepCopy(Object value, boolean threadSafe) {
286        if (value == null) {
287            return (Serializable) value;
288        } else if (value instanceof State) {
289            return deepCopy((State) value, threadSafe);
290        } else if (value instanceof List) {
291            @SuppressWarnings("unchecked")
292            List<Serializable> list = (List<Serializable>) value;
293            return (Serializable) deepCopy(list, threadSafe);
294        } else if (value instanceof Object[]) {
295            // array values are supposed to be scalars
296            return ((Object[]) value).clone();
297        }
298        // else scalar value -- check anyway (debug)
299        else if (!isScalar(value)) {
300            throw new UnsupportedOperationException("Cannot deep copy: " + value.getClass().getName());
301        }
302        return (Serializable) value;
303    }
304
305    /**
306     * Makes a deep copy of a {@link State} map.
307     */
308    public static State deepCopy(State state) {
309        return deepCopy(state, false);
310    }
311
312    /**
313     * Makes a deep copy of a {@link State} map, optionally thread-safe.
314     *
315     * @param threadSafe if {@code true}, then thread-safe datastructures are used
316     */
317    public static State deepCopy(State state, boolean threadSafe) {
318        State copy = new State(state.size(), threadSafe);
319        for (Entry<String, Serializable> en : state.entrySet()) {
320            copy.put(en.getKey(), deepCopy(en.getValue(), threadSafe));
321        }
322        return copy;
323    }
324
325    /**
326     * Makes a deep copy of a {@link List}.
327     */
328    public static List<Serializable> deepCopy(List<Serializable> list) {
329        return deepCopy(list, false);
330    }
331
332    /**
333     * Makes a deep copy of a {@link List}, optionally thread-safe.
334     *
335     * @param threadSafe if {@code true}, then thread-safe datastructures are used
336     */
337    public static List<Serializable> deepCopy(List<Serializable> list, boolean threadSafe) {
338        List<Serializable> copy = threadSafe ? new CopyOnWriteArrayList<>() : new ArrayList<>(
339                list.size());
340        for (Serializable v : list) {
341            copy.add(deepCopy(v, threadSafe));
342        }
343        return copy;
344    }
345
346    /**
347     * Does a diff of two values.
348     *
349     * @return a {@link StateDiff}, a {@link ListDiff}, {@link State#NOP}, or an actual value (including {@code null})
350     */
351    public static Serializable diff(Object a, Object b) {
352        if (equalsLoose(a, b)) {
353            return NOP;
354        }
355        if ((a == null || a instanceof Object[]) && (b == null || b instanceof Object[])) {
356            return diff((Object[]) a, (Object[]) b);
357        }
358        if ((a == null || a instanceof List) && (b == null || b instanceof List)) {
359            @SuppressWarnings("unchecked")
360            List<Object> la = (List<Object>) a;
361            @SuppressWarnings("unchecked")
362            List<Object> lb = (List<Object>) b;
363            return diff(la, lb);
364        }
365        if (a instanceof State && b instanceof State) {
366            StateDiff diff = diff((State) a, (State) b);
367            return diff.isEmpty() ? NOP : diff;
368        }
369        return (Serializable) b;
370    }
371
372    public static Serializable diff(Object[] a, Object[] b) {
373        List<Object> la = a == null ? null : Arrays.asList(a);
374        List<Object> lb = b == null ? null : Arrays.asList(b);
375        Serializable diff = diff(la, lb);
376        if (diff instanceof List) {
377            return b;
378        }
379        ListDiff listDiff = (ListDiff) diff;
380        listDiff.isArray = true;
381        return listDiff;
382    }
383
384    public static Serializable diff(List<Object> a, List<Object> b) {
385        ListDiff listDiff = new ListDiff();
386        if (a == null) {
387            a = Collections.emptyList();
388        }
389        if (b == null) {
390            b = Collections.emptyList();
391        }
392        List<Object> pull = findPull(a, b);
393        if (pull != null) {
394            listDiff.pull = pull;
395            return listDiff;
396        }
397        int aSize = a.size();
398        int bSize = b.size();
399        boolean doRPush = aSize < bSize;
400        // we can use a list diff if lists are the same size,
401        // or we have a rpush
402        boolean doDiff = aSize == bSize || doRPush;
403        if (!doDiff) {
404            return (Serializable) b;
405        }
406        int len = Math.min(aSize, bSize);
407        List<Object> diff = new ArrayList<>(len);
408        int nops = 0;
409        int diffs = 0;
410        for (int i = 0; i < len; i++) {
411            Serializable elemDiff = diff(a.get(i), b.get(i));
412            if (elemDiff == NOP) {
413                nops++;
414            } else if (elemDiff instanceof StateDiff) {
415                diffs++;
416            }
417            // TODO if the individual element diffs are big StateDiffs,
418            // do a full State replacement instead
419            diff.add(elemDiff);
420        }
421        if (nops == len) {
422            // only nops
423            diff = null;
424        } else if (diffs == 0) {
425            // only setting elements or nops
426            // TODO use a higher ratio than 0% of diffs
427            return (Serializable) b;
428        }
429        listDiff.diff = diff;
430        if (doRPush) {
431            List<Object> rpush = new ArrayList<>(bSize - aSize);
432            for (int i = aSize; i < bSize; i++) {
433                rpush.add(b.get(i));
434            }
435            listDiff.rpush = rpush;
436        }
437        return listDiff;
438    }
439
440    /** @since 11.5 */
441    public static List<Object> findPull(List<Object> a, List<Object> b) {
442        // check that list size decreased
443        if (a.size() <= b.size()) {
444            return null;
445        }
446        // check that we have scalar element types (complex not supported)
447        Object value = a.get(0);
448        if (!isScalar(value)) {
449            return null;
450        }
451        // get the set difference
452        Set<Object> set = new HashSet<>(a);
453        set.removeAll(b);
454        if (set.isEmpty()) {
455            return null;
456        }
457        // check that it's really a pull operation (all candidates are removed)
458        List<Object> list = new ArrayList<>(a);
459        List<Object> pull = new ArrayList<>(set);
460        list.removeAll(pull);
461        if (!equalsLoose(list, b)) {
462            return null;
463        }
464        return pull;
465    }
466
467    /**
468     * Makes a diff copy of two {@link State} maps.
469     * <p>
470     * The returned diff state contains only the key/values that changed. {@code null} values are equivalent to absent
471     * values.
472     * <p>
473     * For values set to null or removed, the value is null.
474     * <p>
475     * When setting a delta, the old value is checked to know if the delta should be kept or if a full value should be
476     * set instead.
477     * <p>
478     * For sub-documents, a recursive diff is returned.
479     *
480     * @return a {@link StateDiff} which, when applied to a, gives b.
481     */
482    public static StateDiff diff(State a, State b) {
483        StateDiff diff = new StateDiff();
484        for (Entry<String, Serializable> en : a.entrySet()) {
485            Serializable va = en.getValue();
486            if (va == null) {
487                // checked by loop on b
488                continue;
489            }
490            String key = en.getKey();
491            Serializable vb = b.get(key);
492            if (vb == null) {
493                // value must be cleared
494                diff.put(key, null);
495            } else {
496                // compare values
497                Serializable elemDiff = diff(va, vb);
498                if (elemDiff != NOP) {
499                    if (elemDiff instanceof Delta) {
500                        Delta delta = (Delta) elemDiff;
501                        Serializable deltaBase = delta.getBase();
502                        if (!Objects.equals(va, deltaBase)) {
503                            // delta's base is not the old value
504                            // -> set a new value, don't use a delta update
505                            elemDiff = delta.getFullValue();
506                        }
507                        // else delta's base is the in-database value
508                        // because base is consistent with old value, assume the delta is already properly computed
509                    }
510                    diff.put(key, elemDiff);
511                }
512            }
513        }
514        for (Entry<String, Serializable> en : b.entrySet()) {
515            String key = en.getKey();
516            Serializable va = a.get(key);
517            if (va != null) {
518                // already checked by loop on a
519                continue;
520            }
521            Serializable vb = en.getValue();
522            if (!equalsLoose(null, vb)) {
523                // value must be set or rpushed
524                diff.put(key, rpushOrValue(vb));
525            }
526        }
527        return diff;
528    }
529
530    /**
531     * Returns a pure rpush ListDiff for the value if it's an array/list.
532     *
533     * @param value the value
534     * @return a pure rpush ListDiff for the value, if possible
535     * @since 11.4
536     */
537    public static Serializable rpushOrValue(Serializable value) {
538        if (value instanceof Object[]) {
539            ListDiff listDiff = new ListDiff();
540            listDiff.isArray = true;
541            listDiff.rpush = Arrays.asList((Object[]) value);
542            return listDiff;
543        } else if (value instanceof List) {
544            ListDiff listDiff = new ListDiff();
545            @SuppressWarnings("unchecked")
546            List<Object> list = (List<Object>) value;
547            listDiff.rpush = list;
548            return listDiff;
549        } else {
550            return value;
551        }
552    }
553
554    /**
555     * Changes the deltas stored into actual full values.
556     *
557     * @since 6.0
558     */
559    public static void resetDeltas(State state) {
560        if (Boolean.parseBoolean(Framework.getProperty(DISABLED_DELTA_PROP, "false"))) {
561            return;
562        }
563        for (Entry<String, Serializable> en : state.entrySet()) {
564            Serializable value = en.getValue();
565            if (value instanceof State) {
566                resetDeltas((State) value);
567            } else if (value instanceof Delta) {
568                state.put(en.getKey(), ((Delta) value).getFullValue());
569            }
570        }
571    }
572
573}