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}