Mercurial > hg4j
view src/org/tmatesoft/hg/internal/RevisionSet.java @ 677:1c49c0cee540
Report line number at the first appearance, like 'hg annotate -l' does
author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
---|---|
date | Thu, 18 Jul 2013 18:47:45 +0200 |
parents | 629a7370554c |
children |
line wrap: on
line source
/* * Copyright (c) 2013 TMate Software Ltd * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program 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 General Public License for more details. * * For information on how to redistribute this software under * the terms of a license other than GNU General Public License * contact TMate Software at support@hg4j.com */ package org.tmatesoft.hg.internal; import static org.tmatesoft.hg.repo.HgRepository.NO_REVISION; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import org.tmatesoft.hg.core.Nodeid; import org.tmatesoft.hg.repo.HgChangelog; import org.tmatesoft.hg.repo.HgParentChildMap; import org.tmatesoft.hg.repo.HgRepository; /** * Unmodifiable collection of revisions with handy set operations * * @author Artem Tikhomirov * @author TMate Software Ltd. */ public final class RevisionSet implements Iterable<Nodeid> { private final Set<Nodeid> elements; public RevisionSet(Nodeid... revisions) { this(revisions == null ? null : Arrays.asList(revisions)); } public RevisionSet(Collection<Nodeid> revisions) { this(revisions == null ? new HashSet<Nodeid>() : new HashSet<Nodeid>(revisions)); } private RevisionSet(HashSet<Nodeid> revisions) { if (revisions.isEmpty()) { elements = Collections.<Nodeid>emptySet(); } else { elements = revisions; } } /** * elements of the set with no parents or parents not from the same set */ public RevisionSet roots(HgParentChildMap<HgChangelog> ph) { HashSet<Nodeid> copy = new HashSet<Nodeid>(elements); for (Nodeid n : elements) { assert ph.knownNode(n); Nodeid p1 = ph.firstParent(n); if (p1 != null && elements.contains(p1)) { copy.remove(n); continue; } Nodeid p2 = ph.secondParent(n); if (p2 != null && elements.contains(p2)) { copy.remove(n); continue; } } return copy.size() == elements.size() ? this : new RevisionSet(copy); } /** * Same as {@link #roots(HgParentChildMap)}, but doesn't require a parent-child map */ public RevisionSet roots(HgRepository repo) { // TODO introduce parent access interface, use it here, provide implementations // that delegate to HgParentChildMap or HgRepository HashSet<Nodeid> copy = new HashSet<Nodeid>(elements); final HgChangelog clog = repo.getChangelog(); byte[] parent1 = new byte[Nodeid.SIZE], parent2 = new byte[Nodeid.SIZE]; int[] parentRevs = new int[2]; for (Nodeid n : elements) { assert clog.isKnown(n); clog.parents(clog.getRevisionIndex(n), parentRevs, parent1, parent2); if (parentRevs[0] != NO_REVISION && elements.contains(new Nodeid(parent1, false))) { copy.remove(n); continue; } if (parentRevs[1] != NO_REVISION && elements.contains(new Nodeid(parent2, false))) { copy.remove(n); continue; } } return copy.size() == elements.size() ? this : new RevisionSet(copy); } /** * elements of the set that has no children in this set */ public RevisionSet heads(HgParentChildMap<HgChangelog> ph) { HashSet<Nodeid> copy = new HashSet<Nodeid>(elements); // can't do copy.removeAll(ph.childrenOf(asList())); as actual heads are indeed children of some other node for (Nodeid n : elements) { assert ph.knownNode(n); Nodeid p1 = ph.firstParent(n); Nodeid p2 = ph.secondParent(n); if (p1 != null && elements.contains(p1)) { copy.remove(p1); } if (p2 != null && elements.contains(p2)) { copy.remove(p2); } } return copy.size() == elements.size() ? this : new RevisionSet(copy); } /** * Any ancestor of an element from the supplied child set found in this one. * Elements of the supplied child set are not part of return value. */ public RevisionSet ancestors(RevisionSet children, HgParentChildMap<HgChangelog> parentHelper) { if (isEmpty()) { return this; } if (children.isEmpty()) { return children; } RevisionSet chRoots = children.roots(parentHelper); HashSet<Nodeid> ancestors = new HashSet<Nodeid>(); Set<Nodeid> childrenToCheck = chRoots.elements; while (!childrenToCheck.isEmpty()) { HashSet<Nodeid> nextRound = new HashSet<Nodeid>(); for (Nodeid n : childrenToCheck) { Nodeid p1 = parentHelper.firstParent(n); Nodeid p2 = parentHelper.secondParent(n); if (p1 != null && elements.contains(p1)) { nextRound.add(p1); } if (p2 != null && elements.contains(p2)) { nextRound.add(p2); } } ancestors.addAll(nextRound); childrenToCheck = nextRound; } return new RevisionSet(ancestors); } /** * Revisions that are both direct and indirect children of elements of this revision set * as known in supplied parent-child map */ public RevisionSet children(HgParentChildMap<HgChangelog> parentHelper) { if (isEmpty()) { return this; } List<Nodeid> children = parentHelper.childrenOf(elements); return new RevisionSet(new HashSet<Nodeid>(children)); } public RevisionSet intersect(RevisionSet other) { if (isEmpty()) { return this; } if (other.isEmpty()) { return other; } HashSet<Nodeid> copy = new HashSet<Nodeid>(elements); copy.retainAll(other.elements); return copy.size() == elements.size() ? this : new RevisionSet(copy); } public RevisionSet subtract(RevisionSet other) { if (isEmpty() || other.isEmpty()) { return this; } HashSet<Nodeid> copy = new HashSet<Nodeid>(elements); copy.removeAll(other.elements); return copy.size() == elements.size() ? this : new RevisionSet(copy); } public RevisionSet union(RevisionSet other) { if (isEmpty()) { return other; } if (other.isEmpty()) { return this; } HashSet<Nodeid> copy = new HashSet<Nodeid>(elements); copy.addAll(other.elements); return copy.size() == elements.size() ? this : new RevisionSet(copy); } /** * A ^ B := (A\B).union(B\A) * A ^ B := A.union(B) \ A.intersect(B) */ public RevisionSet symmetricDifference(RevisionSet other) { if (isEmpty()) { return this; } if (other.isEmpty()) { return other; } HashSet<Nodeid> copyA = new HashSet<Nodeid>(elements); HashSet<Nodeid> copyB = new HashSet<Nodeid>(other.elements); copyA.removeAll(other.elements); copyB.removeAll(elements); copyA.addAll(copyB); return new RevisionSet(copyA); } public boolean isEmpty() { return elements.isEmpty(); } public int size() { return elements.size(); } public List<Nodeid> asList() { return new ArrayList<Nodeid>(elements); } public Iterator<Nodeid> iterator() { return elements.iterator(); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append('<'); if (!isEmpty()) { sb.append(elements.size()); sb.append(':'); } for (Nodeid n : elements) { sb.append(n.shortNotation()); sb.append(','); } if (sb.length() > 1) { sb.setCharAt(sb.length() - 1, '>'); } else { sb.append('>'); } return sb.toString(); } @Override public boolean equals(Object obj) { if (false == obj instanceof RevisionSet) { return false; } return elements.equals(((RevisionSet) obj).elements); } @Override public int hashCode() { return elements.hashCode(); } }