/*
 * ====================================================================
 * 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.io;

import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.tmatesoft.svn.core.ISVNCanceller;
import org.tmatesoft.svn.core.ISVNDirEntryHandler;
import org.tmatesoft.svn.core.ISVNLogEntryHandler;
import org.tmatesoft.svn.core.SVNAuthenticationException;
import org.tmatesoft.svn.core.SVNDepth;
import org.tmatesoft.svn.core.SVNDirEntry;
import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNErrorMessage;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNLock;
import org.tmatesoft.svn.core.SVNLogEntry;
import org.tmatesoft.svn.core.SVNLogEntryPath;
import org.tmatesoft.svn.core.SVNMergeInfo;
import org.tmatesoft.svn.core.SVNMergeInfoInheritance;
import org.tmatesoft.svn.core.SVNNodeKind;
import org.tmatesoft.svn.core.SVNProperties;
import org.tmatesoft.svn.core.SVNProperty;
import org.tmatesoft.svn.core.SVNPropertyValue;
import org.tmatesoft.svn.core.SVNRevisionProperty;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryUtil;
import org.tmatesoft.svn.core.internal.util.SVNHashMap;
import org.tmatesoft.svn.core.internal.util.SVNHashSet;
import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
import org.tmatesoft.svn.core.io.diff.SVNDeltaGenerator;
import org.tmatesoft.svn.core.io.diff.SVNDiffWindow;
import org.tmatesoft.svn.util.ISVNDebugLog;
import org.tmatesoft.svn.util.SVNDebugLog;
import org.tmatesoft.svn.util.SVNLogType;

/**
 * The abstract class <b>SVNRepository</b> provides an interface for protocol
 * specific drivers used for direct working with a Subversion repository. 
 * <b>SVNRepository</b> joins all low-level API methods needed for repository
 * access operations.
 * 
 * <p>
 * In particular this low-level protocol driver is used by the high-level API 
 * (represented by the <B><A HREF="../wc/package-summary.html">org.tmatesoft.svn.core.wc</A></B> package) 
 * when an access to a repository is needed. 
 * 
 * <p>
 * It is important to say that before using the library it must be configured 
 * according to implimentations to be used. That is if a repository is assumed
 * to be accessed either via the <i>WebDAV</i> protocol (<code>http://</code> or 
 * <code>https://</code>), or a custom <i>svn</i> one (<code>svn://</code> or <code>svn+ssh://</code>) 
 * or immediately on the local machine (<code>file:///</code>) a user must initialize the library
 * in a proper way:
 * <pre class="javacode">
 * <span class="javacomment">//import neccessary classes</span>
 * <span class="javakeyword">import</span> org.tmatesoft.svn.core.internal.io.fs.FSRepositoryFactory;
 * <span class="javakeyword">import</span> org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory;
 * <span class="javakeyword">import</span> org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl;
 * <span class="javakeyword">import</span> org.tmatesoft.svn.core.SVNURL;
 * <span class="javakeyword">import</span> org.tmatesoft.svn.core.io.SVNRepository;
 * <span class="javakeyword">import</span> org.tmatesoft.svn.core.io.SVNRepositoryFactory;
 * <span class="javakeyword">import</span> import org.tmatesoft.svn.core.wc.SVNWCUtil;
 * <span class="javakeyword">import</span> import org.tmatesoft.svn.core.SVNException;
 * ...
 * 
 * <span class="javacomment">//Set up connection protocols support:</span>
 * <span class="javacomment">//http:// and https://</span>
 * DAVRepositoryFactory.setup();
 * <span class="javacomment">//svn://, svn+xxx:// (svn+ssh:// in particular)</span>
 * SVNRepositoryFactoryImpl.setup();
 * <span class="javacomment">//file:///</span>
 * FSRepositoryFactory.setup();
 * </pre>
 * <code>svn+xxx://</code> can be any tunnel scheme for tunneled working with a 
 * repository. <code>xxx</code> URL scheme is looked up in the section <code>tunnels</code> of the 
 * standard Subversion <code>config</code> file.
 * 
 * <p>
 * So, only after these setup steps the client can create http | svn | file protocol 
 * implementations of the <code>SVNRepository</code> abstract class to access 
 * the repository.
 * 
 * <p>
 * This is a general way how a user creates an <b>SVNRepository</b> driver object:
 * <pre class="javacode">
 * String url=<span class="javastring">"http://svn.collab.net/svn/trunk"</span>;
 * String name=<span class="javastring">"my name"</span>;
 * String password=<span class="javastring">"my password"</span>;
 * repository = <span class="javakeyword">null</span>;
 * <span class="javakeyword">try</span> { 
 *     repository = SVNRepositoryFactory.create(SVNURL.parseURIDecoded(url));
 *     ISVNAuthenticationManager authManager = 
 *                  SVNWCUtil.createDefaultAuthenticationManager(name, password);
 *     repository.setAuthenticationManager(authManager);
 *     ...
 * } <span class="javakeyword">catch</span> (SVNException e){
 *     e.printStackTrace();
 *     System.exit(1);
 * }
 * <span class="javacomment">//work with the repository</span>
 * ... </pre>
 * <p>
 * <b>SVNRepository</b> objects are not thread-safe, we're strongly recommend
 * you not to use one <b>SVNRepository</b> object from within multiple threads.  
 * 
 * <p>
 * Also methods of <b>SVNRepository</b> objects are not reenterable - that is, 
 * you can not call operation methods of an <b>SVNRepository</b> driver neither 
 * from within those handlers that are passed to some of the driver's methods, nor 
 * during committing with the help of a commit editor (until the editor's {@link ISVNEditor#closeEdit() closeEdit()} 
 * method is called). 
 * 
 * <p>
 * To authenticate a user over network <b>SVNRepository</b> drivers use
 * <b>ISVNAuthenticationManager</b> auth drivers.
 * 
 * @version     1.3
 * @author      TMate Software Ltd.
 * @since       1.2
 * @see         SVNRepositoryFactory
 * @see         org.tmatesoft.svn.core.auth.ISVNAuthenticationManager
 * @see         <a href="http://svnkit.com/kb/examples/">Examples</a>
 */
public abstract class SVNRepository {
    
    /**
     * Is used as an initialization value in cases, when revision is not defined, often represents HEAD revision
     */
    public static final long INVALID_REVISION = -1L;
        
    protected String myRepositoryUUID;
    protected SVNURL myRepositoryRoot;
    protected SVNURL myLocation;
    
    private int myLockCount;
    private Thread myLocker;
    private ISVNAuthenticationManager myAuthManager;
    private ISVNSession myOptions;
    private ISVNTunnelProvider myTunnelProvider;
    private ISVNDebugLog myDebugLog;
    private ISVNCanceller myCanceller;
    private Collection myConnectionListeners;

    protected SVNRepository(SVNURL location, ISVNSession options) {
        myLocation = location;
        myOptions = options;
        myConnectionListeners = new SVNHashSet();
    }
	
    /**
	 * Returns the repository location to which this object is set. 
     * It may be the location that was used to create this object
     * (see {@link SVNRepositoryFactory#create(SVNURL)}), or the recent 
     * one the object was set to.
     * 
	 * @return 	a repository location set for this driver
     * @see     #setLocation(SVNURL, boolean) 
	 * 			
	 */    
    public SVNURL getLocation() {
        return myLocation;
    }
    
