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}