package com.almworks.jira.structure.api.forest;
import com.almworks.integers.LongArray;
import com.almworks.integers.LongList;
import com.almworks.integers.util.LongListConcatenation;
import com.almworks.jira.structure.api.StructureManager;
import org.jetbrains.annotations.NotNull;
import static com.almworks.jira.structure.util.StructureUtil.lastOrZero;
/**
* <p>A <code>ForestOp</code> represents a single operation on a forest, such as move or delete.
* A list of <code>ForestOp</code>s in the {@link ForestUpdate.Incremental} define a sequence
* of operations that bring a forest from one version to another, newer version.
* </p>
*
* <p>There are several types of the operations, all defined as the inner classes of <code>ForestOp:</code></p>
*
* <ul>
* <li>{@link ForestOp.Add} - add operation;</li>
* <li>{@link ForestOp.Remove} - remove operation;</li>
* <li>{@link ForestOp.Move} - move operation.</li>
* </ul>
*
* <p>Typically, a client code would inspect the exact type of each <code>ForestOp</code>
* and perform the required action to bring their forest to the up-to-date state.
* </p>
*
* @see ForestUpdate
* @see StructureManager#getForestUpdate
* @author Igor Sereda
*/
public abstract class ForestOp {
private final LongList myParents;
private final LongList myAncestors;
protected ForestOp(LongList parents, LongList ancestors) {
assert parents != null;
assert ancestors != null;
myParents = parents;
myAncestors = ancestors;
}
/**
* <p>This method returns the list of issues that were subject to the operation, or possibly
* the reduced set of these issues if other affected issues can be implied from the returned
* list and the current forest state.</p>
*
* <p>Namely, {@link ForestOp.Move} returns only the top
* issue in this list because all other issues moved can be figured
* from the state after the operation. If it wasn't the last operation then this
* holds true transitively via other operations.</p>
*
* @return a list of issues affected by this operation
*/
@NotNull
public abstract LongList getAffectedIssues();
/**
* @return a list of issues that are parents or used to be parents of the affected issues,
* may contain issues from the affected issues set if one issue was (is) a parent of another
*/
@NotNull
public LongList getParents() {
return myParents;
}
/**
* @return a list of all ancestors of the affected issues,
* may contain issues from the affected issues set if one issue was (is) a parent of another
*/
public LongList getAncestors() {
return myAncestors;
}
/**
* @return issues that are used to define this operation - used to check whether the operation
* is visible to a user
*/
@NotNull
public abstract LongList getAnchorIssues();
/**
* Represents move operation.
*
* @see ForestOp
* @see ForestAccessor#moveSubtree
* @see ForestAccessor#mergeForest
*/
public static class Move extends ForestOp {
private final long myIssue;
private final Forest myMovedSubtree;
private final LongList myParentPathFrom;
private final long myAfterFrom;
private final LongList myParentPathTo;
private final long myAfterTo;
private final int myDirection;
private final LongArray myAnchor = new LongArray(3);
public Move(
long issue, Forest movedSubtree, LongList parentPathFrom, long afterFrom,
LongList parentPathTo, long afterTo, int direction, LongList parents, LongList ancestors)
{
super(parents, ancestors);
myIssue = issue;
myMovedSubtree = movedSubtree.makeImmutable();
myParentPathFrom = parentPathFrom;
myAfterFrom = afterFrom;
myParentPathTo = parentPathTo;
myAfterTo = afterTo;
myDirection = direction;
long under = lastOrZero(parentPathTo);
myAnchor.add(issue);
if (under > 0) myAnchor.add(under);
if (myAfterTo > 0) myAnchor.add(myAfterTo);
}
@NotNull
public LongList getAffectedIssues() {
return LongArray.create(myIssue);
}
@NotNull
public LongList getAnchorIssues() {
return myAnchor;
}
/**
* @return The new parent of the moved issue, 0 if top level.
*/
public long getUnder() {
return lastOrZero(myParentPathTo);
}
/**
* @return The new immediately preceding sibling of the moved issue, 0 if none.
*/
public long getAfter() {
return myAfterTo;
}
/**
* @return The moved issue.
*/
public long getIssue() {
return myIssue;
}
/**
* @return The whole moved subtree rooted at {@link #getIssue()}.
*/
@NotNull
public Forest getMovedSubtree() {
return myMovedSubtree;
}
/**
* @return The old parent path of the moved issue.
* @see Forest#getParentPathForIndex(int)
*/
@NotNull
public LongList getParentPathFrom() {
return myParentPathFrom;
}
/**
* @return The old immediately preceding sibling of the moved issue, 0 if none.
*/
public long getAfterFrom() {
return myAfterFrom;
}
/**
* @return The new parent path of the moved issue.
* @see Forest#getParentPathForIndex(int)
*/
@NotNull
public LongList getParentPathTo() {
return myParentPathTo;
}
/**
* @return Positive if the issue has been moved downwards; negative if upwards.
*/
public int getDirection() {
return myDirection;
}
public String toString() {
return "move(" + myParentPathFrom + "/" + myAfterFrom + "=>" + myParentPathTo + "/" + myAfterTo + "," + myMovedSubtree + ")";
}
}
/**
* Represents the add operation.
*
* @see ForestOp
* @see ForestAccessor#mergeForest
* @see ForestAccessor#addIssue
*/
public static class Add extends ForestOp {
private final Forest myForest;
private final LongList myParentPath;
private final long myAfter;
private final LongList myAnchor;
public Add(Forest forest, LongList parentPath, long after, LongList parents, LongList ancestors) {
super(parents, ancestors);
assert forest.size() > 0 : forest + " " + parentPath + " " + after;
myForest = forest.makeImmutable();
myParentPath = parentPath;
myAfter = after;
long under = lastOrZero(parentPath);
if (under > 0 || after > 0) {
LongArray array = new LongArray();
if (under > 0) array.add(under);
if (after > 0) array.add(after);
myAnchor = new LongListConcatenation(array, myForest.getIssues());
} else {
myAnchor = myForest.getIssues();
}
}
@NotNull
public LongList getAffectedIssues() {
return myForest.getIssues();
}
@NotNull
public LongList getAnchorIssues() {
return myAnchor;
}
/**
* @return The added forest.
*/
@NotNull
public Forest getAddedForest() {
return myForest;
}
/**
* @return The parent path of the (roots of the) added forest.
* @see Forest#getParentPathForIndex(int)
*/
@NotNull
public LongList getParentPath() {
return myParentPath;
}
/**
* @return The parent of the (roots of the) added forest, 0 if top level.
*/
public long getUnder() {
return lastOrZero(myParentPath);
}
/**
* @return The immediately preceding sibling of the (first root of the) added forest.
*/
public long getAfter() {
return myAfter;
}
public String toString() {
return "add(" + myParentPath + "/" + myAfter + "," + myForest + ")";
}
}
/**
* Represents the remove operation.
*
* @see ForestOp
* @see ForestAccessor#removeSubtree
*/
public static class Remove extends ForestOp {
private final long myIssue;
private final Forest myRemovedSubtree;
private final LongList myParentPath;
private final long myAfter;
public Remove(long issue, Forest removedSubtree, LongList parentPath, long after, LongList parents, LongList ancestors) {
super(parents, ancestors);
myIssue = issue;
myRemovedSubtree = removedSubtree;
myParentPath = parentPath;
myAfter = after;
assert removedSubtree.isEmpty() || removedSubtree.getIssues().get(0) == issue : issue + " " + removedSubtree;
}
/**
* @return The removed issue.
*/
public long getIssue() {
return myIssue;
}
/**
* @return The whole removed subtree rooted at {@link #getIssue()}.
*/
@NotNull
public Forest getRemovedSubtree() {
return myRemovedSubtree;
}
/**
* @return The parent path of the removed issue in the original forest.
* @see Forest#getParentPathForIndex(int)
*/
@NotNull
public LongList getParentPath() {
return myParentPath;
}
/**
* @return The immediately preceding sibling of the removed issue in the original forest, 0 if none.
*/
public long getAfter() {
return myAfter;
}
@NotNull
public LongList getAffectedIssues() {
return myRemovedSubtree.getIssues();
}
@NotNull
public LongList getAnchorIssues() {
return myRemovedSubtree.isEmpty() ? LongList.EMPTY : LongArray.create(myIssue);
}
public String toString() {
return "remove(" + myParentPath + "/" + myAfter + "," + myRemovedSubtree + ")";
}
}
}