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; } }