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