    /**
     * Sets a new repository location for this object. The ability to reset
     * an old repository location to a new one (to switch the working location)
     * lets a developer to use the same <b>SVNRepository</b> object instead of
     * creating a new object per each repository location. This advantage gives
     * memory & coding efforts economy. 
     * 
     * <p>
     * But you can not specify a new repository location url with a protocol
     * different from the one used for the previous (essentially, the current) 
     * repository location, since <b>SVNRepository</b> objects are protocol dependent.    
     * 
     * <p>
     * If a new <code>url</code> is located within the same repository, this object
     * just switches to that <code>url</code> not closing the current session (i.e. 
     * not calling {@link #closeSession()}).
     * 
     * <p>
     * If either a new <code>url</code> refers to the same host (including a port
     * number), or refers to an absolutely different host, or this object has got
     * no repository root location cached (hasn't ever accessed a repository yet),
     * or <code>forceReconnect</code> is <span class="javakeyword">true</span>, then
     * the current session is closed, cached repository credentials (UUID and repository 
     * root directory location ) are reset and this object is switched to a new 
     * repository location.
     * 
     * @param  url             a new repository location url
     * @param  forceReconnect  if <span class="javakeyword">true</span> then
     *                         forces to close the current session, resets the
     *                         cached repository credentials and switches this object to 
     *                         a new location (doesn't matter whether it's on the same 
     *                         host or not)
     * @throws SVNException    if the old url and a new one has got different
     *                         protocols
     */
    public void setLocation(SVNURL url, boolean forceReconnect) throws SVNException {
        lock();
        try {
            if (url == null) {
                return;
            } else if (!url.getProtocol().equals(myLocation.getProtocol())) {
                SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.RA_NOT_IMPLEMENTED, "SVNRepository URL could not be changed from ''{0}'' to ''{1}''; create new SVNRepository instance instead", new Object[] {myLocation, url});
                SVNErrorManager.error(err, SVNLogType.NETWORK);
            }
            if (forceReconnect) {
                closeSession();
                myRepositoryRoot = null;
                myRepositoryUUID = null;
            } else if (myRepositoryRoot == null) {
                // no way to check whether repos is the same. just compare urls
                if (!(url.toString().startsWith(myLocation.toString() + "/") || url.equals(getLocation()))) {
                    closeSession();
                    myRepositoryRoot = null;
                    myRepositoryUUID = null;
                }
            } else if (url.toString().startsWith(myRepositoryRoot.toString() + "/") || myRepositoryRoot.equals(url)) {
                // just do nothing, we are still below old root.
            } else {
                closeSession();
                myRepositoryRoot = null;
                myRepositoryUUID = null;
            }
            myLocation = url;
        } finally {
            unlock();
        }
    }

    /**
     * @return 	the UUID of a repository
     * @deprecated use {@link #getRepositoryUUID(boolean) } instead 
     */
    public String getRepositoryUUID() {
        try {
            return getRepositoryUUID(false);
        } catch (SVNException e) {
        }
        return myRepositoryUUID;
    }

    /**
     * Gets the Universal Unique IDentifier (UUID) of the repository this 
     * driver is created for.  
     *  
     * @param   forceConnection   if <span class="javakeyword">true</span> then forces
     *                            this driver to test a connection - try to access a 
     *                            repository 
     * @return  the UUID of a repository
     * @throws SVNException in case the repository could not be connected
     * @throws org.tmatesoft.svn.core.SVNAuthenticationException in case of authentication problems
     */
    public String getRepositoryUUID(boolean forceConnection) throws SVNException {
        if (forceConnection && myRepositoryUUID == null) {
            testConnection();
        }
        return myRepositoryUUID;
    }


    /**
     * @return 	the repository root directory location url
     * @see     #getRepositoryRoot(boolean)
     * 
     * @deprecated use #getRepositoryRoot(boolean) instead
     */
    public SVNURL getRepositoryRoot() {
        try {
            return getRepositoryRoot(false);
        } catch (SVNException e) {
            // will not be thrown.
        }
        return null;
    }
    
    /**
     * Gets a repository's root directory location. 
     * If this driver object is switched to a different repository location during
     * runtime (probably to an absolutely different repository, see {@link #setLocation(SVNURL, boolean) setLocation()}), 
     * the root directory location may be changed. 
     
     * This method may need to establish connection with the repository 
     * if the information on the repository's root location has not been received yet from the repository.
     * 
     * @param   forceConnection   if <span class="javakeyword">true</span> then forces
     *                            this driver to test a connection - try to access a 
     *                            repository 
     * @return                    the repository root directory location url
     * @throws SVNException in case the repository could not be connected
     * @throws org.tmatesoft.svn.core.SVNAuthenticationException in case of authentication problems
     * @see                       #testConnection()
     */
    public SVNURL getRepositoryRoot(boolean forceConnection) throws SVNException {
        if (forceConnection && myRepositoryRoot == null) {
            testConnection();
        }
        return myRepositoryRoot;
    }
    
    /**
     * Sets an authentication driver for this object. The auth driver
     * may be implemented to retrieve cached credentials, to prompt
     * a user for credentials or something else (actually, this is up
     * to an implementor). Also there's a default implementation - see
     * the {@link org.tmatesoft.svn.core.wc.SVNWCUtil} class for more
     * details.  
     * 
     * @param authManager an authentication driver to provide user 
     *                    credentials 
     * @see               #getAuthenticationManager()
     */
    public void setAuthenticationManager(ISVNAuthenticationManager authManager) {
        myAuthManager = authManager;
    }
    
    /**
     * Returns the authentication driver registered for this 
     * object.
     * 
     * @return an authentication driver that is used by this object 
     *         to authenticate a user over network
     */
    public ISVNAuthenticationManager getAuthenticationManager() {
        return myAuthManager;
    }
    
    /**
     * Sets a tunnel provider. Actually relevant only to  
     * <code>svn+xxx://</code> scheme cases. The provider is responsible 
     * for matching <code>xxx</code> to the tunnel command string.   
     * 
     * <p>
     * If one would like to have a standard Subversion behaviour 
     * (when tunnel commands are fetched from the <code>config</code> file 
     * beneath the section named <code>tunnels</code>), he should provide a 
     * default provider (default implementation of the {@link org.tmatesoft.svn.core.wc.ISVNOptions} 
     * interface). Refer to {@link org.tmatesoft.svn.core.wc.SVNWCUtil} class 
     * for more details on how to get a default options driver.   
     * 
     * @param tunnelProvider a tunnel provider
     * @see                  #getTunnelProvider()
     */
    public void setTunnelProvider(ISVNTunnelProvider tunnelProvider) {
        myTunnelProvider = tunnelProvider;
    }
    
    /**
     * Returns a tunnel provider.  
     * 
     * @return a tunnel provider
     * @see    #setTunnelProvider(ISVNTunnelProvider)
     */
    public ISVNTunnelProvider getTunnelProvider() {
        return myTunnelProvider;
    }
    
    /**
     * Sets a canceller to this object.
     * 
     * @param canceller  canceller object
     * @since            1.2.0 
     */
    public void setCanceller(ISVNCanceller canceller) {
        myCanceller = canceller;
    }
    
    /**
     * Returns the canceller, stored in this object.
     * 
     * @return  canceller object
     * @since   1.2.0
     */
    public ISVNCanceller getCanceller() {
        return myCanceller == null ? ISVNCanceller.NULL : myCanceller;
    }
    
    /**
     * Caches identification parameters (UUID, rood directory location) 
     * of the repository with which this driver is working.
     * 
     * @param uuid 		the repository's Universal Unique IDentifier 
     * 					(UUID) 
     * @param rootURL	the repository's root directory location
     * @see 			#getRepositoryRoot(boolean)
     * @see 			#getRepositoryUUID(boolean)
     */
    protected void setRepositoryCredentials(String uuid, SVNURL rootURL) {
        if (uuid != null && rootURL != null) {
            myRepositoryUUID = uuid;
            myRepositoryRoot = rootURL;
        }
    }
    
    /**
     * Tries to access a repository. Used to check if there're no problems 
     * with accessing a repository and to cache a repository UUID and root
     * directory location.  
     * 
     * @throws SVNException if a failure occured while connecting to a repository 
     *                      or the user's authentication failed (see 
     *                      {@link org.tmatesoft.svn.core.SVNAuthenticationException})
     */
    public abstract void testConnection() throws SVNException; 
    
    /**
     * Returns the number of the latest revision of the repository this 
     * driver is working with.
     *  
     * @return 					the latest revision number
     * @throws 	SVNException	if a failure occured while connecting to a repository 
     *                          or the user's authentication failed (see 
     *                          {@link org.tmatesoft.svn.core.SVNAuthenticationException})
     */
    public abstract long getLatestRevision() throws SVNException;
    
    /**
     * Returns the recent repository revision number for the particular moment 
     * in time - the closest one before or at the specified datestamp. 
     * 
     * <p>
     * Example: if you specify a single date without specifying a time of the day 
     * (e.g. 2002-11-27) the timestamp is assumed to 00:00:00 and the method won't 
     * return any revisions for the day you have specified but for the day just 
     * before it. 
     * 
     * @param  date			a datestamp for defining the needed
     * 						moment in time
     * @return 				the revision of the repository
     *                      for that time
     * @throws SVNException if a failure occured while connecting to a repository 
     *                      or the user's authentication failed (see 
     *                      {@link org.tmatesoft.svn.core.SVNAuthenticationException})
     */
    public abstract long getDatedRevision(Date date) throws SVNException;
    
    /**
     * Returns unversioned revision properties for a particular revision.
     * Property names (keys) are mapped to their values. You may use  
     * <b>SVNRevisionProperty</b> constants to retrieve property values
     * from the map.
     *  
     * @param  revision 	a revision number 
     * @param  properties   if not <span class="javakeyword">null</span> then 	
     *                      properties will be placed in this map, otherwise 
     *                      a new map will be created  
     * @return 				a map containing unversioned revision properties
     * @throws SVNException in the following cases:
     *                      <ul>
     *                      <li><code>revision</code> number is invalid 
     * 						<li>there's no such <code>revision</code> at all
     * 						<li>a failure occured while connecting to a repository 
     *                      <li>the user authentication failed 
     *                      (see {@link org.tmatesoft.svn.core.SVNAuthenticationException})
     *                      </ul>
     * @see                 org.tmatesoft.svn.core.SVNRevisionProperty
     */
    public abstract SVNProperties getRevisionProperties(long revision, SVNProperties properties) throws SVNException;
    
    /**
     * Sets a revision property with the specified name to a new value. 
     * 
     * <p>
     * <b>NOTE:</b> revision properties are not versioned. So, the old values
     * may be lost forever.
     * 
     * @param  revision			the number of the revision which property is to
     * 							be changed
     * @param  propertyName		a revision property name
     * @param  propertyValue 	the value of the revision property  
     * @throws SVNException		in the following cases:
     *                          <ul>
     *                          <li>the repository is configured not to allow clients
     * 							to modify revision properties (e.g. a pre-revprop-change-hook
     *                          program is not found or failed)
     * 							<li><code>revision</code> is invalid or doesn't 
     * 							exist at all 
     *                          <li>a failure occured while connecting to a repository 
     *                          <li>the user authentication failed 
     *                          (see {@link org.tmatesoft.svn.core.SVNAuthenticationException})
     *                          </ul>
     * @see                     org.tmatesoft.svn.core.SVNRevisionProperty
     */

    public abstract void setRevisionPropertyValue(long revision, String propertyName, SVNPropertyValue propertyValue) throws SVNException;
    /**
     * Gets the value of an unversioned property. 
     * 
     * 
     * @param 	revision 		a revision number
     * @param 	propertyName 	a property name
     * @return 					a revision property value or <span class="javakeyword">null</span> 
     *                          if there's no such revision property 
     * @throws 	SVNException	in the following cases:
     *                          <ul>
     *                          <li><code>revision</code> number is invalid or
     * 							if there's no such <code>revision</code> at all.
     *                          <li>a failure occured while connecting to a repository 
     *                          <li>the user authentication failed 
     *                          (see {@link org.tmatesoft.svn.core.SVNAuthenticationException})
     *                          </ul>
     */

    public abstract SVNPropertyValue getRevisionPropertyValue(long revision, String propertyName) throws SVNException;
    
    /**
	 * Returns the kind of an item located at the specified path in
     * a particular revision. If the <code>path</code> does not exist 
     * under the specified <code>revision</code>, {@link org.tmatesoft.svn.core.SVNNodeKind#NONE SVNNodeKind.NONE} 
     * will be returned.
	 * 
     * <p>
     * The <code>path</code> arg can be both relative to the location of 
     * this driver and absolute to the repository root (starts with <code>"/"</code>).
     * 
     * @param  path				an item's path  
     * @param  revision			a revision number
     * @return 					the node kind for the given <code>path</code> at the given 
     * 							<code>revision</code>
     * @throws SVNException  	if a failure occured while connecting to a repository 
     *                          or the user's authentication failed (see 
     *                          {@link org.tmatesoft.svn.core.SVNAuthenticationException})
     */
    public abstract SVNNodeKind checkPath(String path, long revision) throws SVNException;
    
    /**
	 * Fetches the contents and/or properties of a file located at the specified path
	 * in a particular revision.
	 * 
	 * <p>
     * If <code>contents</code> arg is not <span class="javakeyword">null</span> it 
     * will be written with file contents.
     * 
     * <p>
	 * If <code>properties</code> arg is not <span class="javakeyword">null</span> it will 
     * receive the properties of the file.  This includes all properties: not just ones 
     * controlled by a user and stored in the repository filesystem, but also non-tweakable 
     * ones (e.g. 'wcprops', 'entryprops', etc.). Property names (keys) are mapped to property
     * values. 
	 *
     * <p>
     * The <code>path</code> arg can be both relative to the location of 
     * this driver and absolute to the repository root (starts with <code>"/"</code>).
     * 
     * <p>
     * If <code>revision</code> is invalid (negative), HEAD revision will be used. 
     * 
     * @param path 				a file path
     * @param revision 			a file revision
     * @param properties 		a file properties receiver map
     * @param contents 			an output stream to write the file contents to
     * @return 					the revision the file has been taken at
     * @throws SVNException		in the following cases:
     *                          <ul>
     * 							<li>there's	no such <code>path</code> in <code>revision</code>
     *                          <li>a failure occured while connecting to a repository 
     *                          <li>the user authentication failed 
     *                          (see {@link org.tmatesoft.svn.core.SVNAuthenticationException})
     *                          </ul>
     */
    public abstract long getFile(String path, long revision, SVNProperties properties, OutputStream contents) throws SVNException; 

    /**
     * Fetches the contents and/or properties of a directory located at the specified path
     * in a particular revision. 
     * <p>
     * This method is the same as {@link #getDir(String, long, SVNProperties, int, ISVNDirEntryHandler)} 
     * with <code>entryFields</code> parameter set to <code>DIRENT_ALL</code>.
     * 
     * @param  path 		a directory path   
     * @param  revision 	a directory revision 
     * @param  properties 	a directory properties receiver map
     * @param  handler 		a handler to process directory entries
     * @return 				the revision of the directory
     * @throws SVNException	in the following cases:
     *                      <ul>
     *                      <li><code>path</code> not found in the specified <code>revision</code>
     * 						<li><code>path</code> is not a directory
     *                      <li>a failure occured while connecting to a repository 
     *                      <li>the user authentication failed 
     *                      (see {@link org.tmatesoft.svn.core.SVNAuthenticationException})
     *                      </ul>
     * @see 				#getDir(String, long, boolean, Collection)
     * @see                 #getDir(String, long, SVNProperties, int, Collection)
     * @see                 org.tmatesoft.svn.core.SVNDirEntry
     */
    public abstract long getDir(String path, long revision, SVNProperties properties, ISVNDirEntryHandler handler) throws SVNException;
    
    /**
     * Fetches the contents and/or properties of a directory located at the specified path
     * in a particular revision with the possibility to specify fields of the entry to fetch. 
     * 
     * <p>
     * If <code>handler</code> arg is not <span class="javakeyword">null</span> it 
     * will be dispatched information of each directory entry represented by an 
     * <b>SVNDirEntry</b> object.
     *
     * <p>
     * If <code>properties</code> arg is not <span class="javakeyword">null</span> it will 
     * receive the properties of the file.  This includes all properties: not just ones 
     * controlled by a user and stored in the repository filesystem, but also non-tweakable 
     * ones (e.g. 'wcprops', 'entryprops', etc.). Property names (keys) are mapped to property
     * values. 
     *
     * <p>
     * The <code>path</code> arg can be both relative to the location of 
     * this driver and absolute to the repository root (starts with <code>"/"</code>).
     * 
     * <p>
     * If <code>revision</code> is invalid (negative), HEAD revision will be used. 
     * 
     * <b>NOTE:</b> you may not invoke operation methods of this <b>SVNRepository</b>
     * object from within the provided <code>handler</code>.
     * 
     * @param  path         a directory path   
     * @param  revision     a directory revision 
     * @param  properties   a directory properties receiver map
     * @param  entryFields  a combination of fields for the entry
     * @param  handler      a handler to process directory entries
     * @return              the revision of the directory
     * @throws SVNException in the following cases:
     *                      <ul>
     *                      <li><code>path</code> not found in the specified <code>revision</code>
     *                      <li><code>path</code> is not a directory
     *                      <li>a failure occured while connecting to a repository 
     *                      <li>the user authentication failed 
     *                      (see {@link org.tmatesoft.svn.core.SVNAuthenticationException})
     *                      </ul>
     * @see                 #getDir(String, long, boolean, Collection)
     * @see                 #getDir(String, long, SVNProperties, int, Collection)
     * @see                 org.tmatesoft.svn.core.SVNDirEntry
     */

    public abstract long getDir(String path, long revision, SVNProperties properties, int entryFields, ISVNDirEntryHandler handler) throws SVNException; 

    /**
     * Retrieves interesting file revisions for the specified file. 
	 * 
	 * <p>
	 * A file revision is represented by an <b>SVNFileRevision</b> object. Each
     * file revision is handled by the file revision handler provided. Only those 
     * revisions will be retrieved in which the file was changed.
	 * The iteration will begin at the first such revision starting from the 
	 * <code>startRevision</code> and so on - up to the <code>endRevision</code>.
	 * If the method succeeds, the provided <code>handler</code> will have
	 * been invoked at least once.
	 * 
	 * <p>
	 * For the first interesting revision the file contents  
     * will be provided to the <code>handler</code> as a text delta against an empty file.  
     * For the following revisions, the delta will be against the fulltext contents of the 
     * previous revision.
     *
     * <p>
     * The <code>path</code> arg can be both relative to the location of 
     * this driver and absolute to the repository root (starts with <code>"/"</code>).
	 * 
     * <p>
     * <b>NOTES:</b> 
     * <ul>
     * <li>you may not invoke methods of this <b>SVNRepository</b>
     *     object from within the provided <code>handler</code>
     * <li>this functionality is not available in pre-1.1 servers
     * </ul>
	 * 
	 * @param  path 			a file path 
	 * @param  startRevision 	a revision to start from 
	 * @param  endRevision   	a revision to stop at
	 * @param  handler 			a handler that processes file revisions passed  
	 * @return 					the number of retrieved file revisions  
	 * @throws SVNException		if a failure occured while connecting to a repository 
     *                          or the user's authentication failed (see 
     *                          {@link org.tmatesoft.svn.core.SVNAuthenticationException})
	 * @see 					#getFileRevisions(String, Collection, long, long)
	 * @see 					SVNFileRevision
	 * @since					SVN 1.1
	 */    
    public int getFileRevisions(String path, long startRevision, long endRevision, ISVNFileRevisionHandler handler) throws SVNException {
        return getFileRevisions(path, startRevision, endRevision, false, handler);
    }

    /**
     * Retrieves interesting file revisions for the specified file with possibility to include merged revisions. 
     * 
     * <p>
     * A file revision is represented by an <b>SVNFileRevision</b> object. Each
     * file revision is handled by the file revision handler provided. 
     * The iteration will begin at the first such revision starting from the 
     * <code>startRevision</code> and so on - up to the <code>endRevision</code>.
     * If <code>includeMergedRevisions</code> is <code>true</code>, then revisions which 
     * were result of a merge will be included as well.
     * If the method succeeds, the provided <code>handler</code> will have
     * been invoked at least once.
     * 
     * <p>
     * For the first interesting revision the file contents  
     * will be provided to the <code>handler</code> as a text delta against an empty file.  
     * For the following revisions, the delta will be against the fulltext contents of the 
     * previous revision.
     *
     * <p>
     * The <code>path</code> arg can be both relative to the location of 
     * this driver and absolute to the repository root (starts with <code>"/"</code>).
     * 
     * <p>
     * <b>NOTES:</b> 
     * <ul>
     * <li>you may not invoke methods of this <b>SVNRepository</b>
     *     object from within the provided <code>handler</code>
     * <li>this functionality is not available in pre-1.1 servers
     * </ul>
     * 
     * @param  path                     a file path 
     * @param  startRevision            a revision to start from 
     * @param  endRevision              a revision to stop at
     * @param  includeMergedRevisions   if is <code>true</code>, merged revisions will be returned as well
     * @param  handler                  a handler that processes file revisions passed  
     * @return                          the number of retrieved file revisions  
     * @throws SVNException             if a failure occured while connecting to a repository 
     *                                  or the user's authentication failed (see 
     *                                  {@link org.tmatesoft.svn.core.SVNAuthenticationException})
     * @see                             #getFileRevisions(String, Collection, long, long)
     * @see                             SVNFileRevision
     * @since                           SVNKit 1.2.0, SVN 1.5.0
     */  
    public int getFileRevisions(String path, long startRevision, long endRevision, 
            boolean includeMergedRevisions, ISVNFileRevisionHandler handler) throws SVNException {
        if (includeMergedRevisions) {
            assertServerIsMergeInfoCapable(null);
        }
        
        try {
            return getFileRevisionsImpl(path, startRevision, endRevision, includeMergedRevisions, handler);
        } catch (SVNException svne) {
            if (svne.getErrorMessage().getErrorCode() == SVNErrorCode.RA_NOT_IMPLEMENTED) {
                return getFileRevisionsFromLog(path, startRevision, endRevision, handler); 
            } 
            throw svne;
        }
    }

    /**
     * Checks that this object is connected to a mergeinfo capable repository. 
     * 
     * <p/>
     * If the repository that this object is used to access to does not support merge-tracking,
     * an {@link SVNException} is thrown with the error code {@link SVNErrorCode#UNSUPPORTED_FEATURE}.
     *  
     * <p/>
     * <code>pathOrURL</code> is used only in an error message. If <code>pathOrURL</code> is 
     * <span class="javakeyword">null</span>, it defaults to the {@link #getLocation() location} of this 
     * object.  
     * 
     * @param pathOrURL        path or URl string for an error message 
     * @throws SVNException    exception with {@link SVNErrorCode#UNSUPPORTED_FEATURE} error code - if 
     *                         the repository does not support merge-tracking 
     * @since                  1.2.0 
     */
    public void assertServerIsMergeInfoCapable(String pathOrURL) throws SVNException {
        boolean isMergeInfoCapable = hasCapability(SVNCapability.MERGE_INFO);
        if (!isMergeInfoCapable) {
            if (pathOrURL == null) {
                SVNURL sessionURL = getLocation();
                pathOrURL = sessionURL.toString();
            }
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNSUPPORTED_FEATURE, 
                    "Retrieval of mergeinfo unsupported by ''{0}''", pathOrURL);
            SVNErrorManager.error(err, SVNLogType.NETWORK);
        }
    }

    /**
	 * Traverses revisions history. In other words, collects per revision
     * information that includes the revision number, author, datestamp, 
     * log message and maybe a list of changed paths (optional). For each
     * revision this information is represented by an <b>SVNLogEntry</b> 
     * object. Such objects are passed to the provided <code>handler</code>. 
     * 
     * <p>
     * This method invokes <code>handler</code> on each log entry from
	 * <code>startRevision</code> to <code>endRevision</code>.
	 * <code>startRevision</code> may be greater or less than
	 * <code>endRevision</code>; this just controls whether the log messages are
	 * processed in descending or ascending revision number order.
	 * 
	 * <p>
	 * If <code>startRevision</code> or <code>endRevision</code> is invalid, it
	 * defaults to the youngest.
	 * 
	 * <p>
	 * If <code>targetPaths</code> has one or more elements, then
	 * only those revisions are processed in which at least one of <code>targetPaths</code> was
	 * changed (i.e., if a file text or properties changed; if dir properties
	 * changed or an entry was added or deleted). Each path is relative 
	 * to the repository location that this object is set to.
	 * 
	 * <p>
	 * If <code>changedPath</code> is <span class="javakeyword">true</span>, then each 
     * <b>SVNLogEntry</b> passed to the handler will contain info about all 
     * paths changed in that revision it represents. To get them call 
     * {@link org.tmatesoft.svn.core.SVNLogEntry#getChangedPaths()} that returns a map,
     * which keys are the changed paths and the values are <b>SVNLogEntryPath</b> objects.
     * If <code>changedPath</code> is <span class="javakeyword">false</span>, changed paths
     * info will not be provided. 
	 * 
	 * <p>
	 * If <code>strictNode</code> is <span class="javakeyword">true</span>, copy history will 
     * not be traversed (if any exists) when harvesting the revision logs for each path.
	 * 
     * <p>
     * Target paths can be both relative to the location of 
     * this driver and absolute to the repository root (starts with <code>"/"</code>).
	 * 
     * <p>
	 * <b>NOTE:</b> you may not invoke methods of this <b>SVNRepository</b>
     * object from within the provided <code>handler</code>.
	 * 
     * @param  targetPaths 		paths that mean only those revisions at which they were 
     * 							changed
     * @param  startRevision 	a revision to start from
     * @param  endRevision 		a revision to end at 
     * @param  changedPath 		if <span class="javakeyword">true</span> then
     *                          revision information will also include all changed paths per 
     *                          revision, otherwise not
     * @param  strictNode 		if <span class="javakeyword">true</span> then copy history (if any) is not 
     * 							to be traversed
     * @param  handler 			a caller's handler that will be dispatched log entry objects
     * @return 					the number of revisions traversed
     * @throws SVNException     if a failure occured while connecting to a repository 
     *                          or the user's authentication failed (see 
     *                          {@link org.tmatesoft.svn.core.SVNAuthenticationException})
     * @see                     #log(String[], Collection, long, long, boolean, boolean)
     * @see                     #log(String[], long, long, boolean, boolean, long, ISVNLogEntryHandler)
     * @see 					org.tmatesoft.svn.core.ISVNLogEntryHandler
     * @see 					org.tmatesoft.svn.core.SVNLogEntry
     * @see                     org.tmatesoft.svn.core.SVNLogEntryPath
     */
    public long log(String[] targetPaths, long startRevision, long endRevision, boolean changedPath, boolean strictNode,
            ISVNLogEntryHandler handler) throws SVNException {
        return log(targetPaths, startRevision, endRevision, changedPath, strictNode, 0, handler);
    }

    /**
     * Traverses revisions history. In other words, collects per revision
     * information that includes the revision number, author, datestamp, 
     * log message and maybe a list of changed paths (optional). For each
     * revision this information is represented by an <b>SVNLogEntry</b> 
     * object. Such objects are passed to the provided <code>handler</code>. 
     * 
     * <p>
     * This method invokes <code>handler</code> on each log entry from
     * <code>startRevision</code> to <code>endRevision</code>.
     * <code>startRevision</code> may be greater or less than
     * <code>endRevision</code>; this just controls whether the log messages are
     * processed in descending or ascending revision number order.
     * 
     * <p>
     * If <code>startRevision</code> or <code>endRevision</code> is invalid, it
     * defaults to the youngest.
     * 
     * <p>
     * If <code>targetPaths</code> has one or more elements, then
     * only those revisions are processed in which at least one of <code>targetPaths</code> was
     * changed (i.e., if a file text or properties changed; if dir properties
     * changed or an entry was added or deleted). Each path can be either absolute or relative 
     * to the repository location that this object is set to.
     * 
     * <p>
     * If <code>changedPath</code> is <span class="javakeyword">true</span>, then each 
     * <b>SVNLogEntry</b> passed to the handler will contain info about all 
     * paths changed in that revision it represents. To get them call 
     * {@link org.tmatesoft.svn.core.SVNLogEntry#getChangedPaths()} that returns a map,
     * which keys are the changed paths and the values are <b>SVNLogEntryPath</b> objects.
     * If <code>changedPath</code> is <span class="javakeyword">false</span>, changed paths
     * info will not be provided. 
     * 
     * <p>
     * If <code>strictNode</code> is <span class="javakeyword">true</span>, copy history will 
     * not be traversed (if any exists) when harvesting the revision logs for each path.
     * 
     * <p>
     * If <code>limit</code> is > 0 then only the first <code>limit</code> log entries
     * will be handled. Otherwise (i.e. if <code>limit</code> is 0) this number is ignored.
     * 
     * <p>
     * Target paths can be both relative to the location of 
     * this driver and absolute to the repository root (starts with <code>"/"</code>).
     * 
     * <p>
     * <b>NOTE:</b> you may not invoke methods of this <b>SVNRepository</b>
     * object from within the provided <code>handler</code>.
     * 
     * @param  targetPaths      paths that mean only those revisions at which they were 
     *                          changed
     * @param  startRevision    a revision to start from
     * @param  endRevision      a revision to end at 
     * @param  changedPath      if <span class="javakeyword">true</span> then
     *                          revision information will also include all changed paths per 
     *                          revision, otherwise not
     * @param  strictNode       if <span class="javakeyword">true</span> then copy history (if any) is not 
     *                          to be traversed
     * @param  limit            the maximum number of log entries to process
     * @param  handler          a caller's handler that will be dispatched log entry objects
     * @return                  the number of revisions traversed
     * @throws SVNException     if a failure occured while connecting to a repository 
     *                          or the user's authentication failed (see 
     *                          {@link org.tmatesoft.svn.core.SVNAuthenticationException})
     * @see                     #log(String[], Collection, long, long, boolean, boolean)
     * @see                     #log(String[], long, long, boolean, boolean, long, ISVNLogEntryHandler)
     * @see                     org.tmatesoft.svn.core.ISVNLogEntryHandler
     * @see                     org.tmatesoft.svn.core.SVNLogEntry
     * @see                     org.tmatesoft.svn.core.SVNLogEntryPath
     */
    public long log(String[] targetPaths, long startRevision, long endRevision, boolean changedPath, boolean strictNode, long limit,
            ISVNLogEntryHandler handler) throws SVNException {
        return log(targetPaths, startRevision, endRevision, changedPath, strictNode, limit, false, null, handler);
    }
    
    /**
     * Invokes <code>handler</code> on each log message from <code>startRevision</code> to 
     * <code>endRevision</code>. <code>startRevision</code> may be greater or less than <code>endRevision</code>;
     * this just controls whether the log messages are processed in descending
     * or ascending revision number order.
     * 
     * <p/>
     * If <code>startRevision</code> or <code>endRevision</code> is invalid (<code>&lt;0</code>), it defaults 
     * to youngest.
     * 
     * <p/>
     * If <code>targetPaths</code> is non-<span class="javakeyword">null</span> and has one or more elements, 
     * then shows only revisions in which at least one of <code>targetPaths</code> was changed (i.e., if
     * file, text or props changed; if dir, props changed or an entry was added or deleted). Each path may be 
     * either absolut (starts with '/') or relative to this object's {@link #getLocation() location}.
     * 
     * <p/>
     * If <code>limit</code> is non-zero only invokes <code>handler</code> on the first <code>limit</code>
     * logs.
     * 
     * <p/>
     * If <code>discoverChangedPaths</code> is set, then each call to <code>handler</code> passes an 
     * {@link org.tmatesoft.svn.core.SVNLogEntry} object with non-empty  
     * {@link org.tmatesoft.svn.core.SVNLogEntry#getChangedPaths() changed paths map};
     * the hash's keys are all the paths committed in that revision.
     * Otherwise, each call to <code>handler</code> passes <span class="javakeyword">null</span> for changed paths.
     * 
     * <p/>
     * If <code>strictNodeHistory</code> is set, copy history will not be traversed (if any exists) when 
     * harvesting the revision logs for each path.
     *
     * <p/>
     * If <code>includeMergedRevisions</code> is set, log information for revisions which have been merged to 
     * <code>targetPahts</code> will also be returned.
     * 
     * <p/> 
     * Refer to {@link org.tmatesoft.svn.core.SVNLogEntry#hasChildren()} for additional information on how 
     * to handle mergeinfo information during a log operation.
     * 
     * <p/>
     * If <code>revisionProperties</code> is <span class="javakeyword">null</span>, retrieves all revprops; else, 
     * retrieves only the revprops named in the array (i.e. retrieves none if the array is empty).
     *
     * <p/>
     * If any invocation of <code>handler</code> throws an exception, then throws that exception immediately and 
     * without wrapping it.
     * 
     * <p/>
     * Note: the caller may not invoke any repository access operations using from within <code>handler</code>.
     * 
     * <p/>
     * Note: if <code>targetPahts</code> is <span class="javakeyword">null</span> or empty, the result depends 
     * on the server. Pre-1.5 servers will send nothing; 1.5 servers will effectively perform the log operation 
     * on the root of the repository. This behavior may be changed in the future to ensure consistency across 
     * all pedigrees of server.
     * 
     * <p/>
     * Note: pre-1.5 servers do not support custom revision properties retrieval; if 
     * <code>revisionProperties</code> is <span class="javakeyword">null</span> or contains a revision property 
     * other than {@link SVNRevisionProperty#AUTHOR}, {@link SVNRevisionProperty#DATE}, 
     * {@link SVNRevisionProperty#LOG}, an exception with the {@link SVNErrorCode#RA_NOT_IMPLEMENTED} error code 
     * is thrown.
     *
     * @param  targetPaths               paths that mean only those revisions at which they were 
     *                                   changed
     * @param  startRevision             a revision to start from
     * @param  endRevision               a revision to end at 
     * @param  discoverChangedPaths      if <span class="javakeyword">true</span> then revision information will 
     *                                   also include all changed paths per revision, otherwise not
     * @param  strictNodeHistory         if <span class="javakeyword">true</span> then copy history (if any) is not 
     *                                   to be traversed
     * @param  limit                     the maximum number of log entries to process
     * @param  includeMergedRevisions    whether mergeinfo information should be taken into account or not
     * @param  revisionProperties        revision properties to fetch with log entries
     * @param  handler                   a caller's handler that will be dispatched log entry objects
     * @return                           the number of revisions traversed
     * @throws SVNException              in the following cases:
     *                                   <ul>
     *                                   <li/>exception with {@link SVNErrorCode#FS_NO_SUCH_REVISION} error code - 
     *                                   if <code>startRevision</code> or <code>endRevision</code> is a 
     *                                   non-existent revision
     *                                   <li/>exception with {@link SVNErrorCode#RA_NOT_IMPLEMENTED} error code -
     *                                   in case of a pre-1.5 server and custom revision properties requested
     *                                   </ul> 
     * @since                            1.2.0, New in Subversion 1.5.0
     */
    public long log(String[] targetPaths, long startRevision, long endRevision, boolean discoverChangedPaths, 
            boolean strictNodeHistory, long limit, boolean includeMergedRevisions, String[] revisionProperties, 
            ISVNLogEntryHandler handler) throws SVNException {
        if (includeMergedRevisions) {
            assertServerIsMergeInfoCapable(null);
        }
        return logImpl(targetPaths, startRevision, endRevision, discoverChangedPaths, strictNodeHistory, limit, 
                includeMergedRevisions, revisionProperties, handler);
    }

    /**
	 * Gets entry locations in time. The location of an entry in a repository
     * may change from revision to revision. This method allows to trace entry locations 
     * in different revisions. 
     * 
     * <p>
     * For each interesting revision (taken from <code>revisions</code>) an entry location
     * is represented by an <b>SVNLocationEntry</b> object which is passed to the provided
     * <code>handler</code>. Each <b>SVNLocationEntry</b> object represents a repository path 
     * in a definite revision.
	 * 
     * <p>
     * The <code>path</code> arg can be both relative to the location of 
     * this driver and absolute to the repository root (starts with <code>"/"</code>).
     * 
     * <p>
     * <b>NOTES:</b> 
     * <ul>
     * <li>you may not invoke methods of this <b>SVNRepository</b>
     *     object from within the provided <code>handler</code>
     * <li>this functionality is not available in pre-1.1 servers
     * </ul>
     * 
     * @param  path			an item's path 
     * @param  pegRevision 	a revision in which <code>path</code> is first 
     *                      looked up 
     * @param  revisions 	an array of numbers of interesting revisions in which
     *                      locations are looked up. If <code>path</code> 
     * 						doesn't exist in an interesting revision, that revision 
     *                      will be ignored. 
     * @param  handler 		a location entry handler that will handle all found entry
     * 						locations
     * @return 				the number of the entry locations found 
     * @throws SVNException in the following cases:
     *                      <ul>
     *                      <li><code>path</code> not found in the specified <code>pegRevision</code>
     *                      <li><code>pegRevision</code> is not valid
     *                      <li>a failure occured while connecting to a repository 
     *                      <li>the user authentication failed 
     *                      (see {@link org.tmatesoft.svn.core.SVNAuthenticationException})
     *                      </ul>
     * @see                 #getLocations(String, Collection, long, long[])
     * @see                 #getLocations(String, Map, long, long[])                      
     * @see 				ISVNLocationEntryHandler
     * @see 				SVNLocationEntry
     * @since               SVN 1.1
     */
    public int getLocations(String path, long pegRevision, long[] revisions, ISVNLocationEntryHandler handler) throws SVNException {
        try {
            return getLocationsImpl(path, pegRevision, revisions, handler);
        } catch (SVNException svne) {
            if (svne.getErrorMessage().getErrorCode() == SVNErrorCode.RA_NOT_IMPLEMENTED) {
                return getLocationsFromLog(path, pegRevision, revisions, handler);
            }
            throw svne;
        }
    }

    
    /**
     * Calls <code>handler</code> for each segment in the location history of <code>path</code> in 
     * <code>pegRevision</code>, working backwards in time from <code>startRevision</code> to 
     * <code>endRevision</code>.
     * 
     * <p/>
     * <code>endRevision</code> may be invalid (<code>&lt;0</code>) to indicate that you want to trace the 
     * history of the object to its origin.
     * 
     * <p/>
     * <code>startRevision</code> may be invalid to indicate "the HEAD revision". 
     * Otherwise, <code>startRevision</code> must be younger than <code>endRevision</code> (unless 
     * <code>endRevision</code> is invalid).
     * 
     * <p/>
     * <code>pegRevision</code> may be invalid to indicate "the HEAD revision", and must evaluate to be at 
     * least as young as <code>startRevision</code>.
     * 
     * @param path             repository path 
     * @param pegRevision      revision in which <code>path</code> is valid
     * @param startRevision    revision range start 
     * @param endRevision      revision range end
     * @param handler          caller's segment handler implementation 
     * @return                 number of revisions covered by all segments found
     * @throws SVNException    
     * @since                  1.2.0, New in Subversion 1.5.0
     */
    public long getLocationSegments(String path, long pegRevision, long startRevision, long endRevision, 
            ISVNLocationSegmentHandler handler) throws SVNException {
        try {
            return getLocationSegmentsImpl(path, pegRevision, startRevision, endRevision, handler);
        } catch (SVNException svne) {
            if (svne.getErrorMessage().getErrorCode() == SVNErrorCode.RA_NOT_IMPLEMENTED) {
                return getLocationSegmentsFromLog(path, pegRevision, startRevision, endRevision, handler);
            }
            throw svne;
        }
    }

    /**
     * Calls <code>handler</code> for each segment in the location history of <code>path</code> in 
     * <code>pegRevision</code>, working backwards in time from <code>startRevision</code> to 
     * <code>endRevision</code>.
     * 
     * <p/>
     * The same as {@link #getLocationSegments(String, long, long, long, ISVNLocationSegmentHandler)} except for 
     * this method returns a list of all the segments fetched for <code>path</code>. The list will be sorted 
     * into an ascenging order (segments with greater start revisions will be placed in the head of the list). 
     * 
     * @param path             repository path 
     * @param pegRevision      revision in which <code>path</code> is valid
     * @param startRevision    revision range start 
     * @param endRevision      revision range end
     * @return                 number of revisions covered by all segments found
     * @throws SVNException 
     * @since                  1.2.0, New in Subversion 1.5.0
     */
    public List<SVNLocationSegment> getLocationSegments(String path, long pegRevision, long startRevision, long endRevision) throws SVNException {
        
        final List result = new LinkedList();
        getLocationSegments(path, pegRevision, startRevision, endRevision, new ISVNLocationSegmentHandler() {
            public void handleLocationSegment(SVNLocationSegment locationSegment) throws SVNException {
                result.add(locationSegment);
                getCanceller().checkCancelled();
            }
        });
        
        Collections.sort(result, new Comparator() {
            public int compare(Object o1, Object o2) {
                if (o1 == o2) {
                    return 0;
                } else if (o1 == null) {
                    return -1;
                } else if (o2 == null) {
                    return 1;
                } 
                SVNLocationSegment segment1 = (SVNLocationSegment) o1;
                SVNLocationSegment segment2 = (SVNLocationSegment) o2;
                if (segment1.getStartRevision() == segment2.getStartRevision()) {
                    return 0;
                }
                return segment1.getStartRevision() < segment2.getStartRevision() ? -1 : 1;
            }
        });
        
        return result; 
    }

    /**
     * Retrieves and returns interesting file revisions for the specified file. 
     * 
     * <p>
     * A file revision is represented by an <b>SVNFileRevision</b> object. 
     * Only those revisions will be retrieved in which the file was changed.
     * The iteration will begin at the first such revision starting from the 
     * <code>startRevision</code> and so on - up to the <code>endRevision</code>.
     * 
     * <p>
     * The <code>path</code> arg can be both relative to the location of 
     * this driver and absolute to the repository root (starts with <code>"/"</code>).
     * 
     * <p>
     * <b>NOTE:</b> this functionality is not available in pre-1.1 servers
     *   
     * @param  path         a file path 
     * @param  revisions 	if not <span class="javakeyword">null</span> this collection
     *                      will receive all the fetched file revisions   
     * @param  sRevision 	a revision to start from
     * @param  eRevision 	a revision to stop at
     * @return 				a collection that keeps	file revisions - {@link SVNFileRevision} instances 
     * @throws SVNException if a failure occured while connecting to a repository 
     *                      or the user's authentication failed (see 
     *                      {@link org.tmatesoft.svn.core.SVNAuthenticationException})
     * @see                 #getFileRevisions(String, long, long, ISVNFileRevisionHandler)
     * @see                 SVNFileRevision
     * @since               SVN 1.1
     */
    public Collection getFileRevisions(String path, Collection revisions, long sRevision, long eRevision) throws SVNException {
        final Collection result = revisions != null ? revisions : new LinkedList();
        ISVNFileRevisionHandler handler = new ISVNFileRevisionHandler() {
            public void openRevision(SVNFileRevision fileRevision) throws SVNException {
                result.add(fileRevision);
            }
            public void applyTextDelta(String path, String baseChecksum) throws SVNException {
            }
            public OutputStream textDeltaChunk(String token, SVNDiffWindow diffWindow) throws SVNException {
                return SVNFileUtil.DUMMY_OUT;
            }
            public void textDeltaEnd(String token) throws SVNException {
            }
            public void closeRevision(String token) throws SVNException {
            }
        };
        getFileRevisions(path, sRevision, eRevision, handler);
        return result;
    }
    
    /**
     * Fetches the contents and properties of a directory located at the specified path
     * in a particular revision. Information of each directory 
     * entry is represented by a single <b>SVNDirEntry</b> object.
     * <p>
     * This method is the same as {@link #getDir(String, long, SVNProperties, int, Collection)} with 
     * <code>entryFields</code> parameter set to <code>DIRENT_ALL</code>.
     * <p>
     * The <code>path</code> arg can be both relative to the location of 
     * this driver and absolute to the repository root (starts with <code>"/"</code>).
     * 
     * @param  path         a directory path   
     * @param  revision     a revision number 
     * @param  properties   if not <span class="javakeyword">null</span> then all
     *                      directory properties (including non-tweakable ones)
     *                      will be put into this map (where keys are property names
     *                      and mappings are property values)
     * @param  dirEntries 	if not <span class="javakeyword">null</span> then this
     *                      collection receives fetched dir entries (<b>SVNDirEntry</b> objects)
     * @return 				a collection containing fetched directory entries (<b>SVNDirEntry</b> objects)
     * @throws SVNException in the following cases:
     *                      <ul>
     *                      <li><code>path</code> not found in the specified <code>revision</code>
     *                      <li><code>path</code> is not a directory
     *                      <li>a failure occured while connecting to a repository 
     *                      <li>the user authentication failed 
     *                      (see {@link org.tmatesoft.svn.core.SVNAuthenticationException})
     *                      </ul>
     * @see                 #getDir(String, long, SVNProperties, int, Collection)
     * @see                 #getDir(String, long, boolean, Collection)
     * @see                 org.tmatesoft.svn.core.SVNDirEntry
     */
    public Collection getDir(String path, long revision, SVNProperties properties, Collection dirEntries) throws SVNException {
        return getDir(path, revision, properties, SVNDirEntry.DIRENT_ALL, dirEntries);
    }

    /**
     * Fetches the contents and properties of a directory located at the specified path
     * in a particular revision with the possibility to specify fields of the entry to fetch.
     * Information of each directory entry is represented by a single <b>SVNDirEntry</b> object.
     *
     * <p>
     * The <code>path</code> arg can be both relative to the location of 
     * this driver and absolute to the repository root (starts with <code>"/"</code>).
     * 
     * @param  path         a directory path   
     * @param  revision     a revision number 
     * @param  properties   if not <span class="javakeyword">null</span> then all
     *                      directory properties (including non-tweakable ones)
     *                      will be put into this map (where keys are property names
     *                      and mappings are property values)
     * @param  entryFields  a combination of fields for the entry
     * @param  dirEntries   if not <span class="javakeyword">null</span> then this
     *                      collection receives fetched dir entries (<b>SVNDirEntry</b> objects)
     * @return              a collection containing fetched directory entries (<b>SVNDirEntry</b> objects)
     * @throws SVNException in the following cases:
     *                      <ul>
     *                      <li><code>path</code> not found in the specified <code>revision</code>
     *                      <li><code>path</code> is not a directory
     *                      <li>a failure occured while connecting to a repository 
     *                      <li>the user authentication failed 
     *                      (see {@link org.tmatesoft.svn.core.SVNAuthenticationException})
     *                      </ul>
     * @see                 #getDir(String, long, SVNProperties, int, Collection)
     * @see                 #getDir(String, long, boolean, Collection)
     * @see                 org.tmatesoft.svn.core.SVNDirEntry
     */
    public Collection<SVNDirEntry> getDir(String path, long revision, SVNProperties properties, int entryFields, Collection dirEntries) throws SVNException {
        final Collection result = dirEntries != null ? dirEntries : new LinkedList();
        ISVNDirEntryHandler handler;
        handler = new ISVNDirEntryHandler() {
            public void handleDirEntry(SVNDirEntry dirEntry) {
                result.add(dirEntry);
            }
        };
        getDir(path, revision, properties, entryFields, handler);
        return result;
    }
    
    /**
     * Fetches the contents of a directory into the provided 
     * collection object and returns the directory entry itself. 
     * 
     * <p>
     * If <code>entries</code> arg is not <span class="javakeyword">null</span> it 
     * receives the directory entries. Information of each directory entry is 
     * represented by an <b>SVNDirEntry</b> object.
     * 
     * <p>
     * The <code>path</code> arg can be both relative to the location of 
     * this driver and absolute to the repository root (starts with <code>"/"</code>).
     * 
     * @param  path                    a directory path   
     * @param  revision                a revision number 
     * @param  includeCommitMessages   if <span class="javakeyword">true</span> then
     *                                 dir entries (<b>SVNDirEntry</b> objects) will be supplied 
     *                                 with commit log messages, otherwise not
     * @param  entries                 a collection that receives fetched dir entries                 
     * @return                         the directory entry itself which contents
     *                                 are fetched into <code>entries</code>
     * @throws SVNException            in the following cases:
     *                                 <ul>
     *                                 <li><code>path</code> not found in the specified <code>revision</code>
     *                                 <li><code>path</code> is not a directory
     *                                 <li>a failure occured while connecting to a repository 
     *                                 <li>the user authentication failed 
     *                                 (see {@link org.tmatesoft.svn.core.SVNAuthenticationException})
     *                                 </ul>
     * @see                            #getDir(String, long, SVNProperties, ISVNDirEntryHandler)
     * @see                            #getDir(String, long, SVNProperties, Collection)
     * @see                            org.tmatesoft.svn.core.SVNDirEntry
     */
    public abstract SVNDirEntry getDir(String path, long revision, boolean includeCommitMessages, Collection entries) throws SVNException;

    /**
     * Traverses revisions history and returns a collection of log entries. In 
     * other words, collects per revision information that includes the revision number, 
     * author, datestamp, log message and maybe a list of changed paths (optional). For each
     * revision this information is represented by an <b>SVNLogEntry</b>. 
     * object. 
     * 
     * <p>
     * <code>startRevision</code> may be greater or less than
     * <code>endRevision</code>; this just controls whether the log messages are
     * processed in descending or ascending revision number order.
     * 
     * <p>
     * If <code>startRevision</code> or <code>endRevision</code> is invalid, it
     * defaults to the youngest.
     * 
     * <p>
     * If <code>targetPaths</code> has one or more elements, then
     * only those revisions are processed in which at least one of <code>targetPaths</code> was
     * changed (i.e., if a file text or properties changed; if dir properties
     * changed or an entry was added or deleted). Each path is relative 
     * to the repository location that this object is set to.
     * 
     * <p>
     * If <code>changedPath</code> is <span class="javakeyword">true</span>, then each 
     * <b>SVNLogEntry</b> object is supplied with info about all 
     * paths changed in that revision it represents. To get them call 
     * {@link org.tmatesoft.svn.core.SVNLogEntry#getChangedPaths()} that returns a map,
     * which keys are the changed paths and the mappings are <b>SVNLogEntryPath</b> objects.
     * If <code>changedPath</code> is <span class="javakeyword">false</span>, changed paths
     * info will not be provided. 
     * 
     * <p>
     * If <code>strictNode</code> is <span class="javakeyword">true</span>, copy history will 
     * not be traversed (if any exists) when harvesting the revision logs for each path.
     * 
     * <p>
     * Target paths can be both relative to the location of 
     * this driver and absolute to the repository root (starts with <code>"/"</code>).
     * 
     * @param  targetPaths      paths that mean only those revisions at which they were 
     *                          changed
     * @param  entries 			if not <span class="javakeyword">null</span> then this collection
     *                          will receive log entries
     * @param  startRevision    a revision to start from
     * @param  endRevision      a revision to end at 
     * @param  changedPath      if <span class="javakeyword">true</span> then
     *                          revision information will also include all changed paths per 
     *                          revision, otherwise not
     * @param  strictNode       if <span class="javakeyword">true</span> then copy history (if any) is not 
     *                          to be traversed
     * @return 					a collection with log entries
     * @throws SVNException     if a failure occured while connecting to a repository 
     *                          or the user's authentication failed (see 
     *                          {@link org.tmatesoft.svn.core.SVNAuthenticationException})
     * @see                     #log(String[], long, long, boolean, boolean, ISVNLogEntryHandler)
     * @see                     #log(String[], long, long, boolean, boolean, long, ISVNLogEntryHandler)
     * @see                     org.tmatesoft.svn.core.ISVNLogEntryHandler
     * @see                     org.tmatesoft.svn.core.SVNLogEntry
     * @see                     org.tmatesoft.svn.core.SVNLogEntryPath
     */
    public Collection log(String[] targetPaths, Collection entries, long startRevision, long endRevision, boolean changedPath, boolean strictNode) throws SVNException {
        final Collection result = entries != null ? entries : new LinkedList();
        log(targetPaths, startRevision, endRevision, changedPath, strictNode, new ISVNLogEntryHandler() {
            public void handleLogEntry(SVNLogEntry logEntry) {
                result.add(logEntry);
            }        
        });
        return result;
    }
    
    /**
     * Gets entry locations in time. The location of an entry in a repository
     * may change from revision to revision. This method allows to trace entry locations 
     * in different revisions. 
     * 
     * <p>
     * For each interesting revision (taken from <code>revisions</code>) an entry location
     * is represented by an <b>SVNLocationEntry</b> object. Each <b>SVNLocationEntry</b> 
     * object represents a repository path in a definite revision.
     * 
     * <p>
     * The <code>path</code> arg can be both relative to the location of 
     * this driver and absolute to the repository root (starts with <code>"/"</code>).
     * 
     * <p>
     * <b>NOTE:</b> this functionality is not available in pre-1.1 servers
     * 
     * @param  path         an item's path
     * @param  entries 		if not <span class="javakeyword">null</span> then this 
     *                      collection object receives entry locations
     * @param  pegRevision  a revision in which <code>path</code> is first 
     *                      looked up 
     * @param  revisions    an array of numbers of interesting revisions in which
     *                      locations are looked up. If <code>path</code> 
     *                      doesn't exist in an interesting revision, that revision 
     *                      will be ignored. 
     * @return 				a collection with retrieved entry locations
     * @throws SVNException in the following cases:
     *                      <ul>
     *                      <li><code>path</code> not found in the specified <code>pegRevision</code>
     *                      <li><code>pegRevision</code> is not valid
     *                      <li>a failure occured while connecting to a repository 
     *                      <li>the user authentication failed 
     *                      (see {@link org.tmatesoft.svn.core.SVNAuthenticationException})
     *                      </ul>
     * @see                 #getLocations(String, long, long[], ISVNLocationEntryHandler)
     * @see                 #getLocations(String, Map, long, long[])
     * @see 				SVNLocationEntry
     * @see 				ISVNLocationEntryHandler
     * @since               SVN 1.1
     */
    public Collection getLocations(String path, Collection entries, long pegRevision, long[] revisions) throws SVNException {
        final Collection result = entries != null ? entries : new LinkedList();
        getLocations(path, pegRevision, revisions, new ISVNLocationEntryHandler() {
            public void handleLocationEntry(SVNLocationEntry locationEntry) {
                result.add(locationEntry);
            } 
        });
        return result;        
    }

    /**
     * Gets entry locations in time. The location of an entry in a repository
     * may change from revision to revision. This method allows to trace entry locations 
     * in different revisions. 
     * 
     * <p>
     * For each interesting revision (taken from <code>revisions</code>) an entry location
     * is represented by an <b>SVNLocationEntry</b> object. Each <b>SVNLocationEntry</b> 
     * object represents a repository path in a definite revision.
     * 
     * <p>
     * The <code>path</code> arg can be both relative to the location of 
     * this driver and absolute to the repository root (starts with <code>"/"</code>).
     * 
     * <p>
     * <b>NOTE:</b> this functionality is not available in pre-1.1 servers
     * 
     * @param  path         an item's path
     * @param  entries      if not <span class="javakeyword">null</span> then this 
     *                      map object receives entry locations (which keys are revision
     *                      numbers as Longs and mappings are entry locations objects)
     * @param  pegRevision  a revision in which <code>path</code> is first 
     *                      looked up 
     * @param  revisions    an array of numbers of interesting revisions in which
     *                      locations are looked up. If <code>path</code> 
     *                      doesn't exist in an interesting revision, that revision 
     *                      will be ignored. 
     * @return              a map (which keys are revision numbers as Longs and mappings 
     *                      are entry locations objects) with collected entry locations            
     * @throws SVNException in the following cases:
     *                      <ul>
     *                      <li><code>path</code> not found in the specified <code>pegRevision</code>
     *                      <li><code>pegRevision</code> is not valid
     *                      <li>a failure occured while connecting to a repository 
     *                      <li>the user authentication failed 
     *                      (see {@link org.tmatesoft.svn.core.SVNAuthenticationException})
     *                      </ul>
     * @see                 #getLocations(String, long, long[], ISVNLocationEntryHandler)
     * @see                 #getLocations(String, Collection, long, long[])
     * @see                 SVNLocationEntry
     * @see                 ISVNLocationEntryHandler
     * @since               SVN 1.1
     */
    public Map getLocations(String path, Map entries, long pegRevision, long[] revisions) throws SVNException {
        final Map result = entries != null ? entries : new SVNHashMap();
        getLocations(path, pegRevision, revisions, new ISVNLocationEntryHandler() {
            public void handleLocationEntry(SVNLocationEntry locationEntry) {
                result.put(new Long(locationEntry.getRevision()), locationEntry);
            } 
        });
        return result;        
    }
	
	/**
	 * Calculates the differences between two items. 
	 *
     * <p>
     * <code>target</code> is the name (one-level path component) of an entry that will restrict
     * the scope of the diff operation to this entry. In other words <code>target</code> is a child entry of the 
     * directory represented by the repository location to which this object is set. For
     * example, if we have something like <code>"/dirA/dirB"</code> in a repository, then
     * this object's repository location may be set to <code>"svn://host:port/path/to/repos/dirA"</code>,
     * and <code>target</code> may be <code>"dirB"</code>.
     * 
     * <p>
     * If <code>target</code> is <span class="javakeyword">null</span> or empty (<code>""</code>)
	 * then the scope of the diff operation is the repository location to which
     * this object is set.
     * 
     * <p>
     * The <code>reporter</code> is used to describe the state of the target item(s) (i.e. 
     * items' revision numbers). All the paths described by the <code>reporter</code>
     * should be relative to the repository location to which this object is set. 
     * 
     * <p>
     * After that the <code>editor</code> is used to carry out all the work on 
     * evaluating differences against <code>url</code>. This <code>editor</code> contains 
     * knowledge of where the change will begin (when {@link ISVNEditor#openRoot(long) ISVNEditor.openRoot()} 
     * is called).
	 * 
	 * <p>
	 * If <code>ignoreAncestry</code> is <span class="javakeyword">false</span> then
     * the ancestry of the paths being diffed is taken into consideration - they
     * are treated as related. In this case, for example, if calculating differences between 
     * two files with identical contents but different ancestry, 
     * the entire contents of the target file is considered as having been removed and 
     * added again. 
     * 
     * <p>
     * If <code>ignoreAncestry</code> is <span class="javakeyword">true</span>
     * then the two paths are merely compared ignoring the ancestry.
	 * 
     * <p>
     * <b>NOTE:</b> you may not invoke methods of this <b>SVNRepository</b>
     * object from within the provided <code>reporter</code> and <code>editor</code>.
	 *
	 * <p/>
	 * This method is analogous to 
	 * <code>diff(url, targetRevision, revision, target, ignoreAncestry, SVNDepth.fromRecurse(recursive), getContents, reporter, editor)</code>.
	 * 
	 * @param  url 				a repository location of the entry against which 
     *                          differences are calculated 
	 * @param  targetRevision   a revision number of the entry located at the 
     *                          specified <code>url</code>; defaults to the
     *                          latest revision (HEAD) if this arg is invalid
     * @param  revision         a revision number of the repository location to which 
     *                          this driver object is set
	 * @param  target 			a target entry name (optional)
	 * @param  ignoreAncestry 	if <span class="javakeyword">true</span> then
     *                          the ancestry of the two entries to be diffed is 
     *                          ignored, otherwise not 
     * @param  recursive        if <span class="javakeyword">true</span> and the diff scope
     *                          is a directory, descends recursively, otherwise not 
     * @param  getContents      if <span class="javakeyword">false</span> contents (diff windows) will not be sent to 
     *                          the editor. 
     * @param  reporter 		a caller's reporter
     * @param  editor 			a caller's editor
     * @throws SVNException     in the following cases:
     *                          <ul>
     *                          <li><code>url</code> not found neither in the specified 
     *                          <code>revision</code> nor in the HEAD revision
     *                          <li>a failure occured while connecting to a repository 
     *                          <li>the user authentication failed 
     *                          (see {@link org.tmatesoft.svn.core.SVNAuthenticationException})
     *                          </ul>
     * @deprecated              use {@link #diff(SVNURL, long, long, String, boolean, SVNDepth, boolean, ISVNReporterBaton, ISVNEditor)}
     *                          instead
	 */
    public void diff(SVNURL url, long targetRevision, long revision, String target, boolean ignoreAncestry, 
            boolean recursive, boolean getContents, ISVNReporterBaton reporter, ISVNEditor editor) throws SVNException {
        diff(url, targetRevision, revision, target, ignoreAncestry, SVNDepth.fromRecurse(recursive), 
                getContents, reporter, editor);
    }

    /**
     * Asks this repository access object to 'diff' a working copy against <code>targetRevision</code>.
     * 
     * <p/>
     * Note: this method cannot be used to diff a single file, only a directory. 
     * See the {@link #update(SVNURL, long, String, SVNDepth, ISVNReporterBaton, ISVNEditor)} method for more 
     * details.
     * 
     * <p/>
     * The client initially provides an <code>editor</code>; this editor contains knowledge of where the common 
     * diff root is in the working copy (when {@link ISVNEditor#openRoot(long)} is called).
     * 
     * <p/>
     * In return, the client receives a {@link ISVNReporter} object in his {@link ISVNReporterBaton}
     * implementation. The client then describes its working copy by making calls into the reporter object.
     * 
     * <p/>
     * When finished, the client calls {@link ISVNReporter#finishReport()}. This <code>SVNRepository</code> 
     * object then does a complete drive of <code>editor</code>, ending with {@link ISVNEditor#closeEdit()}, to 
     * transmit the diff.
     * 
     * <p/>
     * <code>target</code> is an optional single path component that will restrict the scope of things affected 
     * by the switch to an entry in the directory represented by the {@link #getLocation() location} of this 
     * object, or <span class="javakeyword">null</span> if the entire directory is meant to be switched.
     * 
     * <p/>
     * The working copy will be diffed against <code>url</code> as it exists in revision 
     * <code>targeRevision</code>, or as it is in HEAD if <code>targetRevision</code> is invalid (<code>&lt;0</code>).
     * 
     * <p/>
     * Use <code>ignoreAncestry</code> to control whether or not items being diffed will be checked for 
     * relatedness first. Unrelated items are typically transmitted to the editor as a deletion of one thing
     * and the addition of another, but if this flag is <span class="javakeyword">true</span>,
     * unrelated items will be diffed as if they were related.
     * 
     * <p/>
     * Diffs only as deeply as <code>depth</code> indicates.
     *
     * <p/>
     * The caller may not perform any repository access operations using this <code>SVNRepository</code> object 
     * before finishing the report, and may not perform any repository access operations using this 
     * <code>SVNRepository</code> object from within the editing operations of <code>editor</code>.
     *
     * <p/>
     * <code>getContents</code> instructs the driver of the <code>editor</code> to enable the generation of 
     * text deltas. If <code>getContents</code> is <span class="javakeyword">false</span> the <code>editor</code>'s
     * {@link ISVNDeltaConsumer#textDeltaChunk(String, SVNDiffWindow)} method will be called once with  
     * {@link SVNDiffWindow#EMPTY}.
     *
     * <p/>
     * Note: the reporter provided by this function does NOT supply copy-from information to the diff editor 
     * callbacks.
     * 
     * <p/>
     * Note: in order to prevent pre-1.5 servers from doing more work than needed, and sending too much data 
     * back, a pre-1.5 'recurse' directive may be sent to the server, based on <code>depth</code>.
     * 
     * @param  url              a repository location of the entry against which 
     *                          differences are calculated 
     * @param  targetRevision   a revision number of the entry located at the 
     *                          specified <code>url</code>; defaults to the
     *                          latest revision (HEAD) if this arg is invalid
     * @param  revision         a revision number of the repository location to which 
     *                          this driver object is set
     * @param  target           a target entry name (optional)
     * @param  ignoreAncestry   if <span class="javakeyword">true</span> then
     *                          the ancestry of the two entries to be diffed is 
     *                          ignored, otherwise not 
     * @param  depth            tree depth to process 
     * @param  getContents      if <span class="javakeyword">false</span> contents (diff windows) will not be sent to 
     *                          the editor 
     * @param  reporter         a caller's reporter
     * @param  editor           a caller's editor
     * @throws SVNException     in the following cases:
     *                          <ul>
     *                          <li><code>url</code> not found neither in the specified 
     *                          <code>revision</code> nor in the HEAD revision
     *                          <li>a failure occured while connecting to a repository 
     *                          <li>the user authentication failed 
     *                          (see {@link org.tmatesoft.svn.core.SVNAuthenticationException})
     *                          </ul>
     * @since                   1.2.0, New in Subversion 1.5.0
     */
    public abstract void diff(SVNURL url, long targetRevision, long revision, String target, 
            boolean ignoreAncestry, SVNDepth depth, boolean getContents, ISVNReporterBaton reporter, 
            ISVNEditor editor) throws SVNException;

    /**
     * This method is analogous to 
     * <code>diff(SVNURL, long, long, String, boolean, boolean, boolean, ISVNReporterBaton, ISVNEditor)</code>.
     * 
     * @param url 
     * @param targetRevision 
     * @param revision 
     * @param target 
     * @param ignoreAncestry 
     * @param recursive 
     * @param reporter 
     * @param editor 
     * @throws SVNException 
     * @deprecated use {@link #diff(SVNURL, long, long, String, boolean, SVNDepth, boolean, ISVNReporterBaton, ISVNEditor)} 
     *                 instead
     */
    public void diff(SVNURL url, long targetRevision, long revision, String target, boolean ignoreAncestry, boolean recursive, ISVNReporterBaton reporter, ISVNEditor editor) throws SVNException {
        diff(url, targetRevision, revision, target, ignoreAncestry, SVNDepth.fromRecurse(recursive), 
                true, reporter, editor);
    }

    
    /**
     * Calculates the differences between two items. 
     *
     * <p>
     * <code>target</code> is the name (one-level path component) of an entry that will restrict
     * the scope of the diff operation to this entry. In other words <code>target</code> is a child entry of the 
     * directory represented by the repository location to which this object is set. For
     * example, if we have something like <code>"/dirA/dirB"</code> in a repository, then
     * this object's repository location may be set to <code>"svn://host:port/path/to/repos/dirA"</code>,
     * and <code>target</code> may be <code>"dirB"</code>.
     * 
     * <p>
     * If <code>target</code> is <span class="javakeyword">null</span> or empty (<code>""</code>)
     * then the scope of the diff operation is the repository location to which
     * this object is set.
     * 
     * <p>
     * The <code>reporter</code> is used to describe the state of the target item(s) (i.e. 
     * items' revision numbers). All the paths described by the <code>reporter</code>
     * should be relative to the repository location to which this object is set. 
     * 
     * <p>
     * After that the <code>editor</code> is used to carry out all the work on 
     * evaluating differences against <code>url</code>. This <code>editor</code> contains 
     * knowledge of where the change will begin (when {@link ISVNEditor#openRoot(long) ISVNEditor.openRoot()} 
     * is called).
     * 
     * <p>
     * If <code>ignoreAncestry</code> is <span class="javakeyword">false</span> then
     * the ancestry of the paths being diffed is taken into consideration - they
     * are treated as related. In this case, for example, if calculating differences between 
     * two files with identical contents but different ancestry, 
     * the entire contents of the target file is considered as having been removed and 
     * added again. 
     * 
     * <p>
     * If <code>ignoreAncestry</code> is <span class="javakeyword">true</span>
     * then the two paths are merely compared ignoring the ancestry.
     * 
     * <p>
     * <b>NOTE:</b> you may not invoke methods of this <b>SVNRepository</b>
     * object from within the provided <code>reporter</code> and <code>editor</code>.
     * 
     * <p/>
     * This method is analogous to 
     * <code>diff(url, revision, revision, target, ignoreAncestry, SVNDepth.fromRecurse(recursive), true, reporter, editor)</code>. 
     * 
     * @param  url              a repository location of the entry against which 
     *                          differences are calculated 
     * @param  revision         a revision number of the repository location to which 
     *                          this driver object is set
     * @param  target           a target entry name (optional)
     * @param  ignoreAncestry   if <span class="javakeyword">true</span> then
     *                          the ancestry of the two entries to be diffed is 
     *                          ignored, otherwise not 
     * @param  recursive        if <span class="javakeyword">true</span> and the diff scope
     *                          is a directory, descends recursively, otherwise not 
     * @param  reporter         a caller's reporter
     * @param  editor           a caller's editor
     * @throws SVNException     in the following cases:
     *                          <ul>
     *                          <li><code>url</code> not found neither in the specified 
     *                          <code>revision</code> nor in the HEAD revision
     *                          <li>a failure occured while connecting to a repository 
     *                          <li>the user authentication failed 
     *                          (see {@link org.tmatesoft.svn.core.SVNAuthenticationException})
     *                          </ul>
     * @deprecated              use {@link #diff(SVNURL, long, long, String, boolean, SVNDepth, boolean, ISVNReporterBaton, ISVNEditor)}
     *                          instead
     */
    public void diff(SVNURL url, long revision, String target, boolean ignoreAncestry, boolean recursive, ISVNReporterBaton reporter, ISVNEditor editor) throws SVNException {
        diff(url, revision, revision, target, ignoreAncestry, SVNDepth.fromRecurse(recursive), true, reporter, 
                editor);
    }
    
    /**
     * Updates a path switching it to a new repository location.  
     * 
     * <p>
     * Updates a path as it's described for the {@link #update(long, String, boolean, ISVNReporterBaton, ISVNEditor) update()}
     * method using the provided <code>reporter</code> and <code>editor</code>, and switching
     * it to a new repository location. 
     * 
     * <p>
     * <b>NOTE:</b> you may not invoke methods of this <b>SVNRepository</b>
     * object from within the provided <code>reporter</code> and <code>editor</code>.
     * 
     * @param  url              a new location in the repository to switch to
     * @param  revision         a desired revision to make update to; defaults
     *                          to the latest revision (HEAD)
     * @param  target           an entry name (optional)  
     * @param  recursive        if <span class="javakeyword">true</span> and the switch scope
     *                          is a directory, descends recursively, otherwise not 
     * @param  reporter         a caller's reporter
     * @param  editor           a caller's editor
     * @throws SVNException     in the following cases:
     *                          <ul>
     *                          <li>a failure occured while connecting to a repository 
     *                          <li>the user authentication failed 
     *                          (see {@link org.tmatesoft.svn.core.SVNAuthenticationException})
     *                          </ul>
     * @deprecated              use {@link #update(SVNURL, long, String, SVNDepth, ISVNReporterBaton, ISVNEditor)} 
     *                          instead
     */
    public void update(SVNURL url, long revision, String target, boolean recursive, ISVNReporterBaton reporter, ISVNEditor editor) throws SVNException {
        update(url, revision, target, SVNDepth.fromRecurse(recursive), reporter, editor);
    }

    /**
     * Asks this repository access object to 'switch' a versioned tree to a new <code>url</code>.
     * 
     * <p/>
     * The client initially provides an <code>editor</code>; this editor contains knowledge of where the 
     * change will begin in the working copy ({@link ISVNEditor#openRoot(long)} is called).
     * 
     * <p/>
     * In return, the client receives a {@link ISVNReporter} object in his {@link ISVNReporterBaton}
     * implementation. The client then describes its working copy by making calls into the reporter object.
     *
     * <p/>
     * When finished, the client calls {@link ISVNReporter#finishReport()}. This <code>SVNRepository</code> 
     * object then does a complete drive of <code>editor</code>, ending with {@link ISVNEditor#closeEdit()}, to 
     * switch the working copy.
     * 
     * <p/>
     * <code>target</code> is an optional single path component that will restrict the scope of things affected 
     * by the switch to an entry in the directory represented by the {@link #getLocation() location} of this 
     * object, or <span class="javakeyword">null</span> if the entire directory is meant to be switched.
     *
     * <p/>
     * Switches the target only as deeply as <code>depth</code> indicates.
     * 
     * <p/>
     * The local tree will be switched to <code>revision</code>, or the HEAD revision if this arg is 
     * invalid.
     *
     * <p/>
     * The caller may not perform any repository access operations using this <code>SVNRepository</code> object 
     * before finishing the report, and may not perform any repository access operations using this 
     * <code>SVNRepository</code> object from within the editing operations of <code>editor</code>.
     * 
     * <p/>
     * Note: the reporter provided by this function does NOT supply copy-from information to the diff editor 
     * callbacks.
     * 
     * <p/>
     * Note: in order to prevent pre-1.5 servers from doing more work than needed, and sending too much data 
     * back, a pre-1.5 'recurse' directive may be sent to the server, based on <code>depth</code>.
     * 
     * @param  url                        a new location in the repository to switch to
     * @param  revision                   a desired revision to make update to; defaults
     *                                    to the latest revision (HEAD)
     * @param  target                     an entry name (optional)  
     * @param  depth                      the depth for update operation 
     * @param  reporter                   a caller's reporter
     * @param  editor                     a caller's editor
     * @throws SVNException               
     * @see                               ISVNReporterBaton
     * @see                               ISVNReporter
     * @see                               ISVNEditor
     * @see                               <a href="http://svnkit.com/kb/dev-guide-update-operation.html">Using ISVNReporter/ISVNEditor in update-related operations</a>
     * @since                             1.2.0, New in Subversion 1.5.0
     */
    public abstract void update(SVNURL url, long revision, String target, SVNDepth depth, 
            ISVNReporterBaton reporter, ISVNEditor editor) throws SVNException;
    
    /**
     * Updates a path receiving changes from a repository.
     *
     * <p>
     * <code>target</code> is the name (one-level path component) of an entry that will 
     * restrict the scope of the update to this entry. In other words <code>target</code> is a child entry of the 
     * directory represented by the repository location to which this object is set. For
     * example, if we have something like <code>"/dirA/dirB"</code> in a repository, then
     * this object's repository location may be set to <code>"svn://host:port/path/to/repos/dirA"</code>,
     * and <code>target</code> may be <code>"dirB"</code>.
     * 
     * <p>
     * If <code>target</code> is <span class="javakeyword">null</span> or empty (<code>""</code>)
     * then the scope of the update operation is the repository location to which
     * this object is set.
     * 
     * <p>
     * The <code>reporter</code> is used to describe the state of the local item(s) (i.e. 
     * items' revision numbers, deleted, switched items). All the paths described by the 
     * <code>reporter</code> should be relative to the repository location to which this 
     * object is set. 
     * 
     * <p>
     * After that the <code>editor</code> is used to carry out all the work on 
     * updating. This <code>editor</code> contains 
     * knowledge of where the change will begin (when {@link ISVNEditor#openRoot(long) ISVNEditor.openRoot()} 
     * is called).
	 * 
     * <p>
     * <b>NOTE:</b> you may not invoke methods of this <b>SVNRepository</b>
     * object from within the provided <code>reporter</code> and <code>editor</code>.
	 * 
     * @param  revision 		a desired revision to make update to; defaults to
     *                          the latest revision (HEAD)
     * @param  target 			an entry name (optional)  
     * @param  recursive        if <span class="javakeyword">true</span> and the update scope
     *                          is a directory, descends recursively, otherwise not 
     * @param  reporter         a caller's reporter
     * @param  editor           a caller's editor
     * @throws SVNException     in the following cases:
     *                          <ul>
     *                          <li>a failure occured while connecting to a repository 
     *                          <li>the user authentication failed 
     *                          (see {@link org.tmatesoft.svn.core.SVNAuthenticationException})
     *                          </ul>
     * @see                     #update(SVNURL, long, String, boolean, ISVNReporterBaton, ISVNEditor)
     * @see 					ISVNReporterBaton
     * @see 					ISVNReporter
     * @see 					ISVNEditor
     * @see                     <a href="http://svnkit.com/kb/dev-guide-update-operation.html">Using ISVNReporter/ISVNEditor in update-related operations</a>
     */
    public void update(long revision, String target, boolean recursive, ISVNReporterBaton reporter, ISVNEditor editor) throws SVNException {
        update(revision, target, SVNDepth.fromRecurse(recursive), false, reporter, editor);
    }

    /**
     * Updates a path receiving changes from a repository.
     * 
     * <p>
     * <code>target</code> is the name (one-level path component) of an entry that will 
     * restrict the scope of the update to this entry. In other words <code>target</code> is a child entry of the 
     * directory represented by the repository location to which this object is set. For
     * example, if we have something like <code>"/dirA/dirB"</code> in a repository, then
     * this object's repository location may be set to <code>"svn://host:port/path/to/repos/dirA"</code>,
     * and <code>target</code> may be <code>"dirB"</code>.
     * 
     * <p>
     * If <code>target</code> is <span class="javakeyword">null</span> or empty (<code>""</code>)
     * then the scope of the update operation is the repository location to which
     * this object is set.
     * 
     * <p>
     * The <code>reporter</code> is used to describe the state of the local item(s) (i.e. 
     * items' revision numbers, deleted, switched items). All the paths described by the 
     * <code>reporter</code> should be relative to the repository location to which this 
     * object is set. 
     * 
     * <p>
     * After that the <code>editor</code> is used to carry out all the work on 
     * updating. This <code>editor</code> contains 
     * knowledge of where the change will begin (when {@link ISVNEditor#openRoot(long) ISVNEditor.openRoot()} 
     * is called).
     * 
     * <p>
     * <b>NOTE:</b> you may not invoke methods of this <b>SVNRepository</b>
     * object from within the provided <code>reporter</code> and <code>editor</code>.
     * 
     * @param  revision         a desired revision to make update to; defaults to
     *                          the latest revision (HEAD)
     * @param  target           an entry name (optional)  
     * @param  depth            a depth for update operation, determines the scope of the update
     * @param  sendCopyFromArgs 
     * @param  reporter         a caller's reporter
     * @param  editor           a caller's editor
     * @throws SVNException     in case the repository could not be connected
     * @throws org.tmatesoft.svn.core.SVNAuthenticationException in case of authentication problems
     * @see                     #update(long, String, boolean, ISVNReporterBaton, ISVNEditor)
     * @see                     ISVNReporterBaton
     * @see                     ISVNReporter
     * @see                     ISVNEditor
     * @see                     <a href="http://svnkit.com/kb/dev-guide-update-operation.html">Using ISVNReporter/ISVNEditor in update-related operations</a>
     */
    public abstract void update(long revision, String target, SVNDepth depth, 
            boolean sendCopyFromArgs, ISVNReporterBaton reporter, ISVNEditor editor) throws SVNException;
    
    /**
     * Gets status of a path.
     *
     * <p>
     * <code>target</code> is the name (one-level path component) of an entry that will 
     * restrict the scope of the status to this entry. In other words <code>target</code> is a child entry of the 
     * directory represented by the repository location to which this object is set. For
     * example, if we have something like <code>"/dirA/dirB"</code> in a repository, then
     * this object's repository location may be set to <code>"svn://host:port/path/to/repos/dirA"</code>,
     * and <code>target</code> may be <code>"dirB"</code>.
     * 
     * <p>
     * If <code>target</code> is <span class="javakeyword">null</span> or empty (<code>""</code>)
     * then the scope of the update operation is the repository location to which
     * this object is set.
     * 
     * <p>
     * The <code>reporter</code> is used to describe the state of the local item(s) (i.e. 
     * items' revision numbers, deleted, switched items). All the paths described by the 
     * <code>reporter</code> should be relative to the repository location to which this 
     * object is set. 
     * 
     * <p>
     * After that the <code>editor</code> is used to carry out all the work on 
     * performing status. This <code>editor</code> contains 
     * knowledge of where the change will begin (when {@link ISVNEditor#openRoot(long) ISVNEditor.openRoot()} 
     * is called).
     * 
     * <p>
     * <b>NOTE:</b> you may not invoke methods of this <b>SVNRepository</b>
     * object from within the provided <code>reporter</code> and <code>editor</code>.
     * 
     * @param  revision         a desired revision to get status against; defaults to
     *                          the latest revision (HEAD)
     * @param  target           an entry name (optional)  
     * @param  recursive        if <span class="javakeyword">true</span> and the status scope
     *                          is a directory, descends recursively, otherwise not 
     * @param  reporter 		a client's reporter-baton
     * @param  editor 			a client's status editor
     * @throws SVNException     in the following cases:
     *                          <ul>
     *                          <li>a failure occured while connecting to a repository 
     *                          <li>the user authentication failed 
     *                          (see {@link org.tmatesoft.svn.core.SVNAuthenticationException})
     *                          </ul>
     * @see 					ISVNReporterBaton
     * @see 					ISVNEditor
     */
    public void status(long revision, String target, boolean recursive, ISVNReporterBaton reporter, ISVNEditor editor) throws SVNException {
        status(revision, target, SVNDepth.fromRecurse(recursive), reporter, editor);
    }
    
    /**
     * Gets status of a path to the particular <code>depth</code> as a scope. 
     * {@link #status(long, String, boolean, ISVNReporterBaton, ISVNEditor)}
     *
     * @param  revision         a desired revision to get status against; defaults to
     *                          the latest revision (HEAD)
     * @param  target           an entry name (optional)  
     * @param  depth            defines the status scope
     * @param  reporter         a client's reporter-baton
     * @param  editor           a client's status editor
     * @throws SVNException     in the following cases:
     *                          <ul>
     *                          <li>a failure occured while connecting to a repository 
     *                          <li>the user authentication failed 
     *                          (see {@link org.tmatesoft.svn.core.SVNAuthenticationException})
     *                          </ul>
     * @see                     #status(long, String, boolean, ISVNReporterBaton, ISVNEditor)
     * @see                     ISVNReporterBaton
     * @see                     ISVNEditor
     */
    public abstract void status(long revision, String target, SVNDepth depth, ISVNReporterBaton reporter, ISVNEditor editor) throws SVNException;
    
    /**
     * Checks out a directory from a repository.
     *
     * <p>
     * <code>target</code> is the name (one-level path component) of an entry that will 
     * restrict the scope of the checkout to this entry. In other words <code>target</code> is a child entry of the 
     * directory represented by the repository location to which this object is set. For
     * example, if we have something like <code>"/dirA/dirB"</code> in a repository, then
     * this object's repository location may be set to <code>"svn://host:port/path/to/repos/dirA"</code>,
     * and <code>target</code> may be <code>"dirB"</code>.
     * 
     * <p>
     * If <code>target</code> is <span class="javakeyword">null</span> or empty (<code>""</code>)
     * then the scope of the checkout operation is the repository location to which
     * this object is set.
     * 
     * <p>
     * The provided <code>editor</code> is used to carry out all the work on 
     * building a local tree of dirs and files being checked out. 
     * 
     * <p>
     * <b>NOTE:</b> you may not invoke methods of this <b>SVNRepository</b>
     * object from within the provided <code>editor</code>.
     * 
     * @param  revision     a desired revision of a dir to check out; defaults
     *                      to the latest revision (HEAD)
     * @param  target       an entry name (optional)  
     * @param  recursive    if <span class="javakeyword">true</span> and the checkout 
     *                      scope is a directory, descends recursively, otherwise not 
     * @param  editor 		a caller's checkout editor
     * @throws SVNException	in the following cases:
     *                      <ul>
     *                      <li>the checkout scope is not a directory (only dirs can
     *                      be checked out)
     *                      <li>a failure occured while connecting to a repository 
     *                      <li>the user authentication failed 
     *                      (see {@link org.tmatesoft.svn.core.SVNAuthenticationException})
     *                      </ul>
     * @see                 #update(long, String, boolean, ISVNReporterBaton, ISVNEditor)
     * @see    				ISVNEditor
     * 
     */
    public void checkout(long revision, String target, boolean recursive, ISVNEditor editor) throws SVNException {
        checkout(revision, target, SVNDepth.fromRecurse(recursive), editor);
    }
    
    /**
     * Checks out a directory from a repository to define <code>depth</code>.
     *
     * <p>
     * <code>target</code> is the name (one-level path component) of an entry that will 
     * restrict the scope of the checkout to this entry. In other words <code>target</code> is a child entry of the 
     * directory represented by the repository location to which this object is set. For
     * example, if we have something like <code>"/dirA/dirB"</code> in a repository, then
     * this object's repository location may be set to <code>"svn://host:port/path/to/repos/dirA"</code>,
     * and <code>target</code> may be <code>"dirB"</code>.
     * 
     * <p>
     * If <code>target</code> is <span class="javakeyword">null</span> or empty (<code>""</code>)
     * then the scope of the checkout operation is the repository location to which
     * this object is set.
     * 
     * <p>
     * The provided <code>editor</code> is used to carry out all the work on 
     * building a local tree of dirs and files being checked out. 
     * 
     * <p>
     * <b>NOTE:</b> you may not invoke methods of this <b>SVNRepository</b>
     * object from within the provided <code>editor</code>.
     * 
     * @param  revision     a desired revision of a dir to check out; defaults
     *                      to the latest revision (HEAD)
     * @param  target       an entry name (optional)  
     * @param  depth        the checkout operation scope
     * @param  editor       a caller's checkout editor
     * @throws SVNException in the following cases:
     *                      <ul>
     *                      <li>the checkout scope is not a directory (only dirs can
     *                      be checked out)
     *                      <li>a failure occured while connecting to a repository 
     *                      <li>the user authentication failed 
     *                      (see {@link org.tmatesoft.svn.core.SVNAuthenticationException})
     *                      </ul>
     * @see                 #update(long, String, SVNDepth, boolean, ISVNReporterBaton, ISVNEditor)
     * @see                 ISVNEditor
     * 
     */
    public void checkout(long revision, String target, SVNDepth depth, ISVNEditor editor) throws SVNException {
        final long lastRev = revision >= 0 ? revision : getLatestRevision();
        // check path?
        SVNNodeKind nodeKind = checkPath("", revision);
        if (nodeKind == SVNNodeKind.FILE) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.RA_ILLEGAL_URL, "URL ''{0}'' refers to a file, not a directory", getLocation());
            SVNErrorManager.error(err, SVNLogType.NETWORK);
        } else if (nodeKind == SVNNodeKind.NONE) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.RA_ILLEGAL_URL, "URL ''{0}'' doesn't exist", getLocation());
            SVNErrorManager.error(err, SVNLogType.NETWORK);
        }
        final SVNDepth reporterDepth = depth;
        update(revision, target, depth, false, new ISVNReporterBaton() {
                    public void report(ISVNReporter reporter) throws SVNException {
                        reporter.setPath("", null, lastRev, reporterDepth, true);
                        reporter.finishReport();
                    }            
                }, editor);
    }
    
    /**
     * Recursively checks out only files from the repository at <code>revision</code> invoking 
     * <code>fileCheckoutHandler</code> on every file received.
     * 
     * <p/>
     * Instead of multiple calls to {@link #getFileRevisions(String, long, long, boolean, ISVNFileRevisionHandler)} 
     * which lead to multiple requests to the repository server a user may use this method to get all interesting 
     * files.  
     *   
     * <p/>
     * If <code>paths</code> is not <span class="javakeyword">null</span> and not empty, checks out only those 
     * <code>paths</code>. It means if a path is from <code>paths</code> and is a file - checks out that file; 
     * if a directory - checks out every file under the directory and down the tree.
     * 
     * <p/>
     * Otherwise if <code>paths</code> is <span class="javakeyword">null</span> or empty, checks out files 
     * from the entire tree under this object's {@link #getLocation()}.
     * 
     * <p/>
     * <code>paths</code> does not have to be sorted; it will be sorted automatically.
     * 
     * @param  revision                   revision of files to checkout 
     * @param  paths                      paths to check out
     * @param  fileCheckoutHandler        caller's file data receiver
     * @throws SVNException 
     * @since                             1.2.0
     */
    public void checkoutFiles(long revision, String[] paths, ISVNFileCheckoutTarget fileCheckoutHandler) throws SVNException {
        final long lastRev = revision >= 0 ? revision : getLatestRevision();
        final List pathsList = new ArrayList(Arrays.asList(paths));
        Collections.sort(pathsList, SVNPathUtil.PATH_COMPARATOR);
        ISVNReporterBaton reporterBaton = new ISVNReporterBaton() {
            public void report(ISVNReporter reporter) throws SVNException {
                reporter.setPath("", null, lastRev, SVNDepth.INFINITY, false);
                for (Iterator ps = pathsList.iterator(); ps.hasNext();) {
                    String path = (String) ps.next();
                    reporter.deletePath(path);
                }
                reporter.finishReport();
            }
        };
        update(lastRev, null, SVNDepth.INFINITY, false, reporterBaton, new SVNFileCheckoutEditor(fileCheckoutHandler));
    }
    
    /**
     * Replays the changes from the specified revision through the given editor.
     * 
     * <p>
     * Changes will be limited to those that occur under a session's URL, and
     * the server will assume that the client has no knowledge of revisions
     * prior to a <code>lowRevision</code>.  These two limiting factors define the portion
     * of the tree that the server will assume the client already has knowledge of,
     * and thus any copies of data from outside that part of the tree will be
     * sent in their entirety, not as simple copies or deltas against a previous
     * version.
     * 
     * <p>
     * If <code>sendDeltas</code> is <span class="javakeyword">true</span>, the actual text 
     * and property changes in the revision will be sent, otherwise no text deltas and 
     * <span class="javakeyword">null</span> property changes will be sent instead.
     * 
     * <p>
     * If <code>lowRevision</code> is invalid, it defaults to 0.
     * 
     * @param  lowRevision     a low revision point beyond which a client has no
     *                         knowledge of paths history        
     * @param  revision        a revision to replay
     * @param  sendDeltas      controls whether text and property changes are to be
     *                         sent
     * @param  editor          a commit editor to receive changes 
     * @throws SVNException in case the repository could not be connected
     * @throws org.tmatesoft.svn.core.SVNAuthenticationException in case of authentication problems
     * @since  1.1, new in SVN 1.4
     */
    public abstract void replay(long lowRevision, long revision, boolean sendDeltas, ISVNEditor editor) throws SVNException;

    /**
     * Replays the changes from a range of revisions between <code>startRevision</code>
     * and <code>endRevision</code>.
     * 
     * <p/>
     * When receiving information for one revision, 
     * {@link ISVNReplayHandler#handleStartRevision(long, SVNProperties)} is called; this method will provide 
     * an editor through which the revision will be replayed.
     * When replaying the revision is finished, 
     * {@link ISVNReplayHandler#handleEndRevision(long, SVNProperties, ISVNEditor)} will be called so the 
     * editor can be closed.
     * 
     * <p/>
     * Changes will be limited to those that occur under this object's {@link #getLocation()}, and
     * the server will assume that the client has no knowledge of revisions prior to <code>lowRevision</code>. 
     * These two limiting factors define the portion of the tree that the server will assume the client already 
     * has knowledge of, and thus any copies of data from outside that part of the tree will be sent in their 
     * entirety, not as simple copies or deltas against a previous version.
     *
     * <p/>
     * If <code>sendDeltas</code> is <span class="javakeyword">true</span>, the actual text and property 
     * changes in the revision will be sent, otherwise dummy text deltas and <span class="javakeyword">null</span> 
     * property changes will be sent instead.
     * 
     * <p/>
     * If the server does not support revision range replication, then invokes {@link #replay(long, long, boolean, ISVNEditor)} 
     * on each revision between <code>startRevision</code> and <code>endRevision</code> inclusively.  
     * 
     * @param  startRevision    revision range start     
     * @param  endRevision      revision range end
     * @param  lowRevision      low water mark revision
     * @param  sendDeltas       whether to ask the server send text and properties 
     * @param  handler          caller's handler
     * @throws SVNException     in the following cases:
     *                          <ul>
     *                          <li/>exception with {@link SVNErrorCode#RA_NOT_IMPLEMENTED} error code - if 
     *                          the server does not support revision range replication
     *                          </ul> 
     * @since                   1.2.0, New in Subversion 1.5.0
     */
    public void replayRange(long startRevision, long endRevision, long lowRevision, boolean sendDeltas, 
            ISVNReplayHandler handler) throws SVNException {
        try {
            replayRangeImpl(startRevision, endRevision, lowRevision, sendDeltas, handler);
        } catch (SVNException svne) {
            if (svne.getErrorMessage().getErrorCode() == SVNErrorCode.RA_NOT_IMPLEMENTED) {
                for (long rev = startRevision; rev <= endRevision; rev++) {
                    SVNProperties revProps = getRevisionProperties(rev, null);
                    ISVNEditor editor = handler.handleStartRevision(rev, revProps);
                    replay(lowRevision, rev, sendDeltas, editor);
                    handler.handleEndRevision(rev, revProps, editor);
                }
            } else {
                throw svne;
            }
        }
    }

    /**
     * Gets an editor for committing changes to a repository. Having got the editor
     * traverse a local tree of dirs and/or files to be committed, handling them    
     * with corresponding methods of the editor. 
     * 
     * <p>
     * <code>mediator</code> is used for temporary delta data storage allocations. 
     * 
     * <p>
     * The root path of the commit is the current repository location to which
     * this object is set.
     * 
     * <p>
     * After the commit has succeeded {@link ISVNEditor#closeEdit() 
     * ISVNEditor.closeEdit()} returns an <b>SVNCommitInfo</b> object
     * that contains a new revision number, the commit date, commit author.
     * 
     * <p>
     * This method should be rather used with pre-1.2 repositories.
     * 
     * <p>
     * <b>NOTE:</b> you may not invoke methods of this <b>SVNRepository</b>
     * object from within the returned commit editor.
	 * 
     * @param  logMessage       a commit log message
     * @param  mediator         temp delta storage provider; used also to cache
     *                          wcprops while committing  
     * @return                  an editor to commit a local tree of dirs and/or files 
     * @throws SVNException     in the following cases:
     *                          <ul>
     *                          <li>the repository location this object is set to is not a 
     *                          directory
     *                          <li>a failure occured while connecting to a repository 
     *                          <li>the user authentication failed 
     *                          (see {@link org.tmatesoft.svn.core.SVNAuthenticationException})
     *                          </ul>
	 * @see 					ISVNEditor
	 * @see 					ISVNWorkspaceMediator
     * @see                     <a href="http://svnkit.com/kb/dev-guide-commit-operation.html">Using ISVNEditor in commit operations</a>
     */
    public ISVNEditor getCommitEditor(String logMessage, final ISVNWorkspaceMediator mediator) throws SVNException {
        return getCommitEditor(logMessage, null, false, mediator);
    }
    
    /**
     * Gives information about an entry located at the specified path in a particular 
     * revision. 
     * 
     * <p>
     * The <code>path</code> arg can be both relative to the location of 
     * this driver and absolute to the repository root (starts with <code>"/"</code>).
     * 
     * @param  path			an item's path
     * @param  revision		a revision of the entry; defaults to the latest 
     *                      revision (HEAD)  
     * @return				an <b>SVNDirEntry</b> containing information about
     * 						the entry or <span class="javakeyword">null</span> if 
     *                      there's no entry with at the specified <code>path</code> 
     *                      under the specified <code>revision</code>
     * @throws SVNException in the following cases:
     *                      <ul>
     *                      <li>a failure occured while connecting to a repository 
     *                      <li>the user authentication failed 
     *                      (see {@link org.tmatesoft.svn.core.SVNAuthenticationException})
     *                      </ul>
     */
    public abstract SVNDirEntry info(String path, long revision) throws SVNException;
    
    /**
	 * Gets an editor for committing changes to a repository. Having got the editor
     * traverse a local tree of dirs and/or files to be committed, handling them    
     * with corresponding methods of the editor. 
     * 
	 * <p>
	 * <code>locks</code> is a map used to provide lock tokens on the locked paths. 
     * Keys are locked paths in a local tree, and each value for a key is a lock 
 	 * token. <code>locks</code> must live during the whole commit operation.
 	 * 
 	 * <p>
 	 * If <code>keepLocks</code> is <span class="javakeyword">true</span>, then the locked 
     * paths won't be unlocked after a successful commit. Otherwise, if 
     * <span class="javakeyword">false</span>, locks will be automatically released.
	 * 
     * <p>
	 * <code>mediator</code> is used for temporary delta data storage allocations. 
     * 
     * <p>
	 * The root path of the commit is the current repository location to which
     * this object is set.
	 * 
	 * <p>
	 * After the commit has succeeded {@link ISVNEditor#closeEdit() 
	 * ISVNEditor.closeEdit()} returns an <b>SVNCommitInfo</b> object
	 * that contains a new revision number, the commit date, commit author.
	 * 
     * <p>
     * <b>NOTE:</b> you may not invoke methods of this <b>SVNRepository</b>
     * object from within the returned commit editor.
     * 
     * @param  logMessage		a commit log message
     * @param  locks			a map containing locked paths mapped to lock 
     *                          tokens
     * @param  keepLocks		<span class="javakeyword">true</span> to keep 
     *                          existing locks;	<span class="javakeyword">false</span> 
     *                          to release locks after the commit
     * @param  mediator			temp delta storage provider; used also to cache
     *                          wcprops while committing  
     * @return					an editor to commit a local tree of dirs and/or files 
     * @throws SVNException     in the following cases:
     *                          <ul>
     *                          <li>the repository location this object is set to is not a 
     *                          directory
     *                          <li>a failure occured while connecting to a repository 
     *                          <li>the user authentication failed 
     *                          (see {@link org.tmatesoft.svn.core.SVNAuthenticationException})
     *                          </ul>
     * @see                     #getCommitEditor(String, ISVNWorkspaceMediator)
     * @see                     <a href="http://svnkit.com/kb/dev-guide-commit-operation.html">Using ISVNEditor in commit operations</a>
     */    
    public abstract ISVNEditor getCommitEditor(String logMessage, Map locks, boolean keepLocks, final ISVNWorkspaceMediator mediator) throws SVNException;
    
    /**
     * Returns an editor for committing changes to the repository ession, setting the revision
     * properties from <code>revisionProperties</code> table. The revisions being committed
     * against are passed to the editor methods, starting with the <code>revision</code> argument to 
     * {@link ISVNEditor#openRoot(long)}. The path root of the commit is this object's 
     * {@link #getLocation() location}.
     * 
     * <p/>
     * <code>revisionProperties</code> maps <code>String</code> property names to {@link SVNPropertyValue} 
     * property values. <code>revisionProperties</code> can not contain either of 
     * {@link SVNRevisionProperty#LOG}, {@link SVNRevisionProperty#DATE} or {@link SVNRevisionProperty#AUTHOR}.
     * 
     * <p/>
     * <code>locks</code>, if non-<span class="javakeyword">null</span>, is a hash mapping <code>String</code> 
     * paths (relative to the {@link #getLocation() location} of this object) to <code>String</code> lock tokens. 
     * The server checks that the correct token is provided for each committed, locked path.  
     * 
     * <p/>
     * If <code>keepLocks</code> is <span class="javakeyword">true</span>, then does not release locks on
     * committed objects. Else, automatically releasees such locks.
     * 
     * <p/>
     * The caller may not perform any repository access operations using this <code>SVNRepository</code> object 
     * before finishing the edit.
     * 
     * @param  logMessage            commit log message
     * @param  locks                 local locks on files 
     * @param  keepLocks             whether to unlock locked files after the commit or not 
     * @param  revisionProperties    custom revision properties
     * @param  mediator              temp delta storage provider; used also to cache
     *                               wcprops while committing  
     * @return                       commit editor
     * @throws SVNException          in the following cases:
     *                               <ul> 
     *                               <li/>exception with {@link SVNErrorCode#CLIENT_PROPERTY_NAME} error code - 
     *                               if <code>revisionProperties</code> contains any <code>svn:</code> namespace 
     *                               property 
     *                               </ul>
     * @since                        1.2.0, New in Subversion 1.5.0
     */
    public ISVNEditor getCommitEditor(String logMessage, Map locks, boolean keepLocks, 
            SVNProperties revisionProperties, final ISVNWorkspaceMediator mediator) throws SVNException {
        revisionProperties = revisionProperties == null ? new SVNProperties() : new SVNProperties(revisionProperties);
        if (logMessage != null) {
            revisionProperties.put(SVNRevisionProperty.LOG, logMessage);
        }
        return getCommitEditorInternal(locks, keepLocks, revisionProperties, mediator);
    }
    
    protected abstract ISVNEditor getCommitEditorInternal(Map locks, boolean keepLocks, SVNProperties revProps, final ISVNWorkspaceMediator mediator) throws SVNException;
    
    /**
     * Gets the lock for the file located at the specified path.
     * If the file has no lock the method returns <span class="javakeyword">null</span>.
     * 
     * <p>
     * The <code>path</code> arg can be both relative to the location of 
     * this driver and absolute to the repository root (starts with <code>"/"</code>).
     * 
     * @param  path		     a file path  
     * @return			     an <b>SVNLock</b> instance (representing the lock) or 
     * 					     <span class="javakeyword">null</span> if there's no lock
     * @throws SVNException  in the following cases:
     *                       <ul>
     *                       <li>a failure occured while connecting to a repository 
     *                       <li>the user authentication failed 
     *                       (see {@link org.tmatesoft.svn.core.SVNAuthenticationException})
     *                       </ul>
     * @see				     #lock(Map, String, boolean, ISVNLockHandler)
     * @see				     #unlock(Map, boolean, ISVNLockHandler)
     * @see				     #getLocks(String)
     * @see				     org.tmatesoft.svn.core.SVNLock
     * @since			     SVN 1.2
     */
    public abstract SVNLock getLock(String path) throws SVNException;

    /**
	 * Gets all locks on or below the <code>path</code>, that is if the repository 
	 * entry (located at the <code>path</code>) is a directory then the method 
	 * returns locks of all locked files (if any) in it.
     * 
     * <p>
     * The <code>path</code> arg can be both relative to the location of 
     * this driver and absolute to the repository root (starts with <code>"/"</code>).
     * 
     * @param  path 		a path under which locks are to be retrieved
     * @return 				an array of <b>SVNLock</b> objects (representing locks)
     * @throws SVNException in the following cases:
     *                      <ul>
     *                      <li>a failure occured while connecting to a repository 
     *                      <li>the user authentication failed 
     *                      (see {@link org.tmatesoft.svn.core.SVNAuthenticationException})
     *                      </ul>
     * @see					#lock(Map, String, boolean, ISVNLockHandler)
     * @see					#unlock(Map, boolean, ISVNLockHandler)
     * @see					#getLock(String)
     * @see                 org.tmatesoft.svn.core.SVNLock
     * @since				SVN 1.2
     */
    public abstract SVNLock[] getLocks(String path) throws SVNException;
    
    /**
     * Returns merge information for the repository entries in <code>paths</code> 
     * for paricular <code>revision</code>, if the repository supports merge-tracking information
     * @param paths                 paths under which merge information is to be retrieved
     * @param revision              revision for which merge information is to be retrieved
     * @param inherit               indicates whether explicit, explicit or inherited, or only inherited merge info is retrieved
     * @param includeDescendants    indicates whether merge info is retrieved for descendants of elements in <code>paths</code>
     * @return                      the map of merge information for the repository entries in <code>paths</code>
     * @throws SVNException         in case the repository could not be connected
     * @throws org.tmatesoft.svn.core.SVNAuthenticationException in case of authentication problems
 
     * @since SVNKit 1.2.0, SVN 1.5.0 
     */
    public Map<String, SVNMergeInfo> getMergeInfo(String[] paths, long revision, SVNMergeInfoInheritance inherit, 
            boolean includeDescendants) throws SVNException {
        if (paths == null) {
            return null;
        }
        assertServerIsMergeInfoCapable(getRepositoryRoot(true).toString());
        return getMergeInfoImpl(paths, revision, inherit, includeDescendants);
    }

    /**
	 * Locks path(s) at definite revision(s).
	 * 
	 * <p>
	 * Note that locking is never anonymous, so any server implementing
	 * this function will have to "pull" a username from the client, if
	 * it hasn't done so already.
	 * 
     * <p>
	 * Each path to be locked is handled with the provided <code>handler</code>.
     * If a path was successfully locked, the <code>handler</code>'s 
     * {@link ISVNLockHandler#handleLock(String, SVNLock, SVNErrorMessage) handleLock()}
     * is called that receives the path and either a lock object (representing the lock
     * that was set on the path) or an error exception, if locking failed for that path.
     * 
     * <p>
	 * If any path is already locked by a different user and the
	 * <code>force</code> flag is <span class="javakeyword">false</span>, then this call fails
	 * with throwing an <b>SVNException</b>. But if <code>force</code> is
	 * <span class="javakeyword">true</span>, then the existing lock(s) will be "stolen" anyway,
	 * even if the user name does not match the current lock's owner.
	 * 
     * <p>
     * Paths can be both relative to the location of this driver and absolute to 
     * the repository root (starting with <code>"/"</code>).
     * 
     * @param  pathsToRevisions		a map which keys are paths and values are 
     * 								revision numbers (as Longs); paths are strings and revision 
     * 								numbers are Long objects 
     * @param  comment				a comment string for the lock (optional)
     * @param  force				<span class="javakeyword">true</span> if the file is to be 
     *                              locked in any way (even if it's already locked by someone else)
     * @param  handler              if not <span class="javakeyword">null</span>, the lock
     *                              handler is invoked on each path to be locked  
     * @throws SVNException         in the following cases:
     *                              <ul>
     *                              <li><code>force</code> is <span class="javakeyword">false</span>
     *                              and a path is already locked by someone else
     *                              <li>a revision of a path is less than its last changed revision
     *                              <li>a path does not exist in the latest revision 
     *                              <li>a failure occured while connecting to a repository 
     *                              <li>the user authentication failed 
     *                              (see {@link org.tmatesoft.svn.core.SVNAuthenticationException})
     *                              </ul>
     * @see							#unlock(Map, boolean, ISVNLockHandler)
     * @see							#getLocks(String)
     * @see							#getLock(String)
     * @see                         org.tmatesoft.svn.core.SVNLock
     * @since 						SVN 1.2
     */
    public abstract void lock(Map pathsToRevisions, String comment, boolean force, ISVNLockHandler handler) throws SVNException;
    
    /**
	 * Removes lock(s) from the file(s). 
	 * 
	 * <p>
	 * Note that unlocking is never anonymous, so any server
	 * implementing this function will have to "pull" a username from
	 * the client, if it hasn't done so already.
	 * 
     * <p>
     * Each path to be unlocked is handled with the provided <code>handler</code>.
     * If a path was successfully unlocked, the <code>handler</code>'s 
     * {@link ISVNLockHandler#handleUnlock(String, SVNLock, SVNErrorMessage) handleUnlock()}
     * is called that receives the path and either a lock object (representing the lock
     * that was removed from the path) or an error exception, if unlocking failed for 
     * that path.
     * 
     * <p>
	 * If the username doesn't match the lock's owner and <code>force</code> is 
	 * <span class="javakeyword">false</span>, this method call fails with
	 * throwing an <b>SVNException</b>.  But if the <code>force</code>
	 * flag is <span class="javakeyword">true</span>, the lock will be "broken" 
	 * by the current user.
	 * 
	 * <p>
	 * Also if the lock token is incorrect or <span class="javakeyword">null</span>
	 * and <code>force</code> is <span class="javakeyword">false</span>, the method 
	 * fails with throwing a <b>SVNException</b>. However, if <code>force</code> is 
	 * <span class="javakeyword">true</span> the lock will be removed anyway.
	 *
     * <p>
     * Paths can be both relative to the location of this driver and absolute to 
     * the repository root (starting with <code>"/"</code>).
     * 
     * @param  pathToTokens	a map which keys are file paths and values are file lock
     * 						tokens (both keys and values are strings)
     * @param  force		<span class="javakeyword">true</span> to remove the 
     * 						lock in any case - i.e. to "break" the lock
     * @param  handler      if not <span class="javakeyword">null</span>, the lock
     *                      handler is invoked on each path to be unlocked
     * @throws SVNException in the following cases:
     *                      <ul>
     *                      <li><code>force</code> is <span class="javakeyword">false</span>
     *                      and the name of the user who tries to unlock a path does not match
     *                      the lock owner
     *                      <li>a lock token is incorrect for a path
     *                      <li>a failure occurred while connecting to a repository 
     *                      <li>the user authentication failed 
     *                      (see {@link org.tmatesoft.svn.core.SVNAuthenticationException})
     *                      </ul>
     * @see 				#lock(Map, String, boolean, ISVNLockHandler)
     * @see					#getLocks(String)
     * @see					#getLock(String)
     * @see                 org.tmatesoft.svn.core.SVNLock
     * @since				SVN 1.2
     */
    public abstract void unlock(Map pathToTokens, boolean force, ISVNLockHandler handler) throws SVNException;
    
    /**
     * Closes the current session closing a socket connection used by
     * this object.    
     * If this driver object keeps a single connection for 
     * all the data i/o, this method helps to reset the connection.
     */
    public abstract void closeSession();
  
    /**
     * Returns <code>true</code> if the repository has specified <code>capability</code>.
     * 
     * This method may need to establish connection with the repository if information on capabilities 
     * has not been received yet from the repository.
     * 
     * @param capability one of {@link SVNCapability}
     * @return boolean if the repository has specified capability
     * 
     * @throws SVNException in case the repository could not be connected
     * @throws org.tmatesoft.svn.core.SVNAuthenticationException in case of authentication problems
     * 
     * @since SVNKit 1.2.0, SVN 1.5.0
     * @see   SVNCapability
     */
    public abstract boolean hasCapability(SVNCapability capability) throws SVNException;

    /**
     * Returns the session options object this driver is using.
     * If no options object was provided to create this driver then 
     * it uses a default one - {@link ISVNSession#DEFAULT}. 
     * 
     * @return a session options object
     */
    public ISVNSession getOptions() {
        if (myOptions == null) {
            myOptions = ISVNSession.DEFAULT;
        }
        return myOptions;
    }
    
    /**
     * Adds a connection listener to this object. There can be more than one connection listeners added to 
     * a single <code>SVNRepository</code> object.
     * 
     * <p/>
     * All the provided listeners will be notified each time by this object about a new connection which it's 
     * opening. 
     * 
     * <p/>
     * Note: listeners are not notified in case of the <code>file:///</code> repository access protocol.
     * 
     * @param listener     caller's connection listener
     * @since              1.2.0 
     */
    public void addConnectionListener(ISVNConnectionListener listener) {
        myConnectionListeners.add(listener);
    }

    /**
     * Removes the specified connection listener from the collection of connection listeners 
     * held by this object.
     * 
     * @param listener      connection listener to remove 
     * @since               1.2.0
     */
    public void removeConnectionListener(ISVNConnectionListener listener) {
        myConnectionListeners.remove(listener);
    }
    
    /**
     * Returns the revision where the path was first deleted within the inclusive revision range 
     * defined by <code>pegRevision</code> and <code>endRevision</code>.
     * 
     * <p/>
     * If <code>path</code> does not exist at <code>pegRevision</code> or was not deleted within
     * the specified range, then returns an invalid revision (<code>&lt;0</code>).
     * 
     * 
     * @param    path           relative or absolute repository path                 
     * @param    pegRevision    peg revision to start the search from    
     * @param    endRevision    end revision to end the search at
     * @return                  revision where <code>path</code> is first deleted
     * @throws   SVNException   if <code>pegRevision</code> or <code>endRevision</code> are invalid or 
     *                          if <code>pegRevision</code> is greater than <code>endRevision</code>, then    
     *                          the exception is set {@link SVNErrorCode#CLIENT_BAD_REVISION} error code;
     *                          if <code>path</code> is the repository root (<code>"/"</code>).
     * @since    1.3
     */
    public long getDeletedRevision(String path, long pegRevision, long endRevision) throws SVNException {
        String repositoryPath = getRepositoryPath(path);
        if ("/".equals(repositoryPath)) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN, "root path could not be deleted");
            SVNErrorManager.error(err, SVNLogType.DEFAULT);
        }
        
        if (isInvalidRevision(pegRevision)) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_BAD_REVISION, "Invalid peg revision {0}", String.valueOf(pegRevision));
            SVNErrorManager.error(err, SVNLogType.DEFAULT);
        }

        if (isInvalidRevision(endRevision)) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_BAD_REVISION, "Invalid end revision {0}", String.valueOf(endRevision));
            SVNErrorManager.error(err, SVNLogType.DEFAULT);
        }
        
        if (endRevision <= pegRevision) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_BAD_REVISION, "Peg revision must precede end revision");
            SVNErrorManager.error(err, SVNLogType.DEFAULT);
        }
        
        try {
            path = getLocationRelativePath(path);
            return getDeletedRevisionImpl(path, pegRevision, endRevision);
        } catch (SVNException svne) {
            SVNErrorCode errCode = svne.getErrorMessage().getErrorCode();
            if (errCode == SVNErrorCode.UNSUPPORTED_FEATURE || errCode == SVNErrorCode.RA_NOT_IMPLEMENTED) {
                return getDeletedRevisionFromLog(path, pegRevision, endRevision);
            }
            throw svne;
        }
    }
    
    public Map<String, SVNProperties> getInheritedProperties(String path, long revision, String propertyName) throws SVNException {
        final Map<String, SVNProperties> result = new TreeMap<String, SVNProperties>(SVNPathUtil.PATH_COMPARATOR);
        getInheritedProperties(path, revision, propertyName, new ISVNInheritedPropertiesHandler() {
            public void handleInheritedProperites(String inheritedFromPath, SVNProperties properties) throws SVNException {
                result.put(inheritedFromPath, properties);
            }
        });
        return result;
    }

    public void getInheritedProperties(String path, long revision, String propertyName, ISVNInheritedPropertiesHandler handler) throws SVNException {
        if (isInvalidRevision(revision)) {
            revision = getLatestRevision();
        }
        if (hasCapability(SVNCapability.INHERITED_PROPS)) {
            getInheritedPropertiesImpl(path, revision, propertyName, handler);
            return;
        }
        
        final SVNURL originalLocation = getLocation();
        String rootRelativePath = getRepositoryPath("");
        try {
            while(!"/".equals(rootRelativePath)) {            
                rootRelativePath = SVNPathUtil.removeTail(rootRelativePath);
                if ("".equals(rootRelativePath)) {
                    rootRelativePath = "/";
                }
                setLocation(getLocation().removePathTail(), false);
                final SVNProperties properties = new SVNProperties();
                try {
                    getDir("", revision, properties, 0, (ISVNDirEntryHandler) null);
                } catch (SVNAuthenticationException ae) {
                    continue;
                }
                
                final SVNProperties filtered = new SVNProperties();
                if (propertyName != null && properties.containsName(propertyName)) {
                    filtered.put(propertyName, properties.getSVNPropertyValue(propertyName));
                } else if (propertyName == null) {
                    for(String propName : properties.nameSet()) {
                        if (SVNProperty.isRegularProperty(propName)) {
                            filtered.put(propName, properties.getSVNPropertyValue(propName));
                        }
                    }
                }
                if (handler != null && !filtered.isEmpty()) {
                    handler.handleInheritedProperites(rootRelativePath, filtered);
                }
            }
        } finally {
            setLocation(originalLocation, false);
        }
    }
    
    protected abstract void getInheritedPropertiesImpl(String path, long revision, String propertyName, ISVNInheritedPropertiesHandler handler) throws SVNException;
    
    protected abstract long getDeletedRevisionImpl(String path, long pegRevision, long endRevision) throws SVNException;
    
    protected abstract long getLocationSegmentsImpl(String path, long pegRevision, long startRevision, long endRevision, 
            ISVNLocationSegmentHandler handler) throws SVNException;
    
    protected abstract int getLocationsImpl(String path, long pegRevision, long[] revisions, 
            ISVNLocationEntryHandler handler) throws SVNException;
    
    protected abstract long logImpl(String[] targetPaths, long startRevision, long endRevision, 
            boolean changedPath, boolean strictNode, long limit, boolean includeMergedRevisions, 
            String[] revisionProperties, ISVNLogEntryHandler handler) throws SVNException;

    protected abstract int getFileRevisionsImpl(String path, long startRevision, long endRevision, 
            boolean includeMergedRevisions, ISVNFileRevisionHandler handler) throws SVNException;

    protected abstract Map getMergeInfoImpl(String[] paths, long revision, SVNMergeInfoInheritance inherit, 
            boolean includeDescendants) throws SVNException;

    protected abstract void replayRangeImpl(long startRevision, long endRevision, long lowRevision, 
            boolean sendDeltas, ISVNReplayHandler handler) throws SVNException;

    protected void fireConnectionOpened() {
        for (Iterator listeners = myConnectionListeners.iterator(); listeners.hasNext();) {
            ISVNConnectionListener listener = (ISVNConnectionListener) listeners.next();
            listener.connectionOpened(this);
        }
    }

    protected void fireConnectionClosed() {
        for (Iterator listeners = myConnectionListeners.iterator(); listeners.hasNext();) {
            ISVNConnectionListener listener = (ISVNConnectionListener) listeners.next();
            listener.connectionClosed(this);
        }
    }

    protected synchronized void lock() {
        lock(false);
    }
    
    protected synchronized void lock(boolean force) {
        try {
            synchronized(this) {
                if (Thread.currentThread() == myLocker) {
                    if (!force) {
                        getDebugLog().logFine(SVNLogType.DEFAULT, new Exception());
                        throw new Error("SVNRepository methods are not reenterable");
                    } 
                    myLockCount++;
                    return;
                }
                while (myLocker != null) {
                    wait();
                }
                myLocker = Thread.currentThread();
                myLockCount++;
            }
    	} catch (InterruptedException e) {
    	    throw new Error("Interrupted attempt to aquire write lock");
    	}
    }
    
    protected synchronized void unlock() {
        synchronized(this) {
            if (--myLockCount <= 0) {
                myLockCount = 0;
                myLocker = null;
                notify();
            }
        }
    }
    
    protected static boolean isInvalidRevision(long revision) {
        return revision < 0;
    }    
    
    protected static boolean isValidRevision(long revision) {
        return revision >= 0;
    }
    
    protected static Long getRevisionObject(long revision) {
        return isValidRevision(revision) ? new Long(revision) : null;
    }
    
    protected static void assertValidRevision(long revision) throws SVNException {
        if (!isValidRevision(revision)) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_BAD_REVISION, 
                    "Invalid revision number ''{0}''", new Long(revision));
            SVNErrorManager.error(err, SVNLogType.NETWORK);
        }
    }
    
    // all paths are uri-decoded.
    //
    // get repository path (path starting with /, relative to repository root).
    // get full path (path starting with /, relative to host).
    // get relative path (repository path, now relative to repository location, not starting with '/').

    /**
     * Returns a path relative to the repository root directory given
     * a path relative to the location to which this driver object is set.
     * 
     * @param  relativePath a path relative to the location to which
     *                      this <b>SVNRepository</b> is set
     * @return              a path relative to the repository root
     * @throws SVNException in case the repository could not be connected
     * @throws org.tmatesoft.svn.core.SVNAuthenticationException in case of authentication problems            
     */
    public String getRepositoryPath(String relativePath) throws SVNException {
        if (relativePath == null) {
            return "/";
        }
        if (relativePath.length() > 0 && relativePath.charAt(0) == '/') {
            return relativePath;
        }
        String fullPath = SVNPathUtil.append(getLocation().getPath(), relativePath);
        String repositoryPath = fullPath.substring(getRepositoryRoot(true).getPath().length());
        if ("".equals(repositoryPath)) {
            return "/";
        }

        //if url does not contain a repos root path component, then it results here in 
        //a path that lacks leading slash, fix that
        if (!repositoryPath.startsWith("/")) {
            repositoryPath = "/" + repositoryPath;
        }
        return repositoryPath;
    }

    protected String getLocationRelativePath(String relativeOrAbsolutePath) throws SVNException {
        if (relativeOrAbsolutePath == null) {
            relativeOrAbsolutePath = "/";
        }
        if (relativeOrAbsolutePath.length() > 0 && relativeOrAbsolutePath.charAt(0) == '/') {
            // get relative path if it is child or equal to location path
            String locationPath = getLocation().getPath();
            locationPath = locationPath.substring(getRepositoryRoot(true).getPath().length());
            if (!locationPath.startsWith("/")) {
                locationPath = "/" + locationPath;
            }
            if (relativeOrAbsolutePath.startsWith(locationPath + "/") || relativeOrAbsolutePath.equals(locationPath)) {
                relativeOrAbsolutePath = relativeOrAbsolutePath.substring(locationPath.length());
                if (relativeOrAbsolutePath.startsWith("/")) {
                    relativeOrAbsolutePath = relativeOrAbsolutePath.substring(1);
                }
            }
            return relativeOrAbsolutePath;
        }
        return relativeOrAbsolutePath;
    }
    
    /**
     * Resolves a path, relative either to the location to which this 
     * driver object is set or to the repository root directory, to a
     * path, relative to the host.
     * 
     * @param  relativeOrRepositoryPath a relative path within the
     *                                  repository 
     * @return                          a path relative to the host
     * @throws SVNException in case the repository could not be connected
     * @throws org.tmatesoft.svn.core.SVNAuthenticationException in case of authentication problems
     */
    public String getFullPath(String relativeOrRepositoryPath) throws SVNException {
        if (relativeOrRepositoryPath == null) {
            return getFullPath("/");
        }
        String fullPath;
        if (relativeOrRepositoryPath.length() > 0 && relativeOrRepositoryPath.charAt(0) == '/') {
            fullPath = SVNPathUtil.append(getRepositoryRoot(true).getPath(), relativeOrRepositoryPath);
        } else {
            fullPath = SVNPathUtil.append(getLocation().getPath(), relativeOrRepositoryPath);
        }
        if (!fullPath.startsWith("/")) {
            fullPath = "/" + fullPath;
        }
        return fullPath;
    }
    
    /**
     * Sets a logger to write debug log information to.
     * 
     * @param log a debug logger
     */
    public void setDebugLog(ISVNDebugLog log) {
        myDebugLog = log;
    }
    
    /**
     * Returns the debug logger currently in use.  
     * 
     * <p>
     * If no debug logger has been specified by the time this call occurs, 
     * a default one (returned by <code>org.tmatesoft.svn.util.SVNDebugLog.getDefaultLog()</code>) 
     * will be created and used.
     * 
     * @return a debug logger
     */
    public ISVNDebugLog getDebugLog() {
        if (myDebugLog == null) {
            return SVNDebugLog.getDefaultLog();
        }
        return myDebugLog;
    }
    
    private long getDeletedRevisionFromLog(String path, long pegRevision, long endRevision) throws SVNException {
        DeletedRevisionLogHandler handler = new DeletedRevisionLogHandler(path);
        log(null, pegRevision, endRevision, true, true, 0, false, null, handler);
        return handler.getDeletedRevision();
    }
    
    private long getLocationSegmentsFromLog(String path, long pegRevision, long startRevision, long endRevision, 
            ISVNLocationSegmentHandler handler) throws SVNException {
        String reposAbsPath = getRepositoryPath(path);
        long youngestRevision = INVALID_REVISION;
        if (isInvalidRevision(pegRevision)) {
            youngestRevision = getLatestRevision();
            pegRevision = youngestRevision;
        }
        
        if (isInvalidRevision(startRevision)) {
            if (isValidRevision(youngestRevision)) {
                startRevision = youngestRevision;
            } else {
                startRevision = getLatestRevision();
            }
        }
        
        if (isInvalidRevision(endRevision)) {
            endRevision = 0;
        }
        

        SVNErrorManager.assertionFailure(pegRevision >= startRevision, null, SVNLogType.NETWORK);
        SVNErrorManager.assertionFailure(startRevision >= endRevision, null, SVNLogType.NETWORK);
        
        SVNNodeKind kind = checkPath(path, pegRevision);
        if (kind == SVNNodeKind.NONE) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NOT_FOUND, 
                    "Path ''{0}'' doesn''t exist in revision {1}", 
                    new Object[] { reposAbsPath, new Long(pegRevision) });
            SVNErrorManager.error(err, SVNLogType.NETWORK);
        }

        LocationSegmentsLogHandler locationSegmentsLogHandler = new LocationSegmentsLogHandler(kind, reposAbsPath, 
                startRevision, handler);
        log(new String[] { path }, pegRevision, endRevision, true, false, 0, false, new String[0], 
                locationSegmentsLogHandler);
        if (!locationSegmentsLogHandler.myIsDone) {
            locationSegmentsLogHandler.maybeCropAndSendSegment(locationSegmentsLogHandler.myLastPath, 
                    startRevision, endRevision, locationSegmentsLogHandler.myRangeEnd, handler);
        }
        return locationSegmentsLogHandler.myCount;
    }

    private int getLocationsFromLog(String path, long pegRevision, long[] revisions, 
            ISVNLocationEntryHandler handler) throws SVNException {
        String reposAbsPath = getRepositoryPath(path);
        SVNNodeKind kind = checkPath(path, pegRevision);
        if (kind == SVNNodeKind.NONE) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NOT_FOUND, 
                    "Path ''{0}'' doesn''t exist in revision {1}", 
                    new Object[] { reposAbsPath, new Long(pegRevision) });
            SVNErrorManager.error(err, SVNLogType.NETWORK);
        }
        if (revisions == null || revisions.length == 0) {
            return 0;
        }
        
        Arrays.sort(revisions);
        long oldestRequestedRev = revisions[0];
        long youngestRequestedRev = revisions[revisions.length - 1];
        long youngest = pegRevision;
        youngest = oldestRequestedRev > youngest ? oldestRequestedRev : youngest;
        youngest = youngestRequestedRev > youngest ? youngestRequestedRev : youngest;
        long oldest = pegRevision;
        oldest = oldestRequestedRev < oldest ? oldestRequestedRev : oldest;
        oldest = youngestRequestedRev < oldest ? youngestRequestedRev : oldest;
        
        LocationsLogHandler locationsLogHandler = new LocationsLogHandler(revisions, kind, reposAbsPath, 
                pegRevision, handler);
        log(new String[] { path }, youngest, oldest, true, false, 0, false, null, locationsLogHandler);
        if (locationsLogHandler.myPegPath == null) {
            locationsLogHandler.myPegPath = locationsLogHandler.myLastPath;
        }
        if (locationsLogHandler.myLastPath != null) {
            for (int i = 0; i < revisions.length; i++) {
                long rev = revisions[i];
                Long revObject = new Long(rev);
                if (handler != null && !locationsLogHandler.myProcessedRevisions.contains(revObject)) {
                    handler.handleLocationEntry(new SVNLocationEntry(rev, locationsLogHandler.myLastPath));
                    locationsLogHandler.myProcessedRevisions.add(revObject);
                }
            }
        }
        
        if (locationsLogHandler.myPegPath == null) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN, 
                    "Unable to find repository location for ''{0}'' in revision {1}", 
                    new Object[] { reposAbsPath, new Long(pegRevision) });
            SVNErrorManager.error(err, SVNLogType.NETWORK);
        }
        
        if (!reposAbsPath.equals(locationsLogHandler.myPegPath)) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_UNRELATED_RESOURCES, 
                    "''{0}'' in revision {1} is an unrelated object",
                    new Object[] { reposAbsPath, new Long(youngest) });
            SVNErrorManager.error(err, SVNLogType.NETWORK);
        }
        return locationsLogHandler.myProcessedRevisions.size();
    }
    
    private int getFileRevisionsFromLog(String path, long startRevision, long endRevision, 
            ISVNFileRevisionHandler handler) throws SVNException {
        SVNURL reposURL = getRepositoryRoot(true);
        SVNURL sessionURL = getLocation();
        String reposAbsPath = getRepositoryPath(path);
        SVNNodeKind kind = checkPath("", endRevision);
        if (kind == SVNNodeKind.DIR) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NOT_FILE, "''{0}'' is not a file", 
                    reposAbsPath);
            SVNErrorManager.error(err, SVNLogType.NETWORK);
        }
        FileRevisionsLogHandler logHandler = new FileRevisionsLogHandler(reposAbsPath);
        log(new String[] { "" }, endRevision, startRevision, true, false, 0, false, null, logHandler);
        LinkedList revisions = logHandler.getRevisions();
        setLocation(reposURL, false);
        
        File lastFile = null;
        SVNProperties lastProps = null;
        SVNDeltaGenerator deltaGenerator = new SVNDeltaGenerator();
        int i = 0;
        for (Iterator revsIter = revisions.iterator(); revsIter.hasNext();) {
            Revision rev = (Revision) revsIter.next();
            File tmpFile = SVNFileUtil.createTempFile("tmp", ".tmp");
            SVNProperties props = new SVNProperties();
            OutputStream os = null;
            try {
                os = SVNFileUtil.openFileForWriting(tmpFile);
                getFile(rev.myPath, rev.myRevision, props, os);
            } finally {
                SVNFileUtil.closeFile(os);
            }
            
            SVNProperties propDiff = FSRepositoryUtil.getPropsDiffs(lastProps, props);
            SVNFileRevision fileRevision = new SVNFileRevision(rev.myPath, rev.myRevision, rev.myProperties, 
                    propDiff, false);
            handler.openRevision(fileRevision);
            
            InputStream srcStream = null;
            InputStream tgtStream = null;
            try {
                srcStream = lastFile != null ? SVNFileUtil.openFileForReading(lastFile, SVNLogType.NETWORK) : SVNFileUtil.DUMMY_IN;
                tgtStream = SVNFileUtil.openFileForReading(tmpFile, SVNLogType.NETWORK);
                deltaGenerator.sendDelta(rev.myPath, srcStream, 0, tgtStream, handler, false);
            } finally {
                SVNFileUtil.closeFile(srcStream);
                SVNFileUtil.closeFile(tgtStream);
            }
            
            handler.closeRevision(rev.myPath);
            if (lastFile != null) {
                SVNFileUtil.deleteFile(lastFile);
            }
            lastFile = tmpFile;
            lastProps = props;
            i++;
        }
        
        setLocation(sessionURL, false);
        return i;
    }

    private static String getPreviousLogPath(char[] action, long[] copyFromRevision, Map changedPaths, 
            String path, SVNNodeKind kind, long revision) throws SVNException {
        if (action != null && action.length > 0) {
            action[0] = SVNLogEntryPath.TYPE_MODIFIED;
        }
        if (copyFromRevision != null && copyFromRevision.length > 0) {
            copyFromRevision[0] = INVALID_REVISION;
        }
        String previousPath = null;
        if (changedPaths != null) {
            SVNLogEntryPath change = (SVNLogEntryPath) changedPaths.get(path);
            if (change != null) {
                if (change.getType() != SVNLogEntryPath.TYPE_ADDED && 
                        change.getType() != SVNLogEntryPath.TYPE_REPLACED) {
                    previousPath = path;
                } else {
                    if (change.getCopyPath() != null) {
                        previousPath = change.getCopyPath();
                    } 
                    if (action != null && action.length > 0) {
                        action[0] = change.getType();
                    }
                    if (copyFromRevision != null && copyFromRevision.length > 0) {
                        copyFromRevision[0] = change.getCopyRevision();
                    }
                    return previousPath;
                }
            }
            
            if (!changedPaths.isEmpty()) {
                String[] sortedPaths = (String[]) changedPaths.keySet().toArray(new String[changedPaths.size()]);
                Arrays.sort(sortedPaths, SVNPathUtil.PATH_COMPARATOR);
                for (int i = sortedPaths.length; i > 0; i--) {
                    String changedPath = sortedPaths[i - 1];
                    if (!(path.startsWith(changedPath) && path.length() > changedPath.length() && 
                            path.charAt(changedPath.length()) == '/')) {
                        continue;
                    }
                    
                    change = (SVNLogEntryPath) changedPaths.get(changedPath);
                    if (change.getCopyPath() != null) {
                        if (action != null && action.length > 0) {
                            action[0] = change.getType();
                        }
                        if (copyFromRevision != null && copyFromRevision.length > 0) {
                            copyFromRevision[0] = change.getCopyRevision();
                        }
                        previousPath = SVNPathUtil.append(change.getCopyPath(), 
                                path.substring(changedPath.length() + 1));
                        break;
                    }
                }
            }
        }
        
        if (previousPath == null) {
            if (kind == SVNNodeKind.DIR) {
                previousPath = path;
            } else {
                SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_UNRELATED_RESOURCES, 
                        "Missing changed-path information for ''{0}'' in revision {1}", 
                        new Object[] { path, new Long(revision) });
                SVNErrorManager.error(err, SVNLogType.NETWORK);
            }
        }
       
        return previousPath;
    }
    
    private static class DeletedRevisionLogHandler implements ISVNLogEntryHandler {
        private String myPath;
        private long myDeletedRevision;
        
        public DeletedRevisionLogHandler(String path) {
            myPath = path;
            myDeletedRevision = INVALID_REVISION;
        }

        public void handleLogEntry(SVNLogEntry logEntry) throws SVNException {
            Map changedPaths = logEntry.getChangedPaths();
            for (Iterator changedPathsIter = changedPaths.keySet().iterator(); changedPathsIter.hasNext();) {
                String path = (String) changedPathsIter.next();
                SVNLogEntryPath change = (SVNLogEntryPath) changedPaths.get(path);
                if (myPath.equals(path) && (change.getType() == SVNLogEntryPath.TYPE_DELETED || 
                        change.getType() == SVNLogEntryPath.TYPE_REPLACED)) {
                    
                    myDeletedRevision = logEntry.getRevision();
                }
            }
        }

        public long getDeletedRevision() {
            return myDeletedRevision;
        }

    }
    
    private static class FileRevisionsLogHandler implements ISVNLogEntryHandler {
        private LinkedList myRevisions;
        private String myPath;
        
        public FileRevisionsLogHandler(String path) {
            myRevisions = new LinkedList();
            myPath = path;
        }
        
        public void handleLogEntry(SVNLogEntry logEntry) throws SVNException {
            Revision rev = new Revision();
            rev.myPath = myPath;
            rev.myRevision = logEntry.getRevision();
            rev.myProperties = logEntry.getRevisionProperties();
            myRevisions.addFirst(rev);
            
            myPath = getPreviousLogPath(null, null, logEntry.getChangedPaths(), myPath, 
                    SVNNodeKind.FILE, logEntry.getRevision());
        }
        
        public LinkedList getRevisions() {
            return myRevisions;
        }
    }
    
    private static class LocationSegmentsLogHandler implements ISVNLogEntryHandler {
        boolean myIsDone;
        String myLastPath;
        SVNNodeKind myNodeKind;
        long myCount;
        long myStartRevision;
        long myRangeEnd;
        ISVNLocationSegmentHandler myHandler;
        
        public LocationSegmentsLogHandler(SVNNodeKind kind, String lastPath, long startRev, 
                ISVNLocationSegmentHandler handler) {
            myNodeKind = kind;
            myLastPath = lastPath;
            myStartRevision = startRev;
            myRangeEnd = startRev;
            myHandler = handler;
            myCount = 0;
        }
        
        public void handleLogEntry(SVNLogEntry logEntry) throws SVNException {
            if (myIsDone) {
                return;
            }
            
            long[] copyFromRevision = { INVALID_REVISION };
            String prevPath = getPreviousLogPath(null, copyFromRevision, logEntry.getChangedPaths(), myLastPath, 
                    myNodeKind, logEntry.getRevision());
            if (prevPath == null) {
                myIsDone = true;
                maybeCropAndSendSegment(myLastPath, myStartRevision, logEntry.getRevision(), myRangeEnd, myHandler); 
                return;
            }
            
            if (isValidRevision(copyFromRevision[0])) {
                maybeCropAndSendSegment(myLastPath, myStartRevision, logEntry.getRevision(), myRangeEnd, myHandler);
                myRangeEnd = logEntry.getRevision() - 1;
                if (logEntry.getRevision() - copyFromRevision[0] > 1) {
                    maybeCropAndSendSegment(null, myStartRevision, copyFromRevision[0] + 1, myRangeEnd, myHandler);
                    myRangeEnd = copyFromRevision[0];
                }
                myLastPath = prevPath;
            }
        }    

        public void maybeCropAndSendSegment(String path, long startRevision, long rangeStart, long rangeEnd, 
                ISVNLocationSegmentHandler handler) throws SVNException {
            long segmentRangeStart = rangeStart;
            long segmentRangeEnd = rangeEnd;
            if (segmentRangeStart <= startRevision) {
                if (segmentRangeEnd > startRevision) {
                    segmentRangeEnd = startRevision;
                }
                if (handler != null) {
                    handler.handleLocationSegment(new SVNLocationSegment(segmentRangeStart, segmentRangeEnd, path));
                    myCount += segmentRangeEnd - segmentRangeStart + 1;
                }
            }
        }

    }
    
    private static class LocationsLogHandler implements ISVNLogEntryHandler {
        String myLastPath;
        String myPegPath;
        SVNNodeKind myNodeKind;
        long myPegRevision;
        long[] myRevisions;
        int myRevisionsCount;
        ISVNLocationEntryHandler myLocationsHandler;
        LinkedList myProcessedRevisions;
        
        public LocationsLogHandler(long[] revisions, SVNNodeKind kind, String lastPath, long pegRevision, ISVNLocationEntryHandler handler) {
            myRevisionsCount = revisions.length;
            myRevisions = revisions;
            myNodeKind = kind;
            myLastPath = lastPath;
            myPegRevision = pegRevision;
            myProcessedRevisions = new LinkedList();
            myLocationsHandler = handler;
        }
        
        public void handleLogEntry(SVNLogEntry logEntry) throws SVNException {
            if (logEntry.getChangedPaths() == null) {
                return;
            }
            
            String currentPath = myLastPath;
            if (currentPath == null) {
                return;
            }
        
            if (myPegPath == null && logEntry.getRevision() <= myPegRevision) {
                myPegPath = currentPath;
            }
            
            while (myRevisionsCount > 0) {
                long nextRev = myRevisions[myRevisionsCount - 1];
                if (logEntry.getRevision() <= nextRev) {
                    if (myLocationsHandler != null) {
                        myLocationsHandler.handleLocationEntry(new SVNLocationEntry(nextRev, currentPath));
                        myProcessedRevisions.add(new Long(nextRev));
                    }
                    myRevisionsCount--;
                } else {
                    break;
                }
            }
            
            String prevPath = getPreviousLogPath(null, null, logEntry.getChangedPaths(), currentPath, 
                    myNodeKind, logEntry.getRevision());
            if (prevPath == null) {
                myLastPath = null;
            } else if (!prevPath.equals(currentPath)) {
                myLastPath = prevPath;
            }
        }
    }
    
    private static class Revision {
        String myPath;
        long myRevision;
        SVNProperties myProperties;
    }
}
