package ch.x42.terye.persistence; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import javax.jcr.RepositoryException; import ch.x42.terye.ItemImpl; import ch.x42.terye.path.Path; import ch.x42.terye.path.PathFactory; import ch.x42.terye.persistence.id.ItemId; public class ChangeLog { private Map<ItemId, ItemState> addedStates; private Map<ItemId, ItemState> modifiedStates; private Map<ItemId, ItemState> removedStates; // additionally keep track of moved states (the source of a move will be // present in removedStates, the destination in addedStates also) private Map<ItemId, ItemState> movedSrcStates; private Map<ItemId, ItemState> movedDestStates; public ChangeLog() { addedStates = new LinkedHashMap<ItemId, ItemState>(); modifiedStates = new LinkedHashMap<ItemId, ItemState>(); removedStates = new LinkedHashMap<ItemId, ItemState>(); movedSrcStates = new LinkedHashMap<ItemId, ItemState>(); movedDestStates = new LinkedHashMap<ItemId, ItemState>(); } private ChangeLog(Map<ItemId, ItemState> addedStates, Map<ItemId, ItemState> modifiedStates, Map<ItemId, ItemState> removedStates, Map<ItemId, ItemState> movedSrcStates, Map<ItemId, ItemState> movedDestStates) { this.addedStates = addedStates; this.modifiedStates = modifiedStates; this.removedStates = removedStates; this.movedSrcStates = movedSrcStates; this.movedDestStates = movedDestStates; } /** * This method creates and returns a new change log containing all changes * that concern item states that are at path 'root' or below. The current * change log will keep all remaining changes. */ public ChangeLog split(Path root) throws RepositoryException { Map<ItemId, ItemState> newAddedStates = new LinkedHashMap<ItemId, ItemState>(); Map<ItemId, ItemState> newModifiedStates = new LinkedHashMap<ItemId, ItemState>(); Map<ItemId, ItemState> newRemovedStates = new LinkedHashMap<ItemId, ItemState>(); // move concerned added states to new change log Iterator<Entry<ItemId, ItemState>> iterator = addedStates.entrySet() .iterator(); while (iterator.hasNext()) { Entry<ItemId, ItemState> entry = iterator.next(); Path path = PathFactory.create(entry.getValue().getPath()); if (path.isEquivalentTo(root) || path.isDescendantOf(root)) { newAddedStates.put(entry.getKey(), entry.getValue()); iterator.remove(); } } // move concerned modified states to new change log iterator = modifiedStates.entrySet().iterator(); while (iterator.hasNext()) { Entry<ItemId, ItemState> entry = iterator.next(); Path path = PathFactory.create(entry.getValue().getPath()); if (path.isEquivalentTo(root) || path.isDescendantOf(root)) { newModifiedStates.put(entry.getKey(), entry.getValue()); iterator.remove(); } } // move concerned removed states to new change log iterator = removedStates.entrySet().iterator(); while (iterator.hasNext()) { Entry<ItemId, ItemState> entry = iterator.next(); Path path = PathFactory.create(entry.getValue().getPath()); if (path.isEquivalentTo(root) || path.isDescendantOf(root)) { newRemovedStates.put(entry.getKey(), entry.getValue()); iterator.remove(); } } // XXX: not correct but ignore conflict cases Map<ItemId, ItemState> newMovedSrcStates = new LinkedHashMap<ItemId, ItemState>( movedSrcStates); Map<ItemId, ItemState> newMovedDestStates = new LinkedHashMap<ItemId, ItemState>( movedDestStates); return new ChangeLog(newAddedStates, newModifiedStates, newRemovedStates, newMovedSrcStates, newMovedDestStates); } public void added(ItemImpl item) { addedStates.put(item.getId(), item.getState()); } public void modified(ItemImpl item) { if (!addedStates.containsKey(item.getId())) { modifiedStates.put(item.getId(), item.getState()); } } public void removed(ItemImpl item) { if (addedStates.remove(item.getId()) == null) { modifiedStates.remove(item.getId()); removedStates.put(item.getId(), item.getState()); } } public void moved(ItemState src, ItemState dest) { movedSrcStates.put(src.getId(), src); movedDestStates.put(dest.getId(), dest); } public Collection<ItemState> getAddedStates() { return addedStates.values(); } public Collection<ItemState> getModifiedStates() { return modifiedStates.values(); } public Collection<ItemState> getRemovedStates() { return removedStates.values(); } public Collection<ItemState> getMovedSrcStates() { return movedSrcStates.values(); } public Collection<ItemState> getMovedDestStates() { return movedDestStates.values(); } /** * This method returns the minimal subset of the removed states, such that * every removed state is either part of the returned set or a descendant of * one of the states in the returned set. */ public List<ItemState> getMinimalRemovedStates() throws RepositoryException { // states contains all removed states List<ItemState> states = new LinkedList<ItemState>( removedStates.values()); // sort states by increasing depth Collections.sort(states, new Comparator<ItemState>() { @Override public int compare(ItemState s1, ItemState s2) { Path p1 = PathFactory.create(s1.getPath()); Path p2 = PathFactory.create(s2.getPath()); if (p1.getDepth() < p2.getDepth()) { return -1; } else if (p1.getDepth() == p2.getDepth()) { return 0; } return 1; } }); // start with an empty list of minimal roots List<ItemState> roots = new LinkedList<ItemState>(); // loop through all states for (ItemState state : states) { boolean covered = false; // loop through all minimal roots for (ItemState root : roots) { // if this state is a descendant of one of the minimal roots, we // can discard it Path statePath = PathFactory.create(state.getPath()); Path rootPath = PathFactory.create(root.getPath()); if (statePath.isDescendantOf(rootPath)) { covered = true; break; } } // this state is not a descendant of one of the minimal roots, add // to minimal roots if (!covered) { roots.add(state); } } return roots; } public boolean isEmpty() { return (addedStates.isEmpty() && modifiedStates.isEmpty() && removedStates .isEmpty()); } public void purge() { addedStates.clear(); modifiedStates.clear(); removedStates.clear(); movedSrcStates.clear(); movedDestStates.clear(); } }