package org.tmatesoft.svn.core.wc2;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNLock;
import org.tmatesoft.svn.core.SVNNodeKind;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
import org.tmatesoft.svn.core.internal.wc2.ISvnCommitRunner;
import org.tmatesoft.svn.core.internal.wc2.compat.SvnCodec;
import org.tmatesoft.svn.core.wc.SVNCommitItem;
import org.tmatesoft.svn.core.wc.SVNCommitPacket;
/**
* Represents storage for <code>SvnCommitItem</code>
* objects which represent information on versioned items intended
* for being committed to a repository.
*
* <p>
* Used by commit-related operations to collect and hold information on paths that are to be committed.
* Each <code>SvnCommitPacket</code> is committed in a single transaction.
*
* @author TMate Software Ltd.
* @see SvnCommitItem
*/
public class SvnCommitPacket {
private Map<SVNURL, Collection<SvnCommitItem>> items;
private Map<String, SvnCommitItem> itemsByPath;
private Object lockingContext;
private ISvnCommitRunner runner;
private Map<SVNURL, String> lockTokens;
private Set<String> skippedPaths;
private AtomicInteger sharedIndex;
/**
* Creates a commit packet and initializes its fields with empty lists.
*/
public SvnCommitPacket() {
items = new HashMap<SVNURL, Collection<SvnCommitItem>>();
itemsByPath = new HashMap<String, SvnCommitItem>();
lockTokens = new HashMap<SVNURL, String>();
skippedPaths = new HashSet<String>();
}
private SvnCommitPacket(Map<SVNURL, Collection<SvnCommitItem>> items, Map<String, SvnCommitItem> itemsByPath, Object lockingContext, Map<SVNURL, String> lockTokens, ISvnCommitRunner runner, Set<String> skippedPaths) {
this.items = items;
this.itemsByPath = itemsByPath;
this.lockingContext = lockingContext;
this.lockTokens = lockTokens;
this.runner = runner;
this.skippedPaths = skippedPaths;
}
/**
* Tests if the commit packet contains the commit item with the path
* @param path the path of the commit item to test
* @return <code>true</code> if commit item with the path is contained in the commit packet, otherwise <code>false</code>
*/
public boolean hasItem(File path) {
return itemsByPath.containsKey(SVNFileUtil.getFilePath(path));
}
/**
* Returns the commit item with the path
* @param path the path of the commit item
* @return commit item
*/
public SvnCommitItem getItem(File path) {
return itemsByPath.get(SVNFileUtil.getFilePath(path));
}
/**
* Returns all unique repository root URLs of all commit items in the commit packet
* @return unmodifiable list of URLs of the commit packet
*/
public Collection<SVNURL> getRepositoryRoots() {
return Collections.unmodifiableCollection(items.keySet());
}
/**
* Returns all commit items in the commit packet with the corresponding repository root URL
* @return unmodifiable list of commit items containing info of versioned items to be committed
*/
public Collection<SvnCommitItem> getItems(SVNURL url) {
return Collections.unmodifiableCollection(items.get(url));
}
/**
* Adds commit item to the commit packet with the repository root URL.
* @param item commit item
* @param repositoryRoot repository root URL
*/
public void addItem(SvnCommitItem item, SVNURL repositoryRoot) {
if (!items.containsKey(repositoryRoot)) {
items.put(repositoryRoot, new HashSet<SvnCommitItem>());
}
items.get(repositoryRoot).add(item);
itemsByPath.put(item.getPath() != null ? SVNFileUtil.getFilePath(item.getPath()) : null, item);
}
/**
* Adds commit item with the path, kind, repository root URL, repository path, revision number,
* copied from path, copied from revision number, flags to the commit packet.
*
* @param path path of the commit item
* @param kind node kind of the commit item
* @param repositoryRoot repository root URL of the commit item
* @param repositoryPath repository path of the commit item
* @param revision revision number of the commit item
* @param copyFromPath path from those commit item was copied
* @param copyFromRevision revision of the repository item from those commit item was copied
* @param flags commit item flags
* @return newly created commit item with initialized fields
* @throws SVNException if URL parse error occurred
*/
public SvnCommitItem addItem(File path, SVNNodeKind kind, SVNURL repositoryRoot, String repositoryPath, long revision,
String copyFromPath, long copyFromRevision, File movedFromAbsPath, int flags) throws SVNException {
SvnCommitItem item = new SvnCommitItem();
item.setPath(path);
item.setKind(kind);
item.setUrl(repositoryRoot.appendPath(repositoryPath, false));
item.setRevision(revision);
if (copyFromPath != null) {
item.setCopyFromUrl(repositoryRoot.appendPath(copyFromPath, false));
item.setCopyFromRevision(copyFromRevision);
} else {
item.setCopyFromRevision(-1);
}
item.setMovedFromAbsPath(movedFromAbsPath);
item.setFlags(flags);
addItem(item, repositoryRoot);
return item;
}
/**
* Adds commit item with the path, repository root URL, kind, URL, revision number,
* revision number, copied from path, copied from revision number, flags to the commit packet.
*
* @param path path of the commit item
* @param rootUrl repository root URL of the commit item
* @param kind node kind of the commit item
* @param url repository URL of the commit item
* @param revision revision number of the commit item
* @param copyFromUrl url from those commit item was copied
* @param copyFromRevision revision of the repository item from those commit item was copied
* @param flags commit item flags
* @return newly created commit item with initialized fields
* @throws SVNException if URL parse error occurred
*/
public SvnCommitItem addItem(File path, SVNURL rootUrl, SVNNodeKind kind, SVNURL url, long revision,
SVNURL copyFromUrl, long copyFromRevision, int flags) throws SVNException {
SvnCommitItem item = new SvnCommitItem();
item.setPath(path);
item.setKind(kind);
item.setUrl(url);
item.setRevision(revision);
if (copyFromUrl!= null) {
item.setCopyFromUrl(copyFromUrl);
item.setCopyFromRevision(copyFromRevision);
} else {
item.setCopyFromRevision(-1);
}
item.setFlags(flags);
if (!items.containsKey(rootUrl)) {
items.put(rootUrl, new HashSet<SvnCommitItem>());
}
items.get(rootUrl).add(item);
itemsByPath.put(SVNFileUtil.getFilePath(path), item);
return item;
}
/**
*
* @param commitRunner
* @param context
*/
public void setLockingContext(ISvnCommitRunner commitRunner, Object context) {
lockingContext = context;
runner = commitRunner;
}
/**
* Disposes the commit packet, if commit runner is set method calls
* {@link ISvnCommitRunner#disposeCommitPacket(Object)} with the commit packet
*
* @throws SVNException
*/
public void dispose() throws SVNException {
try {
if (runner != null) {
runner.disposeCommitPacket(lockingContext, isLastPacket());
}
} finally {
if (sharedIndex != null) {
sharedIndex.decrementAndGet();
}
if (items != null) {
items.clear();
}
if (itemsByPath != null) {
itemsByPath.clear();
}
runner = null;
lockingContext = null;
}
}
/**
* Sets commit packet's lock tokens, containing the information about locks within commit packet URLs.
* @param lockTokens hash of URL, lock tokens for this URL
*/
public void setLockTokens(Map<SVNURL, String> lockTokens) {
this.lockTokens = lockTokens;
}
/**
* Returns all lock tokens of commit packet.
*
* @return hash of URL, lock tokens
*/
public Map<SVNURL, String> getLockTokens() {
return lockTokens;
}
/**
* Tests whether the commit packet has commit items.
*
* @return <code>true</code> if the commit packet has no commit items, otherwise <code>false</code>
*/
public boolean isEmpty() {
for (SVNURL rootUrl : getRepositoryRoots()) {
if (!isEmpty(rootUrl)) {
return false;
}
}
return true;
}
/**
* Tests whether the commit packet has commit items with the repository root URL.
*
* @return <code>true</code> if the commit packet has no commit items with the repository root, otherwise <code>false</code>
*/
public boolean isEmpty(SVNURL repositoryRootUrl) {
for (SvnCommitItem item : getItems(repositoryRootUrl)) {
if (item.getFlags() != SvnCommitItem.LOCK) {
return false;
}
}
return true;
}
/**
* Returns commit packet's locking context.
* @return the locking context for the commit packet
*/
public Object getLockingContext() {
return lockingContext;
}
/**
* Returns commit packet's runner.
* @return the runner for the commit packet
*/
public ISvnCommitRunner getRunner() {
return runner;
}
public void setItemSkipped(File file, boolean skipped) {
final String path = SVNFileUtil.getFilePath(file);
if (skipped) {
skippedPaths.add(path);
} else {
skippedPaths.remove(path);
}
if (lockingContext != null && lockingContext instanceof SVNCommitPacket && !(lockingContext instanceof SvnCodec.SVNCommitPacketWrapper)) {
final SvnCommitItem commitItem = itemsByPath.get(path);
if (commitItem != null) {
final SVNCommitPacket oldPacket = (SVNCommitPacket) lockingContext;
final SVNCommitItem[] oldItems = oldPacket.getCommitItems();
for (SVNCommitItem oldItem : oldItems) {
if (SVNFileUtil.getFilePath(oldItem.getFile()).equals(path)) {
oldPacket.setCommitItemSkipped(oldItem, true);
break;
}
}
}
}
}
public boolean isItemSkipped(File file) {
return skippedPaths.contains(SVNFileUtil.getFilePath(file));
}
public SvnCommitPacket removeSkippedItems() {
final HashMap<String, SvnCommitItem> filteredItemsByPath = new HashMap<String, SvnCommitItem>();
final Map<SVNURL, Collection<SvnCommitItem>> filteredItems = new HashMap<SVNURL, Collection<SvnCommitItem>>();
final Map<SVNURL, String> filteredLockTokens = new HashMap<SVNURL, String>(this.lockTokens);
Object filteredLockingContext = lockingContext;
for (Map.Entry<String, SvnCommitItem> entry : this.itemsByPath.entrySet()) {
final String path = entry.getKey();
SvnCommitItem commitItem = entry.getValue();
if (!skippedPaths.contains(path)) {
filteredItemsByPath.put(path, commitItem);
}
}
for (Map.Entry<SVNURL, Collection<SvnCommitItem>> entry : this.items.entrySet()) {
final SVNURL url = entry.getKey();
final Collection<SvnCommitItem> commitItems = entry.getValue();
final List<SvnCommitItem> filteredCommitItems = new ArrayList<SvnCommitItem>();
for (SvnCommitItem commitItem : commitItems) {
final String path = SVNFileUtil.getFilePath(commitItem.getPath());
if (!skippedPaths.contains(path)) {
filteredCommitItems.add(commitItem);
} else {
lockTokens.remove(commitItem.getUrl());
}
}
if (filteredCommitItems.size() > 0) {
filteredItems.put(url, filteredCommitItems);
}
}
SvnCommitPacket result = new SvnCommitPacket(filteredItems, filteredItemsByPath, filteredLockingContext, filteredLockTokens, runner, skippedPaths);
result.sharedIndex = sharedIndex;
return result;
}
SvnCommitPacket[] split(boolean combinePackets) throws SVNException {
final Map<String, SvnCommitPacket> splitPackets = new HashMap<String, SvnCommitPacket>();
final AtomicInteger sharedIndex = new AtomicInteger(0);
for (SVNURL root : getRepositoryRoots()) {
Collection<SvnCommitItem> items = getItems(root);
for (SvnCommitItem item : items) {
if (isItemSkipped(item.getPath())) {
continue;
}
final String key = getItemKey(item, root, combinePackets);
if (!splitPackets.containsKey(key)) {
final SvnCommitPacket newPacket = new SvnCommitPacket();
newPacket.runner = this.runner;
newPacket.sharedIndex = sharedIndex;
newPacket.setLockTokens(getLockTokens());
sharedIndex.incrementAndGet();
splitPackets.put(key, newPacket);
}
splitPackets.get(key).addItem(item, root);
}
}
for (SvnCommitPacket splitPacket : splitPackets.values()) {
splitPacket.lockingContext = this.lockingContext;
}
return splitPackets.values().toArray(new SvnCommitPacket[splitPackets.size()]);
}
private String getItemKey(SvnCommitItem item, SVNURL rootURL, boolean combinePackets) throws SVNException {
if (combinePackets) {
return rootURL.toString();
}
final File wcRoot = SvnOperationFactory.getWorkingCopyRoot(item.getKind() == SVNNodeKind.FILE ? item.getPath().getParentFile() : item.getPath(), true);
return rootURL.toString() + ":" + wcRoot.getAbsolutePath();
}
public boolean isLastPacket() {
return sharedIndex == null || sharedIndex.get() == 1;
}
}