/* * Copyright (C) 2011 eXo Platform SAS. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.exoplatform.portal.tree.diff; import java.util.Iterator; import java.util.NoSuchElementException; /** * @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a> */ public class HierarchyChangeIterator<L1, N1, L2, N2, H> implements Iterator<HierarchyChangeType> { /** . */ private final HierarchyDiff<L1, N1, L2, N2, H> diff; /** . */ private Frame frame; /** . */ private final HierarchyContext<L1, N1, H> context1; /** . */ private final HierarchyContext<L2, N2, H> context2; /** . */ private final ListDiff<L1, L2, H> listDiff; HierarchyChangeIterator(HierarchyDiff<L1, N1, L2, N2, H> diff, HierarchyContext<L1, N1, H> context1, HierarchyContext<L2, N2, H> context2) { this.diff = diff; this.context1 = context1; this.context2 = context2; this.frame = new Frame(null, context1.getRoot(), context2.getRoot()); this.listDiff = new ListDiff<L1, L2, H>(diff.listAdapter1, diff.listAdapter2, diff.comparator); } /** * The internal status. */ private enum Status { INIT(null), ENTER(HierarchyChangeType.ENTER), KEEP(HierarchyChangeType.KEEP), ADDED(HierarchyChangeType.ADDED), REMOVED(HierarchyChangeType.REMOVED), MOVED_IN(HierarchyChangeType.MOVED_IN), MOVED_OUT(HierarchyChangeType.MOVED_OUT), LEAVE(HierarchyChangeType.LEAVE), ERROR(HierarchyChangeType.ERROR), RESUME(null); /** The associated change type. */ final HierarchyChangeType changeType; private Status(HierarchyChangeType changeType) { this.changeType = changeType; } } private class Frame { /** . */ private final Frame parent; /** . */ private final N1 srcRoot; /** . */ private final N2 dstRoot; /** . */ private ListChangeIterator<L1, L2, H> it; /** . */ private Status previous; /** . */ private Status next; /** . */ private Iterator<H> srcIt; /** . */ private Iterator<H> dstIt; /** . */ private N1 src; /** . */ private N2 dst; private Frame(Frame parent, N1 srcRoot, N2 dstRoot) { this.parent = parent; this.srcRoot = srcRoot; this.dstRoot = dstRoot; this.previous = Status.INIT; } } public boolean hasNext() { if (frame != null && frame.next == null) { while (true) { if (frame.previous == Status.INIT) { H id2 = context2.getHierarchyAdapter().getHandle(frame.dstRoot); if (frame.srcRoot == null) { frame.next = Status.ENTER; frame.src = null; frame.dst = frame.dstRoot; } else { H id1 = context1.getHierarchyAdapter().getHandle(frame.srcRoot); if (diff.comparator.compare(id1, id2) != 0) { frame.next = Status.ERROR; frame.src = frame.srcRoot; frame.dst = frame.dstRoot; } else { frame.next = Status.ENTER; frame.src = frame.srcRoot; frame.dst = frame.dstRoot; } } break; } else if (frame.previous == Status.ERROR) { break; } else if (frame.previous == Status.LEAVE) { frame = frame.parent; if (frame != null) { frame.previous = Status.RESUME; continue; } else { break; } } else if (frame.previous == Status.KEEP) { frame = new Frame(frame, frame.src, frame.dst); continue; } else if (frame.previous == Status.MOVED_IN) { frame = new Frame(frame, frame.src, frame.dst); continue; } else if (frame.previous == Status.ADDED) { frame = new Frame(frame, frame.src, frame.dst); continue; } else if (frame.previous == Status.ENTER) { L1 children1; if (frame.src != null) { children1 = context1.getHierarchyAdapter().getChildren(frame.srcRoot); frame.srcIt = diff.listAdapter1.iterator(children1, false); } else { children1 = null; frame.srcIt = null; } L2 children2 = context2.getHierarchyAdapter().getChildren(frame.dstRoot); frame.dstIt = diff.listAdapter2.iterator(children2, false); frame.it = listDiff.iterator(children1, children2); } else { // Nothing } // if (frame.it.hasNext()) { switch (frame.it.next()) { case SAME: N1 next1 = context1.findByHandle(frame.srcIt.next()); N2 next2 = context2.findByHandle(frame.dstIt.next()); frame.next = Status.KEEP; frame.src = next1; frame.dst = next2; break; case ADD: frame.dstIt.next(); H addedHandle = frame.it.getElement(); N2 added = context2.findByHandle(addedHandle); H addedId = context2.getHierarchyAdapter().getHandle(added); N1 a = context1.findByHandle(addedId); if (a != null) { frame.next = Status.MOVED_IN; frame.src = a; frame.dst = added; } else { frame.next = Status.ADDED; frame.src = null; frame.dst = added; } break; case REMOVE: frame.srcIt.next(); H removedHandle = frame.it.getElement(); N1 removed = context1.findByHandle(removedHandle); H removedId = context1.getHierarchyAdapter().getHandle(removed); N2 b = context2.findByHandle(removedId); if (b != null) { frame.next = Status.MOVED_OUT; frame.src = removed; frame.dst = b; } else { frame.next = Status.REMOVED; frame.src = removed; frame.dst = null; } break; default: throw new AssertionError(); } } else { frame.next = Status.LEAVE; frame.src = frame.srcRoot; frame.dst = frame.dstRoot; } // break; } } return frame != null && frame.next != null; } public HierarchyChangeType next() { if (!hasNext()) { throw new NoSuchElementException(); } else { frame.previous = frame.next; frame.next = null; return frame.previous.changeType; } } public void skip() { if (frame.previous == HierarchyChangeIterator.Status.ENTER) { // A bit hackish as it bypass the main loop // the proper way to do it would be to introduce a SKIP status // and properly react to it to update the state machine // but for now it will do frame.next = Status.LEAVE; frame.src = frame.srcRoot; frame.dst = frame.dstRoot; } else { throw new IllegalStateException("Cannot skip when in state " + frame.previous); } } public N1 getSource() { return frame.src; } public N2 getDestination() { return frame.dst; } public N1 peekSourceRoot() { return frame.srcRoot; } public N2 peekDestinationRoot() { return frame.dstRoot; } public void remove() { throw new UnsupportedOperationException(); } }