/*
* ====================================================================
* Copyright (c) 2004-2012 TMate Software Ltd. All rights reserved.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at http://svnkit.com/license.html.
* If newer versions of this license are posted there, you may use a
* newer version instead, at your option.
* ====================================================================
*/
package org.tmatesoft.svn.core;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
import org.tmatesoft.svn.core.wc.SVNRevision;
import org.tmatesoft.svn.util.SVNLogType;
/**
* The <b>SVNMergeRangeList</b> represents an array of merge ranges applied to a single target.
* Provides addition functionality to operate with merge range lists.
*
* @version 1.3
* @author TMate Software Ltd.
* @since 1.2
*/
public class SVNMergeRangeList {
/**
* A string that is used in mergeinfo to mark the mergeinfo as being non-inheritable.
*/
public static String MERGE_INFO_NONINHERITABLE_STRING = "*";
private SVNMergeRange[] myRanges;
/**
* Creates a new merge range list initializing it with a single merge range which parameters are passed
* to this constructor.
*
* @param start merge range start revision
* @param end merge range end revision
* @param inheritable inheritance information
*/
public SVNMergeRangeList(long start, long end, boolean inheritable) {
this(new SVNMergeRange(start, end, inheritable));
}
/**
* Creates a new merge range list initializing it with the specified single merge range.
*
* @param range merge range
*/
public SVNMergeRangeList(SVNMergeRange range) {
this(new SVNMergeRange[] { range });
}
/**
* Creates a new merge range list initializing it with an array of merge ranges.
*
* <p/>
* Note: <code>ranges</code> are not copied to a separate array but stored immediately, as is.
*
* @param ranges merge ranges array
*/
public SVNMergeRangeList(SVNMergeRange[] ranges) {
myRanges = ranges == null ? new SVNMergeRange[0] : ranges;
}
/**
* Replaces the array of {@link SVNMergeRange} objects backed by this object
* with a new one.
*
* </p>
* This method was introduced because of purposes of convenience. Use this method
* with care as it changes the internal state of this <code>SVNMergeRangeList</code>
* object.
*
* @param ranges new merge ranges array
* @since 1.2.2
*/
public void setRanges(SVNMergeRange[] ranges) {
myRanges = ranges;
}
public void setInheritable(boolean inheritable) {
if (myRanges != null) {
for (int i = 0; i < myRanges.length; i++) {
myRanges[i].setInheritable(inheritable);
}
}
}
/**
* Returns an array of {@link SVNMergeRange} ranges backed by this merge range list object.
*
* <p/>
* Note: this method does not make a copy of the original array, instead it returns the original array
* itself. If you want a safe copy of merge ranges, use {@link #getRangesAsList()} instead.
*
* <p/>
* Note: merge ranges returned in the array are not copied.
*
* @return array of merge ranges
*/
public SVNMergeRange[] getRanges() {
return myRanges;
}
/**
* Returns a list of merge ranges backed by this merge range list.
*
* <p/>
* Note: ranges themselves are not copied but placed in the list as is.
*
* @return a new list instance containing all of the ranges stored in this merge range list
*/
public List<SVNMergeRange> getRangesAsList() {
List<SVNMergeRange> list = new ArrayList<SVNMergeRange>();
for (int i = 0; i < myRanges.length; i++) {
SVNMergeRange range = myRanges[i];
list.add(range);
}
return list;
}
/**
* Appends a new merge range to the end of the ranges list.
* A new {@link SVNMergeRange} is created used the parameters passed to this method.
*
* @param start merge range start revision
* @param end merge range end revision
* @param inheritable inheritance information
*/
public void pushRange(long start, long end, boolean inheritable) {
SVNMergeRange[] ranges = new SVNMergeRange[myRanges.length + 1];
ranges[ranges.length - 1] = new SVNMergeRange(start, end, inheritable);
System.arraycopy(myRanges, 0, ranges, 0, myRanges.length);
myRanges = ranges;
}
/**
* Returns number of merge ranges stored in this merge range list.
*
* @return number of merge ranges
*/
public int getSize() {
return myRanges.length;
}
/**
* Checks whether this merge range list has no merge ranges.
*
* @return <span class="javakeyword">true</span> if this merge range list is empty;
* otherwise <span class="javakeyword">false</span>
*/
public boolean isEmpty() {
return myRanges.length == 0;
}
/**
* Makes a copy of this merge range list. All merge ranges stored in this list will be copied
* to a new array which will be covered into a new <code>SVNMergeRangeList</code> instance.
*
* @return copy of this merge range list
*/
public SVNMergeRangeList dup() {
SVNMergeRange[] ranges = new SVNMergeRange[myRanges.length];
for (int i = 0; i < myRanges.length; i++) {
SVNMergeRange range = myRanges[i];
ranges[i] = range.dup();
}
return new SVNMergeRangeList(ranges);
}
/**
* Merges two range lists placing the results into a new {@link SVNMergeRangeList} object.
* Either range list may be empty.
*
* <p/>
* When intersecting range lists are merged, the inheritability of the resulting {@link SVNMergeRange}
* depends on the inheritability of the operands. If two non-inheritable ranges are merged the result is
* always non-inheritable, in all other cases the resulting range is inheritable.
*
* <p/>
* Note: range lists must be sorted in ascending order. The return range list is guaranteed to remain
* in sorted order and be compacted to the minimal number of ranges needed to represent the merged result.
*
* <p/>
* Note: this method does not change the state of this object. Instead it produces a result in a new object.
*
* @param rangeList range list to merge with
* @return resultant range list
* @throws SVNException
*/
public SVNMergeRangeList merge(SVNMergeRangeList rangeList) throws SVNException {
int i = 0;
int j = 0;
SVNMergeRange lastRange = null;
Collection resultRanges = new LinkedList();
while (i < myRanges.length && j < rangeList.myRanges.length) {
SVNMergeRange range1 = myRanges[i];
SVNMergeRange range2 = rangeList.myRanges[j];
int res = range1.compareTo(range2);
if (res == 0) {
if (range1.isInheritable() || range2.isInheritable()) {
range1.setInheritable(true);
}
lastRange = combineWithLastRange(resultRanges, lastRange, range1, true, false);
i++;
j++;
} else if (res < 0) {
lastRange = combineWithLastRange(resultRanges, lastRange, range1, true, false);
i++;
} else {
lastRange = combineWithLastRange(resultRanges, lastRange, range2, true, false);
j++;
}
}
SVNErrorManager.assertionFailure(i >= myRanges.length || j >= rangeList.myRanges.length, "expected to reach the end of at least one range list", SVNLogType.DEFAULT);
for (; i < myRanges.length; i++) {
SVNMergeRange range = myRanges[i];
lastRange = combineWithLastRange(resultRanges, lastRange, range, true, false);
}
for (; j < rangeList.myRanges.length; j++) {
SVNMergeRange range = rangeList.myRanges[j];
lastRange = combineWithLastRange(resultRanges, lastRange, range, true, false);
}
return SVNMergeRangeList.fromCollection(resultRanges);
}
/**
* Returns a string representation of this object.
*
* @return this object as a string
*/
public String toString() {
String output = "";
for (int i = 0; i < myRanges.length; i++) {
SVNMergeRange range = myRanges[i];
output += range.toString();
if (i < myRanges.length - 1) {
output += ',';
}
}
return output;
}
/**
* Removes <code>eraserRangeList</code> (the subtrahend) from this range list (the
* minuend), and places the resulting difference into a new <code>SVNMergeRangeList</code> object.
*
* @param eraserRangeList ranges to remove from this range list
* @param considerInheritance whether inheritance information should be taken into account
* @return the resultant difference
*/
public SVNMergeRangeList diff(SVNMergeRangeList eraserRangeList, boolean considerInheritance) {
return removeOrIntersect(eraserRangeList, true, considerInheritance);
}
/**
* Finds the intersection of this range list and <code>rangeList</code> and places the result into
* a new <code>SVNMergeRangeList</code> object.
*
* @param rangeList range list to intersect with
* @param considerInheritance whether inheritance information should be taken into account
* @return the result of intersection
*/
public SVNMergeRangeList intersect(SVNMergeRangeList rangeList, boolean considerInheritance) {
return removeOrIntersect(rangeList, false, considerInheritance);
}
/**
* Runs through all merge ranges in this object and says, whether the specified <code>revision</code>
* falls between start and end revision of any of those ranges.
*
* @param revision revision to find in ranges
* @return <span class="javakeyword">true</span> if one of the ranges in this list includes the
* specified <code>revision</code>
*/
public boolean includes(long revision) {
for (int i = 0; i < myRanges.length; i++) {
SVNMergeRange range = myRanges[i];
if (revision > range.getStartRevision() && revision <= range.getEndRevision()) {
return true;
}
}
return false;
}
/**
* Reverses this range list, and the start and end fields of each
* range in this range list, in place.
*
* @return this object itself
*/
public SVNMergeRangeList reverse() {
if (myRanges.length != 0) {
for (int i = 0; i < myRanges.length/2; i++) {
int swapInex = myRanges.length - i - 1;
SVNMergeRange range = myRanges[i];
myRanges[i] = myRanges[swapInex].swapEndPoints();
myRanges[swapInex] = range.swapEndPoints();
}
if (myRanges.length % 2 == 1) {
myRanges[myRanges.length/2].swapEndPoints();
}
}
return this;
}
/**
* Returns a sublist of this range list which excludes all non-inheritable merge ranges.
* If <code>startRev</code> and <code>endRev</code> are
* {@link org.tmatesoft.svn.core.wc.SVNRevision#isValidRevisionNumber(long) valid}
* revisions and <code>startRev</code> is less than or equal to <code>endRev</code>, then excludes only
* the non-inheritable revision ranges that intersect inclusively with the range
* defined by <code>startRev</code> and <code>endRev</code>. If this range list contains no elements,
* returns an empty array.
*
* @param startRev start revision
* @param endRev end revision
* @return a new <code>SVNMergeRangeList</code> object with only inheritable ranges from
* this range list
*/
public SVNMergeRangeList getInheritableRangeList(long startRev, long endRev) {
return getInheritableRangeList(startRev, endRev, true);
}
public SVNMergeRangeList getInheritableRangeList(long startRev, long endRev, boolean inheritable) {
LinkedList inheritableRanges = new LinkedList();
if (myRanges.length > 0) {
if (!SVNRevision.isValidRevisionNumber(startRev) ||
!SVNRevision.isValidRevisionNumber(endRev) ||
endRev < startRev) {
for (int i = 0; i < myRanges.length; i++) {
SVNMergeRange range = myRanges[i];
if (range.isInheritable() == inheritable) {
SVNMergeRange inheritableRange = new SVNMergeRange(range.getStartRevision(),
range.getEndRevision(),
true);
inheritableRanges.add(inheritableRange);
}
}
} else {
SVNMergeRange range = new SVNMergeRange(startRev, endRev, false);
SVNMergeRangeList boundRangeList = new SVNMergeRangeList(range);
return diff(boundRangeList, true);
}
}
SVNMergeRange[] ranges = (SVNMergeRange[]) inheritableRanges.toArray(new SVNMergeRange[inheritableRanges.size()]);
return new SVNMergeRangeList(ranges);
}
/**
* Creates a new <code>SVNMergeRangeList</code> from a collection of
* {@link SVNMergeRange merge ranges}.
*
* @param mergeRanges merge ranges collection
* @return merge range list containing all the ranges from <code>mergeRanges</code>
*/
public static SVNMergeRangeList fromCollection(Collection mergeRanges) {
return new SVNMergeRangeList((SVNMergeRange[])
mergeRanges.toArray(new SVNMergeRange[mergeRanges.size()]));
}
private SVNMergeRangeList removeOrIntersect(SVNMergeRangeList eraserRangeList, boolean remove, boolean considerInheritance) {
Collection ranges = new LinkedList();
SVNMergeRange lastRange = null;
SVNMergeRange range1 = null;
int i = 0;
int j = 0;
int lastInd = -1;
SVNMergeRange whiteBoardElement = new SVNMergeRange(-1, -1, false);
while (i < myRanges.length && j < eraserRangeList.myRanges.length) {
SVNMergeRange range2 = eraserRangeList.myRanges[j];
if (i != lastInd) {
SVNMergeRange tmpRange = myRanges[i];
range1 = tmpRange.dup();
whiteBoardElement = range1;
lastInd = i;
}
if (range2.contains(range1, considerInheritance)) {
if (!remove) {
lastRange = combineWithLastRange(ranges, lastRange, range1, true, considerInheritance);
}
i++;
if (range1.getStartRevision() == range2.getStartRevision() &&
range1.getEndRevision() == range2.getEndRevision()) {
j++;
}
} else if (range2.intersects(range1, considerInheritance)) {
if (range1.getStartRevision() < range2.getStartRevision()) {
SVNMergeRange tmpRange = null;
if (remove) {
tmpRange = new SVNMergeRange(range1.getStartRevision(), range2.getStartRevision(),
range1.isInheritable());
} else {
tmpRange = new SVNMergeRange(range2.getStartRevision(),
Math.min(range1.getEndRevision(), range2.getEndRevision()), range1.isInheritable());
}
lastRange = combineWithLastRange(ranges, lastRange, tmpRange, true, considerInheritance);
}
if (range1.getEndRevision() > range2.getEndRevision()) {
if (!remove) {
SVNMergeRange tmpRange = new SVNMergeRange(Math.max(range1.getStartRevision(),
range2.getStartRevision()), range2.getEndRevision(), range1.isInheritable());
lastRange = combineWithLastRange(ranges, lastRange, tmpRange, true, considerInheritance);
}
whiteBoardElement.setStartRevision(range2.getEndRevision());
whiteBoardElement.setEndRevision(range1.getEndRevision());
} else {
i++;
}
} else {
if (range2.compareTo(range1) < 0) {
j++;
} else {
if (remove && !(lastRange != null && lastRange.canCombine(range1, considerInheritance))) {
lastRange = range1.dup();
ranges.add(lastRange);
}
i++;
}
}
}
if (remove) {
if (i == lastInd && i < myRanges.length) {
lastRange = combineWithLastRange(ranges, lastRange, whiteBoardElement, true, considerInheritance);
i++;
}
for (; i < myRanges.length; i++) {
SVNMergeRange range = myRanges[i];
lastRange = combineWithLastRange(ranges, lastRange, range, true, considerInheritance);
}
}
return SVNMergeRangeList.fromCollection(ranges);
}
public SVNMergeRangeList remove(SVNMergeRangeList remove, boolean considerInheritance) {
return removeOrIntersect(remove, true, considerInheritance);
}
private SVNMergeRange combineWithLastRange(Collection rangeList, SVNMergeRange lastRange, SVNMergeRange mRange, boolean dupMRange, boolean considerInheritance) {
SVNMergeRange pushedMRange1 = null;
SVNMergeRange pushedMRange2 = null;
boolean rangesIntersect = false;
boolean rangesHaveSameInheritance = false;
if (lastRange != null) {
if (lastRange.getStartRevision() <= mRange.getEndRevision() && mRange.getStartRevision() <= lastRange.getEndRevision()) {
rangesIntersect = true;
}
if (lastRange.isInheritable() == mRange.isInheritable()) {
rangesHaveSameInheritance = true;
}
}
if (lastRange == null || !rangesIntersect || (!rangesHaveSameInheritance && considerInheritance)) {
if (dupMRange) {
pushedMRange1 = mRange.dup();
} else {
pushedMRange1 = mRange;
}
} else {
long tmpRevision = -1;
if (rangesHaveSameInheritance) {
lastRange.setStartRevision(Math.min(lastRange.getStartRevision(), mRange.getStartRevision()));
lastRange.setEndRevision(Math.max(lastRange.getEndRevision(), mRange.getEndRevision()));
lastRange.setInheritable(lastRange.isInheritable() || mRange.isInheritable());
} else {
if (lastRange.getStartRevision() == mRange.getStartRevision()) {
if (lastRange.getEndRevision() == mRange.getEndRevision()) {
lastRange.setInheritable(true);
} else if (lastRange.getEndRevision() > mRange.getEndRevision()) {
if (!lastRange.isInheritable()) {
tmpRevision = lastRange.getEndRevision();
lastRange.setEndRevision(mRange.getEndRevision());
lastRange.setInheritable(true);
if (dupMRange) {
pushedMRange1 = mRange.dup();
} else {
pushedMRange1 = mRange;
}
pushedMRange1.setEndRevision(tmpRevision);
lastRange = pushedMRange1;
}
} else {
if (mRange.isInheritable()) {
lastRange.setInheritable(true);
lastRange.setEndRevision(mRange.getEndRevision());
} else {
if (dupMRange) {
pushedMRange1 = mRange.dup();
} else {
pushedMRange1 = mRange;
}
pushedMRange1.setStartRevision(lastRange.getEndRevision());
}
}
} else if (lastRange.getEndRevision() == mRange.getEndRevision()) {
if (lastRange.getStartRevision() < mRange.getStartRevision()) {
if (!lastRange.isInheritable()) {
lastRange.setEndRevision(mRange.getStartRevision());
if (dupMRange) {
pushedMRange1 = mRange.dup();
} else {
pushedMRange1 = mRange;
}
lastRange = pushedMRange1;
}
} else {
lastRange.setStartRevision(mRange.getStartRevision());
lastRange.setEndRevision(mRange.getEndRevision());
lastRange.setInheritable(mRange.isInheritable());
if (dupMRange) {
pushedMRange1 = mRange.dup();
} else {
pushedMRange1 = mRange;
}
pushedMRange1.setStartRevision(lastRange.getEndRevision());
pushedMRange1.setInheritable(true);
}
} else {
if (lastRange.getStartRevision() < mRange.getStartRevision()) {
if (!(lastRange.getEndRevision() > mRange.getEndRevision() && lastRange.isInheritable())) {
tmpRevision = lastRange.getEndRevision();
if (!lastRange.isInheritable()) {
lastRange.setEndRevision(mRange.getStartRevision());
} else {
mRange.setStartRevision(lastRange.getEndRevision());
}
if (dupMRange) {
pushedMRange1 = mRange.dup();
} else {
pushedMRange1 = mRange;
}
if (tmpRevision > mRange.getEndRevision()) {
pushedMRange2 = new SVNMergeRange(mRange.getEndRevision(), tmpRevision, lastRange.isInheritable());
}
mRange.setInheritable(true);
}
} else {
if (lastRange.getEndRevision() < mRange.getEndRevision()) {
if (pushedMRange2 == null) {
pushedMRange2 = new SVNMergeRange(lastRange.getEndRevision(), mRange.getEndRevision(), mRange.isInheritable());
}
tmpRevision = lastRange.getStartRevision();
lastRange.setStartRevision(mRange.getStartRevision());
lastRange.setEndRevision(tmpRevision);
lastRange.setInheritable(mRange.isInheritable());
mRange.setStartRevision(tmpRevision);
mRange.setEndRevision(pushedMRange2.getStartRevision());
mRange.setInheritable(true);
} else {
if (pushedMRange2 == null) {
pushedMRange2 = new SVNMergeRange(mRange.getEndRevision(), lastRange.getEndRevision(), lastRange.isInheritable());
}
tmpRevision = lastRange.getStartRevision();
lastRange.setStartRevision(mRange.getStartRevision());
lastRange.setEndRevision(tmpRevision);
lastRange.setInheritable(mRange.isInheritable());
mRange.setStartRevision(tmpRevision);
mRange.setEndRevision(pushedMRange2.getStartRevision());
mRange.setInheritable(true);
}
}
}
}
}
if (pushedMRange1 != null) {
rangeList.add(pushedMRange1);
lastRange = pushedMRange1;
}
if (pushedMRange2 != null) {
rangeList.add(pushedMRange2);
lastRange = pushedMRange2;
}
return lastRange;
}
public SVNMergeRangeList mergeRevision(long revision) {
if (getSize() > 0 && getRanges()[getSize() - 1].getEndRevision() == revision - 1) {
getRanges()[getSize() - 1].setEndRevision(revision);
return this;
}
pushRange(revision -1 , revision, true);
return this;
}
}