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}