001/** 002 * GRANITE DATA SERVICES 003 * Copyright (C) 2006-2013 GRANITE DATA SERVICES S.A.S. 004 * 005 * This file is part of the Granite Data Services Platform. 006 * 007 * *** 008 * 009 * Community License: GPL 3.0 010 * 011 * This file is free software: you can redistribute it and/or modify 012 * it under the terms of the GNU General Public License as published 013 * by the Free Software Foundation, either version 3 of the License, 014 * or (at your option) any later version. 015 * 016 * This file is distributed in the hope that it will be useful, but 017 * WITHOUT ANY WARRANTY; without even the implied warranty of 018 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 019 * GNU General Public License for more details. 020 * 021 * You should have received a copy of the GNU General Public License 022 * along with this program. If not, see <http://www.gnu.org/licenses/>. 023 * 024 * *** 025 * 026 * Available Commercial License: GraniteDS SLA 1.0 027 * 028 * This is the appropriate option if you are creating proprietary 029 * applications and you are not prepared to distribute and share the 030 * source code of your application under the GPL v3 license. 031 * 032 * Please visit http://www.granitedataservices.com/license for more 033 * details. 034 */ 035package org.granite.client.tide.data.impl; 036 037import java.lang.reflect.Array; 038import java.util.ArrayList; 039import java.util.Arrays; 040import java.util.Collection; 041import java.util.Date; 042import java.util.HashMap; 043import java.util.IdentityHashMap; 044import java.util.Iterator; 045import java.util.List; 046import java.util.Map; 047import java.util.Map.Entry; 048import java.util.Set; 049 050import org.granite.client.persistence.collection.PersistentCollection; 051import org.granite.client.tide.PropertyHolder; 052import org.granite.client.tide.data.Value; 053import org.granite.client.tide.data.spi.DataManager; 054import org.granite.client.tide.data.spi.DataManager.ChangeKind; 055import org.granite.client.tide.data.spi.DirtyCheckContext; 056import org.granite.client.tide.data.spi.MergeContext; 057import org.granite.client.tide.data.spi.Wrapper; 058import org.granite.client.util.WeakIdentityHashMap; 059import org.granite.logging.Logger; 060 061/** 062 * @author William DRAI 063 */ 064public class DirtyCheckContextImpl implements DirtyCheckContext { 065 066 private static Logger log = Logger.getLogger("org.granite.client.tide.data.DirtyCheckContextImpl"); 067 068 private DataManager dataManager; 069 private int dirtyCount = 0; 070 private WeakIdentityHashMap<Object, Map<String, Object>> savedProperties = new WeakIdentityHashMap<Object, Map<String, Object>>(); 071 private WeakIdentityHashMap<Object, Object> unsavedEntities = new WeakIdentityHashMap<Object, Object>(); 072 073 074 public DirtyCheckContextImpl(DataManager dataManager) { 075 this.dataManager = dataManager; 076 } 077 078 private boolean isEntity(Object obj) { 079 return dataManager.isEntity(obj); 080 } 081 082 public boolean isDirty() { 083 return dirtyCount > 0; 084 } 085 086 public void notifyDirtyChange(boolean oldDirty) { 087 if (isDirty() == oldDirty) 088 return; 089 090 dataManager.notifyDirtyChange(oldDirty, isDirty()); 091 } 092 093 public boolean notifyEntityDirtyChange(Object entity, boolean oldDirtyEntity) { 094 boolean newDirtyEntity = isEntityChanged(entity); 095 if (newDirtyEntity != oldDirtyEntity) 096 dataManager.notifyEntityDirtyChange(entity, oldDirtyEntity, newDirtyEntity); 097 return newDirtyEntity; 098 } 099 100 public Map<String, Object> getSavedProperties(Object entity) { 101 return savedProperties.get(entity); 102 } 103 104 public boolean isSaved(Object entity) { 105 return savedProperties.containsKey(entity); 106 } 107 108 /** 109 * Check if the object is marked as new in the context 110 * 111 * @param object object to check 112 * 113 * @return true if the object has been newly attached 114 */ 115 public boolean isUnsaved(Object object) { 116 return unsavedEntities.containsKey(object); 117 } 118 119 public void addUnsaved(Object entity) { 120 unsavedEntities.put(entity, true); 121 } 122 123 public void clear(boolean notify) { 124 boolean wasDirty = isDirty(); 125 dirtyCount = 0; 126 savedProperties.clear(); 127 if (notify) 128 notifyDirtyChange(wasDirty); 129 } 130 131 /** 132 * Check if entity property has been changed since last remote call 133 * 134 * @param entity entity to check 135 * @param propertyName property to check 136 * @param value current value to compare with saved value 137 * 138 * @return true is value has been changed 139 */ 140 public boolean isEntityPropertyChanged(Object entity, String propertyName, Object value) { 141 Map<String, Object> source = savedProperties.get(entity); 142 if (source != null) 143 return source.containsKey(propertyName) && !isSame(source.get(propertyName), value); 144 145 return !isSame(dataManager.getPropertyValue(entity, propertyName), value); 146 } 147 148 149 public boolean isEntityChanged(Object entity) { 150 return isEntityChanged(entity, null, null, null); 151 } 152 153 /** 154 * Check if entity has changed since last save point 155 * 156 * @param entity entity to check 157 * @param propName property name 158 * @param value 159 * 160 * @return entity is dirty 161 */ 162 @SuppressWarnings("unchecked") 163 public boolean isEntityChanged(Object entity, Object embedded, String propName, Object value) { 164 if (!dataManager.isInitialized(entity)) 165 return false; 166 167 boolean dirty = false; 168 169 Map<String, Object> pval = dataManager.getPropertyValues(entity, true, false); 170 171 Map<String, Object> save = savedProperties.get(entity); 172 173 if (embedded == null) 174 embedded = entity; 175 176 for (String p : pval.keySet()) { 177 Object val = (entity == embedded && p.equals(propName)) ? value : pval.get(p); 178 Object saveval = save != null ? save.get(p) : null; 179 180 if (save != null && ((val != null && (ObjectUtil.isSimple(val) || val instanceof byte[])) 181 || (saveval != null && (ObjectUtil.isSimple(saveval) || saveval instanceof byte[])))) { 182 dirty = true; 183 break; 184 } 185 else if (save != null && (val instanceof Value || saveval instanceof Value || val instanceof Enum || saveval instanceof Enum)) { 186 if (saveval != null && ((val == null && saveval != null) || !val.equals(saveval))) { 187 dirty = true; 188 break; 189 } 190 } 191 else if (save != null && (isEntity(val) || isEntity(saveval))) { 192 if (saveval != null && val != save.get(p)) { 193 dirty = true; 194 break; 195 } 196 } 197 else if ((val instanceof Collection<?> || val instanceof Map<?, ?>)) { 198 if (!dataManager.isInitialized(val)) 199 continue; 200 201 List<Change> savedArray = (List<Change>)saveval; 202 if (savedArray != null && !savedArray.isEmpty()) { 203 dirty = true; 204 break; 205 } 206 } 207 else if (val != null 208 && !(isEntity(val) || ObjectUtil.isSimple(val) || val instanceof Enum || val instanceof Value || val instanceof byte[]) 209 && isEntityChanged(val)) { 210 dirty = true; 211 break; 212 } 213 } 214 return dirty; 215 } 216 217 public boolean isEntityDeepChanged(Object entity) { 218 return isEntityDeepChanged(entity, null, new IdentityHashMap<Object, Boolean>()); 219 } 220 221 private boolean isEntityDeepChanged(Object entity, Object embedded, IdentityHashMap<Object, Boolean> cache) { 222 if (cache == null) 223 cache = new IdentityHashMap<Object, Boolean>(); 224 if (cache.containsKey(entity)) 225 return false; 226 cache.put(entity, true); 227 228 if (!dataManager.isInitialized(entity)) 229 return false; 230 231 Map<String, Object> pval = dataManager.getPropertyValues(entity, true, false); 232 233 if (embedded == null) 234 embedded = entity; 235 236 Map<String, Object> save = savedProperties.get(entity); 237 238 for (String p : pval.keySet()) { 239 Object val = pval.get(p); 240 Object saveval = save != null ? save.get(p) : null; 241 242 if (save != null && ((val != null && (ObjectUtil.isSimple(val) || val instanceof byte[])) 243 || (saveval != null && (ObjectUtil.isSimple(saveval) || saveval instanceof byte[])))) { 244 return true; 245 } 246 else if (save != null && (val instanceof Value || saveval instanceof Value || val instanceof Enum || saveval instanceof Enum)) { 247 if (saveval != null && ((val == null && saveval != null) || !val.equals(saveval))) { 248 return true; 249 } 250 } 251 else if (save != null && (isEntity(val) || isEntity(saveval))) { 252 if (saveval != null && val != save.get(p)) 253 return true; 254 255 if (isEntityDeepChanged(val, null, cache)) 256 return true; 257 } 258 else if (val instanceof Collection<?> || val instanceof Map<?, ?>) { 259 if (!dataManager.isInitialized(val)) 260 continue; 261 262 @SuppressWarnings("unchecked") 263 List<Change> savedArray = (List<Change>)saveval; 264 if (savedArray != null && !savedArray.isEmpty()) 265 return true; 266 267 if (val instanceof Collection<?>) { 268 for (Object elt : (Collection<?>)val) { 269 if (isEntityDeepChanged(elt, null, cache)) 270 return true; 271 } 272 } 273 else if (val instanceof Map<?, ?>) { 274 for (Entry<?, ?> me : ((Map<?, ?>)val).entrySet()) { 275 if (isEntityDeepChanged(me.getKey(), null, cache)) 276 return true; 277 if (isEntityDeepChanged(me.getValue(), null, cache)) 278 return true; 279 } 280 } 281 } 282 else if (val != null 283 && !(isEntity(val) || ObjectUtil.isSimple(val) || val instanceof Enum || val instanceof Value || val instanceof byte[]) 284 && isEntityDeepChanged(val, embedded, cache)) { 285 return true; 286 } 287 } 288 289 return false; 290 } 291 292 293 private boolean isSame(Object val1, Object val2) { 294 if (val1 == null && isEmpty(val2)) 295 return true; 296 else if (val2 == null && isEmpty(val1)) 297 return true; 298 else if (ObjectUtil.isSimple(val1) && ObjectUtil.isSimple(val2)) 299 return val1.equals(val2); 300 else if (val1 instanceof byte[] && val2 instanceof byte[]) 301 return Arrays.equals((byte[])val1, (byte[])val2); 302 else if ((val1 instanceof Value && val2 instanceof Value) || (val1 instanceof Enum && val2 instanceof Enum)) 303 return val1.equals(val2); 304 305 Object n = val1 instanceof Wrapper ? ((Wrapper)val1).getWrappedObject() : val1; 306 Object o = val2 instanceof Wrapper ? ((Wrapper)val2).getWrappedObject() : val2; 307 if (isEntity(n) && isEntity(o)) 308 return dataManager.getUid(n) != null && dataManager.getUid(n).equals(dataManager.getUid(o)); 309 return n == o; 310 } 311 312 private boolean isSameList(List<Object> save, Collection<?> coll) { 313 if (save.size() != coll.size()) 314 return false; 315 316 if (coll instanceof List<?>) { 317 List<?> list = (List<?>)coll; 318 for (int i = 0; i < save.size(); i++) { 319 if (!isSame(save.get(i), list.get(i))) 320 return false; 321 } 322 } 323 else { 324 for (int i = 0; i < save.size(); i++) { 325 boolean found = false; 326 for (Iterator<?> ic = coll.iterator(); ic.hasNext(); ) { 327 Object obj = ic.next(); 328 if (isSame(save.get(i), obj)) { 329 found = true; 330 break; 331 } 332 } 333 if (!found) 334 return false; 335 } 336 } 337 return true; 338 } 339 340 private boolean isSameMap(List<Object[]> save, Map<?, ?> map) { 341 if (save.size() != map.size()) 342 return false; 343 344 for (int i = 0; i < save.size(); i++) { 345 Object[] entry = save.get(i); 346 if (!map.containsKey(entry[0])) 347 return false; 348 if (!isSame(entry[1], map.get(entry[0]))) 349 return false; 350 } 351 return true; 352 } 353 354 private boolean isSameExt(Object val1, Object val2) { 355 if (val1 == null && isEmpty(val2)) 356 return true; 357 else if (val2 == null && isEmpty(val1)) 358 return true; 359 else if (ObjectUtil.isSimple(val1) && ObjectUtil.isSimple(val2)) 360 return val1.equals(val2); 361 else if (val1 instanceof byte[] && val2 instanceof byte[]) 362 return Arrays.equals((byte[])val1, (byte[])val2); 363 else if ((val1 instanceof Value && val2 instanceof Value) || (val1 instanceof Enum && val2 instanceof Enum)) 364 return val1.equals(val2); 365 else if (val1 != null && val1.getClass().isArray() && val2 != null && val2.getClass().isArray()) { 366 if (Array.getLength(val1) != Array.getLength(val2)) 367 return false; 368 for (int idx = 0; idx < Array.getLength(val1); idx++) { 369 if (!isSameExt(Array.get(val1, idx), Array.get(val2, idx))) 370 return false; 371 } 372 return true; 373 } 374 else if (val1 instanceof Set<?> && val2 instanceof Set<?>) { 375 if ((val1 instanceof PersistentCollection && !((PersistentCollection)val1).wasInitialized()) 376 || (val2 instanceof PersistentCollection && !((PersistentCollection)val2).wasInitialized())) 377 return false; 378 Collection<?> coll1 = (Collection<?>)val1; 379 Collection<?> coll2 = (Collection<?>)val2; 380 if (coll1.size() != coll2.size()) 381 return false; 382 for (Object e : coll1) { 383 boolean found = false; 384 for (Object f : coll2) { 385 if (isSameExt(e, f)) { 386 found = true; 387 break; 388 } 389 } 390 if (!found) 391 return false; 392 } 393 for (Object e : coll2) { 394 boolean found = false; 395 for (Object f : coll1) { 396 if (isSameExt(e, f)) { 397 found = true; 398 break; 399 } 400 } 401 if (!found) 402 return false; 403 } 404 return true; 405 } 406 else if (val1 instanceof List<?> && val2 instanceof List<?>) { 407 if ((val1 instanceof PersistentCollection && !((PersistentCollection)val1).wasInitialized()) 408 || (val2 instanceof PersistentCollection && !((PersistentCollection)val2).wasInitialized())) 409 return false; 410 List<?> list1 = (List<?>)val1; 411 List<?> list2 = (List<?>)val2; 412 if (list1.size() != list2.size()) 413 return false; 414 for (int idx = 0; idx < list1.size(); idx++) { 415 if (!isSameExt(list1.get(idx), list2.get(idx))) 416 return false; 417 } 418 return true; 419 } 420 else if (val1 instanceof Collection<?> && val2 instanceof Collection<?>) { 421 if ((val1 instanceof PersistentCollection && !((PersistentCollection)val1).wasInitialized()) 422 || (val2 instanceof PersistentCollection && !((PersistentCollection)val2).wasInitialized())) 423 return false; 424 Collection<?> coll1 = (Collection<?>)val1; 425 Collection<?> coll2 = (Collection<?>)val2; 426 if (coll1.size() != coll2.size()) 427 return false; 428 for (Object obj1 : coll1) { 429 boolean found = false; 430 for (Object obj2 : coll2) { 431 if (isSameExt(obj1, obj2)) { 432 found = true; 433 break; 434 } 435 } 436 if (!found) 437 return false; 438 } 439 for (Object obj2 : coll2) { 440 boolean found = false; 441 for (Object obj1 : coll1) { 442 if (isSameExt(obj1, obj2)) { 443 found = true; 444 break; 445 } 446 } 447 if (!found) 448 return false; 449 } 450 return true; 451 } 452 else if (val1 instanceof Map<?, ?> && val2 instanceof Map<?, ?>) { 453 if ((val1 instanceof PersistentCollection && !((PersistentCollection)val1).wasInitialized()) 454 || (val2 instanceof PersistentCollection && !((PersistentCollection)val2).wasInitialized())) 455 return false; 456 Map<?, ?> map1 = (Map<?, ?>)val1; 457 Map<?, ?> map2 = (Map<?, ?>)val2; 458 if (map1.size() != map2.size()) 459 return false; 460 for (Object e : map1.keySet()) { 461 Object key = null; 462 for (Object f : map2.keySet()) { 463 if (isSameExt(e, f)) { 464 key = f; 465 break; 466 } 467 } 468 if (key == null) 469 return false; 470 if (!isSameExt(map1.get(e), map2.get(key))) 471 return false; 472 } 473 for (Object f : map2.keySet()) { 474 Object key = null; 475 for (Object e : map1.keySet()) { 476 if (isSameExt(e, f)) { 477 key = e; 478 break; 479 } 480 } 481 if (key == null) 482 return false; 483 if (!isSameExt(map1.get(key), map2.get(f))) 484 return false; 485 } 486 return true; 487 } 488 489 Object n = val1 instanceof Wrapper ? ((Wrapper)val1).getWrappedObject() : val1; 490 Object o = val2 instanceof Wrapper ? ((Wrapper)val2).getWrappedObject() : val2; 491 if (isEntity(n) && isEntity(o)) 492 return dataManager.getUid(n).equals(dataManager.getUid(o)); 493 494 return n == o; 495 } 496 497 /** 498 * Interceptor for managed entity setters 499 * 500 * @param entity entity to intercept 501 * @param propName property name 502 * @param oldValue old value 503 * @param newValue new value 504 */ 505 public void entityPropertyChangeHandler(Object entity, Object object, String propName, Object oldValue, Object newValue) { 506 boolean oldDirty = isDirty(); 507 508 boolean diff = !isSame(oldValue, newValue); 509 510 if (diff) { 511 boolean oldDirtyEntity = isEntityChanged(entity, object, propName, oldValue); 512 513 String versionPropertyName = dataManager.isEntity(entity) ? dataManager.getVersionPropertyName(entity) : null; 514 Map<String, Object> save = savedProperties.get(object); 515 boolean unsaved = save == null; 516 517 if (unsaved || (versionPropertyName != null && 518 (save.get(versionPropertyName) != dataManager.getVersion(entity) 519 && !(save.get(versionPropertyName) == null && dataManager.getVersion(entity) == null)))) { 520 521 save = new HashMap<String, Object>(); 522 if (versionPropertyName != null) 523 save.put(versionPropertyName, dataManager.getVersion(entity)); 524 savedProperties.put(object, save); 525 save.put(propName, oldValue); 526 if (unsaved) 527 dirtyCount++; 528 } 529 530 if (save != null && (versionPropertyName == null 531 || save.get(versionPropertyName) == dataManager.getVersion(entity) 532 || (save.get(versionPropertyName) == null && dataManager.getVersion(entity) == null))) { 533 534 if (!save.containsKey(propName)) 535 save.put(propName, oldValue); 536 537 if (isSame(save.get(propName), newValue)) { 538 save.remove(propName); 539 int count = 0; 540 for (String p : save.keySet()) { 541 if (!p.equals(versionPropertyName)) 542 count++; 543 } 544 if (count == 0) { 545 savedProperties.remove(object); 546 dirtyCount--; 547 } 548 } 549 } 550 551 notifyEntityDirtyChange(entity, oldDirtyEntity); 552 } 553 554 notifyDirtyChange(oldDirty); 555 } 556 557 558 /** 559 * Collection event handler to save changes on managed collections 560 * 561 * @param owner owner entity of the collection 562 * @param propName property name of the collection 563 * @param coll collection 564 * @param kind change kind 565 * @param location change location 566 * @param items changed items 567 */ 568 @SuppressWarnings("unchecked") 569 public void entityCollectionChangeHandler(Object owner, String propName, Collection<?> coll, ChangeKind kind, Integer location, Object[] items) { 570 boolean oldDirty = isDirty(); 571 572 String versionPropertyName = dataManager.isEntity(owner) ? dataManager.getVersionPropertyName(owner) : null; 573 boolean oldDirtyEntity = isEntityChanged(owner); 574 575 Map<String, Object> esave = savedProperties.get(owner); 576 boolean unsaved = esave == null; 577 578 if (unsaved || (versionPropertyName != null && 579 (esave.get(versionPropertyName) != dataManager.getVersion(owner) 580 && !(esave.get(versionPropertyName) == null && dataManager.getVersion(owner) == null)))) { 581 582 esave = new HashMap<String, Object>(); 583 if (versionPropertyName != null) 584 esave.put(versionPropertyName, dataManager.getVersion(owner)); 585 savedProperties.put(owner, esave); 586 if (unsaved) 587 dirtyCount++; 588 } 589 590 List<Object> save = (List<Object>)esave.get(propName); 591 if (save == null) { 592 save = new ArrayList<Object>(); 593 esave.put(propName, save); 594 595 // Save collection snapshot 596 for (Object e : coll) 597 save.add(e); 598 599 // Adjust with last event 600 if (kind == ChangeKind.ADD) { 601 if (location != null) { 602 for (int i = 0; i < items.length; i++) 603 save.remove(location.intValue()); 604 } 605 else { 606 for (Object item : items) 607 save.remove(item); 608 } 609 } 610 else if (kind == ChangeKind.REMOVE) { 611 if (location != null) { 612 for (int i = 0; i < items.length; i++) 613 save.add(location.intValue()+i, items[i]); 614 } 615 else { 616 for (Object item : items) 617 save.add(item); 618 } 619 } 620 else if (kind == ChangeKind.REPLACE) { 621 if (location != null) 622 save.set(location.intValue(), ((Object[])items[0])[0]); 623 else { 624 save.remove(((Object[])items[0])[1]); 625 save.add(((Object[])items[0])[0]); 626 } 627 } 628 } 629 else { 630 if (isSameList(save, coll)) { 631 esave.remove(propName); 632 int count = 0; 633 for (Object p : esave.keySet()) { 634 if (!p.equals(versionPropertyName)) 635 count++; 636 } 637 if (count == 0) { 638 savedProperties.remove(owner); 639 dirtyCount--; 640 } 641 } 642 } 643 644 notifyEntityDirtyChange(owner, oldDirtyEntity); 645 646 notifyDirtyChange(oldDirty); 647 } 648 649 650 /** 651 * Map event handler to save changes on managed maps 652 * 653 * @param owner owner entity of the map 654 * @param propName property name of the map 655 * @param map map 656 * @param kind change kind 657 * @param items changed items 658 */ 659 @SuppressWarnings("unchecked") 660 public void entityMapChangeHandler(Object owner, String propName, Map<?, ?> map, ChangeKind kind, Object[] items) { 661 boolean oldDirty = isDirty(); 662 663 String versionPropertyName = dataManager.isEntity(owner) ? dataManager.getVersionPropertyName(owner) : null; 664 boolean oldDirtyEntity = isEntityChanged(owner); 665 666 Map<String, Object> esave = savedProperties.get(owner); 667 boolean unsaved = esave == null; 668 669 if (unsaved || (versionPropertyName != null && 670 (esave.get(versionPropertyName) != dataManager.getVersion(owner) 671 && !(esave.get(versionPropertyName) == null && dataManager.getVersion(owner) == null)))) { 672 673 esave = new HashMap<String, Object>(); 674 if (versionPropertyName != null) 675 esave.put(versionPropertyName, dataManager.getVersion(owner)); 676 savedProperties.put(owner, esave); 677 if (unsaved) 678 dirtyCount++; 679 } 680 681 List<Object[]> save = (List<Object[]>)esave.get(propName); 682 if (save == null) { 683 save = new ArrayList<Object[]>(); 684 esave.put(propName, save); 685 686 // Save map snapshot 687 for (Entry<?, ?> entry : map.entrySet()) { 688 boolean found = false; 689 if (kind == ChangeKind.ADD) { 690 for (Object item : items) { 691 if (isSame(entry.getKey(), ((Object[])item)[0])) { 692 found = true; 693 break; 694 } 695 } 696 } 697 else if (kind == ChangeKind.REPLACE) { 698 for (Object item : items) { 699 if (isSame(entry.getKey(), ((Object[])item)[0])) { 700 save.add(new Object[] { entry.getKey(), ((Object[])item)[1] }); 701 found = true; 702 break; 703 } 704 } 705 } 706 if (!found) 707 save.add(new Object[] { entry.getKey(), entry.getValue() }); 708 } 709 710 // Add removed element if needed 711 if (kind == ChangeKind.REMOVE) { 712 for (Object item : items) 713 save.add(new Object[] { ((Object[])item)[0], ((Object[])item)[1] }); 714 } 715 } 716 else { 717 if (isSameMap(save, map)) { 718 esave.remove(propName); 719 int count = 0; 720 for (Object p : esave.keySet()) { 721 if (!p.equals(versionPropertyName)) 722 count++; 723 } 724 if (count == 0) { 725 savedProperties.remove(owner); 726 dirtyCount--; 727 } 728 } 729 } 730 731 notifyEntityDirtyChange(owner, oldDirtyEntity); 732 733 notifyDirtyChange(oldDirty); 734 } 735 736 737 /** 738 * Mark an object merged from the server as not dirty 739 * 740 * @param object merged object 741 * @param entity owner entity (when handling embedded objects) 742 */ 743 public void markNotDirty(Object object, Object entity) { 744 if (entity != null) 745 unsavedEntities.remove(entity); 746 747 if (!savedProperties.containsKey(object)) 748 return; 749 750 boolean oldDirty = isDirty(); 751 752 boolean oldDirtyEntity = false; 753 if (entity == null && isEntity(object)) 754 entity = object; 755 if (entity != null) 756 oldDirtyEntity = isEntityChanged(entity); 757 758 savedProperties.remove(object); 759 760 if (entity != null) 761 notifyEntityDirtyChange(entity, oldDirtyEntity); 762 763 dirtyCount--; 764 765 notifyDirtyChange(oldDirty); 766 } 767 768 769 /** 770 * Check if dirty properties of an object are the same than those of another entity 771 * When they are the same, unmark the dirty flag 772 * 773 * @param mergeContext current merge context 774 * @param entity merged entity 775 * @param source source entity 776 * @param parent owner entity for embedded objects 777 * @return true if the entity is still dirty after comparing with incoming object 778 */ 779 public boolean checkAndMarkNotDirty(MergeContext mergeContext, Object entity, Object source, Object parent) { 780 if (entity != null) 781 unsavedEntities.remove(entity); 782 783 Map<String, Object> save = savedProperties.get(entity); 784 if (save == null) 785 return false; 786 787 Object owner = isEntity(entity) ? entity : parent; 788 789 boolean oldDirty = isDirty(); 790 boolean oldDirtyEntity = isEntityChanged(owner); 791 792 List<String> merged = new ArrayList<String>(); 793 794 String versionPropertyName = dataManager.getVersionPropertyName(owner); 795 796 if (isEntity(source) && versionPropertyName != null) 797 save.put(versionPropertyName, dataManager.getVersion(source)); 798 799 Map<String, Object> pval = dataManager.getPropertyValues(entity, false, false); 800 for (String propName : pval.keySet()) { 801 if (propName.equals(versionPropertyName) || propName.equals("dirty")) 802 continue; 803 804// if (!source.hasOwnProperty(propName)) 805// continue; 806 807 Object localValue = pval.get(propName); 808 if (localValue instanceof PropertyHolder) 809 localValue = ((PropertyHolder)localValue).getObject(); 810 811 Object sourceValue = dataManager.getPropertyValue(source, propName); 812 813 if (isSameExt(sourceValue, localValue)) { 814 merged.add(propName); 815 continue; 816 } 817 818 if (sourceValue == null || ObjectUtil.isSimple(sourceValue) || sourceValue instanceof Value || sourceValue instanceof Enum) { 819 save.put(propName, sourceValue); 820 } 821 else if (isEntity(sourceValue)) { 822 save.put(propName, mergeContext.getFromCache(sourceValue)); 823 } 824 else if (sourceValue instanceof Collection<?> && !(sourceValue instanceof PersistentCollection && !((PersistentCollection)sourceValue).wasInitialized())) { 825 List<Object> snapshot = new ArrayList<Object>((Collection<?>)sourceValue); 826 save.put(propName, snapshot); 827 } 828 else if (sourceValue instanceof Map<?, ?> && !(sourceValue instanceof PersistentCollection && !((PersistentCollection)sourceValue).wasInitialized())) { 829 Map<?, ?> map = (Map<?, ?>)sourceValue; 830 List<Object[]> snapshot = new ArrayList<Object[]>(map.size()); 831 for (Entry<?, ?> entry : map.entrySet()) 832 snapshot.add(new Object[] { entry.getKey(), entry.getValue() }); 833 save.put(propName, snapshot); 834 } 835 } 836 837 for (String propName : merged) 838 save.remove(propName); 839 840 int count = 0; 841 for (String propName : save.keySet()) { 842 if (!propName.equals(versionPropertyName)) 843 count++; 844 } 845 if (count == 0) { 846 savedProperties.remove(entity); 847 dirtyCount--; 848 } 849 850 boolean newDirtyEntity = notifyEntityDirtyChange(owner, oldDirtyEntity); 851 852 notifyDirtyChange(oldDirty); 853 854 return newDirtyEntity; 855 } 856 857 858 public void fixRemovalsAndPersists(MergeContext mergeContext, List<Object> removals, List<Object> persists) { 859 boolean oldDirty = dirtyCount > 0; 860 861 for (Object object : savedProperties.keySet()) { 862 Object owner = null; 863 if (isEntity(object)) 864 owner = object; 865 else { 866 Object[] ownerEntity = mergeContext.getOwnerEntity(object); 867 if (ownerEntity != null && isEntity(ownerEntity[0])) 868 owner = ownerEntity[0]; 869 } 870 871 String versionPropertyName = dataManager.getVersionPropertyName(owner); 872 873 boolean oldDirtyEntity = isEntityChanged(object); 874 875 Map<String, Object> save = savedProperties.get(object); 876 877 Iterator<String> ip = save.keySet().iterator(); 878 while (ip.hasNext()) { 879 String p = ip.next(); 880 Object sn = save.get(p); 881 if (!(sn instanceof List<?>)) 882 continue; 883 884 Object value = dataManager.getPropertyValue(object, p); 885 if (value instanceof Collection<?>) { 886 @SuppressWarnings("unchecked") 887 List<Object> snapshot = (List<Object>)sn; 888 Collection<?> coll = (Collection<?>)value; 889 if (removals != null) { 890 Iterator<Object> isne = snapshot.iterator(); 891 while (isne.hasNext()) { 892 Object sne = isne.next(); 893 for (Object removal : removals) { 894 if (isEntity(sne) && ObjectUtil.objectEquals(dataManager, sne, removal)) 895 isne.remove(); 896 } 897 } 898 } 899 if (persists != null) { 900 for (Object persist : persists) { 901 if (coll instanceof List<?>) { 902 List<?> list = (List<?>)coll; 903 List<Integer> found = new ArrayList<Integer>(); 904 for (int j = 0; j < list.size(); j++) { 905 if (ObjectUtil.objectEquals(dataManager, list.get(j), persist)) 906 found.add(j); 907 } 908 for (int j = 0; j < snapshot.size(); j++) { 909 if (ObjectUtil.objectEquals(dataManager, persist, snapshot.get(j))) { 910 snapshot.remove(j); 911 j--; 912 } 913 } 914 for (int idx : found) 915 snapshot.add(idx, persist); 916 } 917 else { 918 if (coll.contains(persist) && !snapshot.contains(persist)) 919 snapshot.add(persist); 920 } 921 } 922 } 923 924 if (isSameList(snapshot, coll)) 925 ip.remove(); 926 } 927 else if (value instanceof Map<?, ?>) { 928 @SuppressWarnings("unchecked") 929 List<Object[]> snapshot = (List<Object[]>)sn; 930 Map<?, ?> map = (Map<?, ?>)value; 931 if (removals != null) { 932 Iterator<Object[]> isne = snapshot.iterator(); 933 while (isne.hasNext()) { 934 Object[] sne = isne.next(); 935 for (Object removal : removals) { 936 if (isEntity(sne[0]) && ObjectUtil.objectEquals(dataManager, sne[0], removal)) 937 isne.remove(); 938 else if (isEntity(sne[1]) && ObjectUtil.objectEquals(dataManager, sne[1], removal)) 939 isne.remove(); 940 } 941 } 942 } 943 // TODO: persist ? 944// if (persists != null) { 945// for (Object persist : persists) { 946// boolean foundKey = false; 947// List<Object> foundValues = new ArrayList<Object>(); 948// Iterator<Object[]> isne = snapshot.iterator(); 949// while (isne.hasNext()) { 950// Object[] sne = isne.next(); 951// if (ObjectUtil.objectEquals(dataManager, sne[0], persist)) 952// foundKey = true; 953// if (ObjectUtil.objectEquals(dataManager, sne[1], persist)) { 954// foundValues.add(sne[0]); 955// isne.remove(); 956// } 957// } 958// if (map.containsKey(persist) && !foundKey) 959// snapshot.add(new Object[] { persist, map.get(persist) }); 960// if (map.containsValue(persist)) { 961// for (Entry<?, ?> e : map.entrySet()) { 962// if (e.getValue().equals(persist) && !e.getKey().equals(persist)) 963// snapshot.add(new Object[] { e.getKey(), e.getValue() }); 964// } 965// } 966// } 967// } 968 969 if (isSameMap(snapshot, map)) 970 ip.remove(); 971 } 972 } 973 974 int count = 0; 975 for (Object p : save.keySet()) { 976 if (!p.equals(versionPropertyName)) 977 count++; 978 } 979 if (count == 0) { 980 savedProperties.remove(object); 981 dirtyCount--; 982 } 983 984 notifyEntityDirtyChange(object, oldDirtyEntity); 985 } 986 987 notifyDirtyChange(oldDirty); 988 } 989 990 991 /** 992 * Internal implementation of entity reset 993 */ 994 @SuppressWarnings("unchecked") 995 public void resetEntity(MergeContext mergeContext, Object entity, Object parent, Set<Object> cache) { 996 // Should not try to reset uninitialized entities 997 if (!dataManager.isInitialized(entity)) 998 return; 999 1000 if (cache.contains(entity)) 1001 return; 1002 cache.add(entity); 1003 1004 Map<String, Object> save = savedProperties.get(entity); 1005 1006 Map<String, Object> pval = dataManager.getPropertyValues(entity, true, false); 1007 1008 for (String p : pval.keySet()) { 1009 Object val = pval.get(p); 1010 1011 if (val instanceof Collection<?> && dataManager.isInitialized(val)) { 1012 Collection<Object> coll = (Collection<Object>)val; 1013 List<Object> savedArray = save != null ? (List<Object>)save.get(p) : null; 1014 1015 if (savedArray != null) { 1016 for (Object obj : coll) { 1017 if (isEntity(obj)) 1018 resetEntity(mergeContext, obj, parent, cache); 1019 } 1020 coll.clear(); 1021 for (Object e : savedArray) 1022 coll.add(e); 1023 1024 // Must be here because collection reset may have triggered other useless collection change events 1025 markNotDirty(val, parent); 1026 } 1027 1028 for (Object o : coll) { 1029 if (isEntity(o)) 1030 resetEntity(mergeContext, o, o, cache); 1031 } 1032 } 1033 else if (val instanceof Map<?, ?> && dataManager.isInitialized(val)) { 1034 Map<Object, Object> map = (Map<Object, Object>)val; 1035 List<Object[]> savedArray = save != null ? (List<Object[]>)save.get(p) : null; 1036 1037 if (savedArray != null) { 1038 for (Entry<Object, Object> entry : map.entrySet()) { 1039 if (isEntity(entry.getKey())) 1040 resetEntity(mergeContext, entry.getKey(), parent, cache); 1041 if (isEntity(entry.getValue())) 1042 resetEntity(mergeContext, entry.getValue(), parent, cache); 1043 } 1044 map.clear(); 1045 for (Object[] e : savedArray) 1046 map.put(e[0], e[1]); 1047 1048 // Must be here because collection reset has triggered other useless CollectionEvents 1049 markNotDirty(val, parent); 1050 } 1051 1052 for (Entry<Object, Object> me : map.entrySet()) { 1053 if (isEntity(me.getKey())) 1054 resetEntity(mergeContext, me.getKey(), me.getKey(), cache); 1055 if (isEntity(me.getValue())) 1056 resetEntity(mergeContext, me.getValue(), me.getValue(), cache); 1057 } 1058 } 1059 else if (save != null && (ObjectUtil.isSimple(val) || ObjectUtil.isSimple(save.get(p)))) { 1060 if (save.containsKey(p)) 1061 dataManager.setPropertyValue(entity, p, save.get(p)); 1062 } 1063 else if (save != null && (val instanceof Enum || save.get(p) instanceof Enum || val instanceof Value || save.get(p) instanceof Value)) { 1064 if (save.containsKey(p)) 1065 dataManager.setPropertyValue(entity, p, save.get(p)); 1066 } 1067 else if (save != null && save.containsKey(p)) { 1068 if (!ObjectUtil.objectEquals(dataManager, val, save.get(p))) 1069 dataManager.setPropertyValue(entity, p, save.get(p)); 1070 } 1071 else if (isEntity(val)) 1072 resetEntity(mergeContext, val, val, cache); 1073 else if (val != null && parent != null && !ObjectUtil.isSimple(val) && !(val instanceof Enum)) 1074 resetEntity(mergeContext, val, parent, cache); 1075 } 1076 1077 // Must be here because entity reset may have triggered useless new saved properties 1078 markNotDirty(entity, null); 1079 } 1080 1081 1082 /** 1083 * Internal implementation of entity reset all 1084 * 1085 * @param mergeContext current merge context 1086 * @param cache graph traversal cache 1087 */ 1088 public void resetAllEntities(MergeContext mergeContext, Set<Object> cache) { 1089 boolean found = false; 1090 do { 1091 found = false; 1092 for (Object entity : savedProperties.keySet()) { 1093 if (isEntity(entity)) { 1094 found = true; 1095 resetEntity(mergeContext, entity, entity, cache); 1096 break; 1097 } 1098 } 1099 } 1100 while (found); 1101 1102 if (dirtyCount > 0) 1103 log.error("Incomplete reset of context, could be a bug"); 1104 } 1105 1106 1107 /** 1108 * Check if a value is empty 1109 * 1110 * @param val value 1111 * @return value is empty 1112 */ 1113 public boolean isEmpty(Object val) { 1114 if (val == null) 1115 return true; 1116 else if (val instanceof String) 1117 return val.equals(""); 1118 else if (val.getClass().isArray()) 1119 return Array.getLength(val) == 0; 1120 else if (val instanceof Date) 1121 return ((Date)val).getTime() == 0L; 1122 else if (val instanceof Collection<?>) 1123 return ((Collection<?>)val).size() == 0; 1124 else if (val instanceof Map<?, ?>) 1125 return ((Map<?, ?>)val).size() == 0; 1126 return false; 1127 } 1128 1129 1130 public static class Change { 1131 1132 private ChangeKind kind; 1133 private int location; 1134 private Object[] items; 1135 1136 public Change(ChangeKind kind, int location, Object[] items) { 1137 this.kind = kind; 1138 this.location = location; 1139 this.items = items; 1140 } 1141 1142 public ChangeKind getKind() { 1143 return kind; 1144 } 1145 1146 public int getLocation() { 1147 return location; 1148 } 1149 1150 public Object[] getItems() { 1151 return items; 1152 } 1153 1154 public void moveLocation(int offset) { 1155 location += offset; 1156 } 1157 } 1158}