package org.tmatesoft.svn.core.wc2; import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import org.tmatesoft.sqljet.core.internal.SqlJetPagerJournalMode; import org.tmatesoft.svn.core.ISVNCanceller; import org.tmatesoft.svn.core.SVNDepth; import org.tmatesoft.svn.core.SVNErrorCode; import org.tmatesoft.svn.core.SVNErrorMessage; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager; import org.tmatesoft.svn.core.internal.wc.SVNErrorManager; import org.tmatesoft.svn.core.wc.ISVNEventHandler; import org.tmatesoft.svn.core.wc.ISVNOptions; import org.tmatesoft.svn.core.wc.ISVNRepositoryPool; import org.tmatesoft.svn.core.wc.SVNEvent; import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.util.SVNLogType; /** * Base class for all Svn* operations. * Encapsulates mostly used parameters, operation state, different methods used by implementations. * * <p/> * Those parameters includes: * <ul> * <li>operation's target(s)</li> * <li>operation's revision</li> * <li>operation's depth</li> * <li>operation's changelists</li> * <li>whether to sleep after operation fails</li> * </ul> * * <p/> * Those methods are: * <ul> * <li>base implementation of <code>run</code> method, starts the operation execution</li> * <li>methods for access to the factory that created the object and its options, event handler, canceler</li> * <li>variety of methods for getting, setting, recognition operation's targets</li> * <li>cancel the operation</li> * <li>access to the authentication manager</li> * </ul> * * @author TMate Software Ltd. * @version 1.7 * @param <V> type of returning value in {@link #run()} method */ public class SvnOperation<V> implements ISvnOperationOptionsProvider { private SVNDepth depth; private Collection<SvnTarget> targets; private SVNRevision revision; private Collection<String> changelists; private SvnOperationFactory operationFactory; private boolean isSleepForTimestamp; private SqlJetPagerJournalMode sqliteJournalMode; private volatile boolean isCancelled; protected SvnOperation(SvnOperationFactory factory) { this.operationFactory = factory; initDefaults(); } /** * Gets the event handler for the operation, provided by {@link SvnOperationFactory#getEventHandler()}. This event handler will be * dispatched {@link SVNEvent} objects to provide detailed information about * actions and progress state of version control operations performed by * <code>run()</code> method of <code>SVN*</code> operation classes. * * @return handler for events * @see ISVNEventHandler */ public ISVNEventHandler getEventHandler() { return getOperationFactory().getEventHandler(); } /** * Gets operation's options, provided by {@link SvnOperationFactory#getOptions()}. * * @return options of the operation */ public ISVNOptions getOptions() { return getOperationFactory().getOptions(); } protected void initDefaults() { setDepth(SVNDepth.UNKNOWN); setSleepForTimestamp(true); setRevision(SVNRevision.UNDEFINED); this.targets = new ArrayList<SvnTarget>(); } /** * Sets one target of the operation. * * @param target target of the operation * @see SvnTarget */ public void setSingleTarget(SvnTarget target) { this.targets = new ArrayList<SvnTarget>(); if (target != null) { this.targets.add(target); } } /** * Adds one target to the operation's targets. * * @param target target of the operation * @see SvnTarget */ public void addTarget(SvnTarget target) { this.targets.add(target); } /** * Returns all targets of the operation. * * @return targets of the operation * @see SvnTarget */ public Collection<SvnTarget> getTargets() { return Collections.unmodifiableCollection(targets); } /** * Returns first target of the operation. * * @return first target of the operation * @see SvnTarget */ public SvnTarget getFirstTarget() { return targets != null && !targets.isEmpty() ? targets.iterator().next() : null; } /** * Sets the limit of the operation by depth. * * @param depth depth of the operation */ public void setDepth(SVNDepth depth) { this.depth = depth; } /** * Gets the limit of the operation by depth. * * @return depth of the operation */ public SVNDepth getDepth() { return depth; } /** * Sets revision of the operation. * In most cases if revision equals {@link SVNRevision#UNDEFINED}, the operation's revision will be {@link SVNRevision#WORKING} * if target(s) are local; it will be will be {@link SVNRevision#HEAD} it targets are remote. * * @param revision revision of the operation */ public void setRevision(SVNRevision revision) { this.revision = revision; } /** * Gets revision to operate on. * * @return revision of the operation * @see #setRevision(SVNRevision) */ public SVNRevision getRevision() { return revision; } /** * Sets changelists to operate only on members of. * * @param changelists changelists of the operation */ public void setApplicalbeChangelists(Collection<String> changelists) { this.changelists = changelists; } /** * Gets changelists to operate only on members of. * * @return changelists of the operation */ public Collection<String> getApplicableChangelists() { if (this.changelists == null || this.changelists.isEmpty()) { return null; } return Collections.unmodifiableCollection(this.changelists); } /** * Gets the factory that created the operation. * * @return creation factory of the operations */ public SvnOperationFactory getOperationFactory() { return this.operationFactory; } /** * Gets whether or not the operation has local targets. * * @return <code>true</code> if the operation has local targets, otherwise <code>false</code> */ public boolean hasLocalTargets() { for (SvnTarget target : getTargets()) { if (target.isLocal()) { return true; } } return false; } /** * Gets whether or not the operation has remote targets. * * @return <code>true</code> if the operation has remote targets, otherwise <code>false</code> */ public boolean hasRemoteTargets() { for (SvnTarget target : getTargets()) { if (!target.isLocal()) { return true; } } return false; } protected void ensureEnoughTargets() throws SVNException { int targetsCount = getTargets().size(); if (targetsCount < getMinimumTargetsCount()) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ILLEGAL_TARGET, "Wrong number of targets has been specified ({0}), at least {1} is required.", new Object[] {new Integer(targetsCount), new Integer(getMinimumTargetsCount())}, SVNErrorMessage.TYPE_ERROR); SVNErrorManager.error(err, SVNLogType.WC); } if (targetsCount > getMaximumTargetsCount()) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ILLEGAL_TARGET, "Wrong number of targets has been specified ({0}), no more that {1} may be specified.", new Object[] {new Integer(targetsCount), new Integer(getMaximumTargetsCount())}, SVNErrorMessage.TYPE_ERROR); SVNErrorManager.error(err, SVNLogType.WC); } } protected int getMinimumTargetsCount() { return 1; } protected int getMaximumTargetsCount() { return 1; } /** * Cancels the operation. Execution of operation will be stopped at the next point of checking <code>isCancelled</code> state. * If canceler is set, {@link ISVNCanceller#checkCancelled()} is called, * otherwise {@link org.tmatesoft.svn.core.SVNCancelException} is raised at the point of checking <code>isCancelled</code> state. */ public void cancel() { isCancelled = true; } /** * Gets whether or not the operation is cancelled. * * @return <code>true</code> if the operation is cancelled, otherwise <code>false</code> */ public boolean isCancelled() { return isCancelled; } /** * Executes the operation. * * @return result depending on operation type * @throws SVNException */ @SuppressWarnings("unchecked") public V run() throws SVNException { ensureArgumentsAreValid(); return (V) getOperationFactory().run(this); } protected void ensureArgumentsAreValid() throws SVNException { ensureEnoughTargets(); ensureHomohenousTargets(); } protected boolean needsHomohenousTargets() { return true; } protected void ensureHomohenousTargets() throws SVNException { if (getTargets().size() <= 1) { return; } if (!needsHomohenousTargets()) { return; } if (hasLocalTargets() && hasRemoteTargets()) { SVNErrorManager.error(SVNErrorMessage.create(SVNErrorCode.ILLEGAL_TARGET, "Cannot mix repository and working copy targets"), SVNLogType.WC); } } /** * Gets the operation's pool of repositories, provided by {@link SvnOperationFactory#getRepositoryPool()}. * * @return pool of repositories */ public ISVNRepositoryPool getRepositoryPool() { return getOperationFactory().getRepositoryPool(); } /** * Gets operation's authentication manager, provided by {@link SvnOperationFactory#getAuthenticationManager()}. * * @return authentication manager */ public ISVNAuthenticationManager getAuthenticationManager() { return getOperationFactory().getAuthenticationManager(); } /** * Gets the cancel handler of the operation, provided by {@link SvnOperationFactory#getCanceller() }. * * @return cancel handler * @see #cancel() */ public ISVNCanceller getCanceller() { return getOperationFactory().getCanceller(); } /** * Gets whether or not the operation should sleep after if fails. * * @return <code>true</code> if the operation should sleep, otherwise <code>false</code> * @see SvnUpdate * @since 1.7 */ public boolean isSleepForTimestamp() { return isSleepForTimestamp; } /** * Sets whether or not the operation should sleep after if fails. * * @param isSleepForTimestamp <code>true</code> if the operation should sleep, otherwise <code>false</code> * @see SvnUpdate * @since 1.7 */ public void setSleepForTimestamp(boolean isSleepForTimestamp) { this.isSleepForTimestamp = isSleepForTimestamp; } /** * Analyzes the targets and returns whether or not operation has at least one file in targets. * * @return <code>true</code> if operation has at least one file in targets, otherwise <code>false</code> */ public boolean hasFileTargets() { for (SvnTarget target : getTargets()) { if (target.isFile()) { return true; } } return false; } /** * Gets whether or not to use parent working copy format. * * @return <code>true</code> if parent working copy format should be used, otherwise <code>false</code> */ public boolean isUseParentWcFormat() { return false; } /** * Gets whether the operation changes working copy * @return <code>true</code> if the operation changes the working copy, otherwise <code>false</code> */ public boolean isChangesWorkingCopy() { return true; } public SqlJetPagerJournalMode getSqliteJournalMode() { return sqliteJournalMode; } public void setSqliteJournalMode(SqlJetPagerJournalMode journalMode) { sqliteJournalMode = journalMode; } protected File getOperationalWorkingCopy() { if (hasFileTargets()) { return getFirstTarget().getFile(); } return null; } }