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

import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;

import org.tmatesoft.svn.core.ISVNDirEntryHandler;
import org.tmatesoft.svn.core.ISVNLogEntryHandler;
import org.tmatesoft.svn.core.SVNAuthenticationException;
import org.tmatesoft.svn.core.SVNCancelException;
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.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.BasicAuthenticationManager;
import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
import org.tmatesoft.svn.core.auth.SVNAuthentication;
import org.tmatesoft.svn.core.auth.SVNUserNameAuthentication;
import org.tmatesoft.svn.core.internal.delta.SVNDeltaCombiner;
import org.tmatesoft.svn.core.internal.util.SVNDate;
import org.tmatesoft.svn.core.internal.util.SVNEncodingUtil;
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.internal.wc.SVNMergeInfoManager;
import org.tmatesoft.svn.core.io.ISVNEditor;
import org.tmatesoft.svn.core.io.ISVNFileRevisionHandler;
import org.tmatesoft.svn.core.io.ISVNInheritedPropertiesHandler;
import org.tmatesoft.svn.core.io.ISVNLocationEntryHandler;
import org.tmatesoft.svn.core.io.ISVNLocationSegmentHandler;
import org.tmatesoft.svn.core.io.ISVNLockHandler;
import org.tmatesoft.svn.core.io.ISVNReplayHandler;
import org.tmatesoft.svn.core.io.ISVNReporter;
import org.tmatesoft.svn.core.io.ISVNReporterBaton;
import org.tmatesoft.svn.core.io.ISVNSession;
import org.tmatesoft.svn.core.io.ISVNWorkspaceMediator;
import org.tmatesoft.svn.core.io.SVNCapability;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.util.SVNLogType;
import org.tmatesoft.svn.util.Version;

/**
 * @version 1.3
 * @author  TMate Software Ltd.
 */
public class FSRepository extends SVNRepository implements ISVNReporter {

    private File myReposRootDir;
    private FSUpdateContext myReporterContext;
    private FSLocationsFinder myLocationsFinder;
    private FSFS myFSFS;
    private SVNMergeInfoManager myMergeInfoManager;
    private FSLog myLogDriver;
    private boolean myIsHooksEnabled;

    protected FSRepository(SVNURL location, ISVNSession options) {
        super(location, options);
        setHooksEnabled(true);
    }

    public void setHooksEnabled(boolean enabled) {
        myIsHooksEnabled = enabled;
        if (getFSFS() != null) {
            getFSFS().setHooksEnabled(isHooksEnabled());
        }
    }

    public boolean isHooksEnabled() {
        return myIsHooksEnabled;
    }

    public FSFS getFSFS() {
        return myFSFS;
    }

    public void testConnection() throws SVNException {
        // try to open and close a repository
        try {
            openRepository();
        } finally {
            closeRepository();
        }
    }

    public File getRepositoryRootDir() {
        return myReposRootDir;
    }

    public int getReposFormat() {
        return myFSFS.getReposFormat();
    }

    public long getLatestRevision() throws SVNException {
        try {
            openRepository();
            return myFSFS.getYoungestRevision();
        } finally {
            closeRepository();
        }
    }

    public long getDatedRevision(Date date) throws SVNException {
        if (date == null) {
            return getLatestRevision();
        }

        try {
            openRepository();
            return myFSFS.getDatedRevision(date);
        } finally {
            closeRepository();
        }
    }

    public SVNProperties getRevisionProperties(long revision, SVNProperties properties) throws SVNException {
        assertValidRevision(revision);
        try {
            openRepository();
            properties = properties == null ? new SVNProperties() : properties;
            properties.putAll(myFSFS.getRevisionProperties(revision));
        } finally {
            closeRepository();
        }
        return properties;
    }

    public void setRevisionPropertyValue(long revision, String propertyName, SVNPropertyValue propertyValue) throws SVNException {
        setRevisionPropertyValue(revision, propertyName, propertyValue, false);
    }

    public void setRevisionPropertyValue(long revision, String propertyName, SVNPropertyValue propertyValue, boolean bypassHooks) throws SVNException {
        setRevisionPropertyValue(revision, propertyName, propertyValue, bypassHooks, bypassHooks);
    }

    public void setRevisionPropertyValue(long revision, String propertyName, SVNPropertyValue propertyValue, boolean bypassPreRevpropHook, boolean bypassPostRevpropHook) throws SVNException {
         assertValidRevision(revision);
        try {
            openRepository();
            FSRepositoryUtil.validateProperty(propertyName, propertyValue);
            String userName = getUserName();
            SVNProperties revProps = myFSFS.getRevisionProperties(revision);
            SVNPropertyValue oldValue = revProps.getSVNPropertyValue(propertyName);
            String action = null;
            if (propertyValue == null) {
                action = FSHooks.REVPROP_DELETE;
            } else if (oldValue == null) {
                action = FSHooks.REVPROP_ADD;
            } else {
                action = FSHooks.REVPROP_MODIFY;
            }

            byte[] bytes = SVNPropertyValue.getPropertyAsBytes(propertyValue);
            if (isHooksEnabled() && FSHooks.isHooksEnabled() && !bypassPreRevpropHook) {
                FSHooks.runPreRevPropChangeHook(myReposRootDir, propertyName, bytes, userName, revision, action);
            }
            myFSFS.setRevisionProperty(revision, propertyName, propertyValue);
            if (isHooksEnabled() && FSHooks.isHooksEnabled() && !bypassPostRevpropHook) {
                FSHooks.runPostRevPropChangeHook(myReposRootDir, propertyName, bytes, userName, revision, action);
            }
        } finally {
            closeRepository();
        }
    }

    public SVNPropertyValue getRevisionPropertyValue(long revision, String propertyName) throws SVNException {
        assertValidRevision(revision);
        if (propertyName == null) {
            return null;
        }
        try {
            openRepository();
            return myFSFS.getRevisionProperties(revision).getSVNPropertyValue(propertyName);
        } finally {
            closeRepository();
        }
    }

    public SVNNodeKind checkPath(String path, long revision) throws SVNException {
        try {
            openRepository();
            if (!SVNRepository.isValidRevision(revision)) {
                revision = myFSFS.getYoungestRevision();
            }
            String repositoryPath = getRepositoryPath(path);
            FSRevisionRoot root = myFSFS.createRevisionRoot(revision);
            return root.checkNodeKind(repositoryPath);
        } catch (SVNException e) {
            if (e.getErrorMessage().getErrorCode() == SVNErrorCode.FS_NOT_DIRECTORY) {
                return SVNNodeKind.NONE;
            }
            throw e;
        } finally {
            closeRepository();
        }
    }

    public long getFile(String path, long revision, SVNProperties properties, OutputStream contents) throws SVNException {
        try {
            openRepository();
            if (!SVNRepository.isValidRevision(revision)) {
                revision = myFSFS.getYoungestRevision();
            }

            String repositoryPath = getRepositoryPath(path);
            FSRevisionRoot root = myFSFS.createRevisionRoot(revision);

            if (contents != null) {
                InputStream fileStream = null;
                try {
                    fileStream = root.getFileStreamForPath(new SVNDeltaCombiner(), repositoryPath);
                    FSRepositoryUtil.copy(fileStream, contents, getCanceller());
                } finally {
                    SVNFileUtil.closeFile(fileStream);
                }
            }
            if (properties != null) {
                FSRevisionNode revNode = root.getRevisionNode(repositoryPath);
                if (revNode.getFileMD5Checksum() != null) {
                    properties.put(SVNProperty.CHECKSUM, revNode.getFileMD5Checksum());
                }
                if (revision >= 0) {
                    properties.put(SVNProperty.REVISION, Long.toString(revision));
                }
                properties.putAll(collectProperties(revNode));
            }
            return revision;
        } finally {
            closeRepository();
        }
    }

    public long getDir(String path, long revision, SVNProperties properties, ISVNDirEntryHandler handler) throws SVNException {
        return getDir(path, revision, properties, SVNDirEntry.DIRENT_ALL, handler);
    }

    public SVNDirEntry getDir(String path, long revision, boolean includeCommitMessages, Collection entries) throws SVNException {
        try {
            openRepository();
            if (!SVNRepository.isValidRevision(revision)) {
                revision = myFSFS.getYoungestRevision();
            }

            String repositoryPath = getRepositoryPath(path);
            SVNURL parentURL = getLocation().appendPath(path, false);

            FSRevisionRoot root = myFSFS.createRevisionRoot(revision);
            FSRevisionNode parent = root.getRevisionNode(repositoryPath);

            if (entries != null) {
                entries.addAll(getDirEntries(parent, parentURL, SVNDirEntry.DIRENT_ALL));
            }

            SVNDirEntry parentDirEntry = buildDirEntry(new FSEntry(parent.getId(), parent.getType(), ""), parentURL, parent, SVNDirEntry.DIRENT_ALL);
            return parentDirEntry;
        } finally {
            closeRepository();
        }
    }

    public long getDir(String path, long revision, SVNProperties properties, int entryFields, ISVNDirEntryHandler handler) throws SVNException {
        try {
            openRepository();
            if (!SVNRepository.isValidRevision(revision)) {
                revision = myFSFS.getYoungestRevision();
            }
            String repositoryPath = getRepositoryPath(path);
            FSRevisionRoot root = myFSFS.createRevisionRoot(revision);

            FSRevisionNode parent = root.getRevisionNode(repositoryPath);
            if (handler != null) {
                SVNURL parentURL = getLocation().appendPath(path, false);
                Collection entriesCollection = getDirEntries(parent, parentURL, entryFields);
                Iterator entries = entriesCollection.iterator();
                while (entries.hasNext()) {
                    SVNDirEntry entry = (SVNDirEntry) entries.next();
                    handler.handleDirEntry(entry);
                }
            }
            if (properties != null) {
                properties.putAll(collectProperties(parent));
            }
            return revision;
        } finally {
            closeRepository();
        }
    }

    protected int getFileRevisionsImpl(String path, long startRevision, long endRevision, boolean includeMergedRevisions, ISVNFileRevisionHandler handler) throws SVNException {
        try {
            openRepository();
            path = getRepositoryPath(path);
            long latestRevision = INVALID_REVISION;
            if (isInvalidRevision(startRevision)) {
                latestRevision = myFSFS.getYoungestRevision();
                startRevision = latestRevision;
            }

            if (isInvalidRevision(endRevision)) {
                if (isInvalidRevision(latestRevision)) {
                    latestRevision = myFSFS.getYoungestRevision();
                }
                endRevision = latestRevision;
            }

            FSFileRevisionsFinder finder = new FSFileRevisionsFinder(myFSFS);
            int fileRevsNumber = finder.getFileRevisions(path, startRevision, endRevision,
                    includeMergedRevisions, handler);
            return fileRevsNumber;
        } finally {
            closeRepository();
        }
    }

    protected long logImpl(String[] targetPaths, long startRevision, long endRevision, boolean
                    discoverChangedPaths, boolean strictNode, long limit,
                    boolean includeMergedRevisions, String[] revPropNames,
                    ISVNLogEntryHandler handler) throws SVNException {
        try {
            openRepository();
            if (targetPaths == null || targetPaths.length == 0) {
                targetPaths = new String[] {""};
            }
            String[] absPaths = new String[targetPaths.length];
            for (int i = 0; i < targetPaths.length; i++) {
                absPaths[i] = getRepositoryPath(targetPaths[i]);
            }
            long youngestRev = myFSFS.getYoungestRevision();

            if (isInvalidRevision(startRevision)) {
                startRevision = youngestRev;
            }
            if (isInvalidRevision(endRevision)) {
                endRevision = youngestRev;
            }

            long histStart = startRevision;
            long histEnd = endRevision;

            if (startRevision > youngestRev) {
                SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NO_SUCH_REVISION, "No such revision {0}", String.valueOf(startRevision));
                SVNErrorManager.error(err, SVNLogType.FSFS);
            }
            if (endRevision > youngestRev) {
                SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NO_SUCH_REVISION, "No such revision {0}", String.valueOf(endRevision));
                SVNErrorManager.error(err, SVNLogType.FSFS);
            }

            boolean isDescendingOrder = startRevision >= endRevision;
            if (isDescendingOrder) {
                histStart = endRevision;
                histEnd = startRevision;
            }

            FSLog logDriver = getLogDriver(absPaths, limit, histStart, histEnd, isDescendingOrder,
                    discoverChangedPaths, strictNode, includeMergedRevisions, revPropNames, handler);
            return logDriver.runLog();
        } finally {
            closeRepository();
        }
    }

    protected int getLocationsImpl(String path, long pegRevision, long[] revisions,
            ISVNLocationEntryHandler handler) throws SVNException {
        assertValidRevision(pegRevision);
        for (int i = 0; i < revisions.length; i++) {
            assertValidRevision(revisions[i]);
        }
        try {
            openRepository();
            path = getRepositoryPath(path);
            FSLocationsFinder locationsFinder = getLocationsFinder();
            return locationsFinder.traceNodeLocations(path, pegRevision, revisions, handler);
        } finally {
            closeRepository();
        }
    }

    protected long getLocationSegmentsImpl(String path, long pegRevision, long startRevision, long endRevision,
            ISVNLocationSegmentHandler handler) throws SVNException {
        try {
            openRepository();
            path = getRepositoryPath(path);
            FSLocationsFinder locationsFinder = getLocationsFinder();
            return locationsFinder.getNodeLocationSegments(path, pegRevision, startRevision, endRevision, handler);
        } finally {
            closeRepository();
        }
    }

    public void replay(long lowRevision, long highRevision, boolean sendDeltas, ISVNEditor editor) throws SVNException {
        try {
            openRepository();
            FSRevisionRoot root = myFSFS.createRevisionRoot(highRevision);
            String basePath = getRepositoryPath("");
            FSRepositoryUtil.replay(myFSFS, root, basePath, lowRevision, sendDeltas, editor);
        } finally {
            closeRepository();
        }
    }

    public SVNDirEntry info(String path, long revision) throws SVNException {
        try {
            openRepository();
            path = getRepositoryPath(path);
            if (FSRepository.isInvalidRevision(revision)) {
                revision = myFSFS.getYoungestRevision();
            }
            FSRevisionRoot root = myFSFS.createRevisionRoot(revision);

            if (root.checkNodeKind(path) == SVNNodeKind.NONE) {
                return null;
            }

            FSRevisionNode revNode = root.getRevisionNode(path);
            String fullPath = getFullPath(path);
            String parentFullPath = "/".equals(path) ? fullPath : SVNPathUtil.removeTail(fullPath);
            SVNURL url = getLocation().setPath(parentFullPath, false);
            String name = "/".equals(path) ? "" : SVNPathUtil.tail(path);
            FSEntry fsEntry = new FSEntry(revNode.getId(), revNode.getType(), name);
            SVNDirEntry entry = buildDirEntry(fsEntry, url, revNode, SVNDirEntry.DIRENT_ALL);
            return entry;
        } finally {
            closeRepository();
        }
    }

    public ISVNEditor getCommitEditor(String logMessage, Map locks, boolean keepLocks, ISVNWorkspaceMediator mediator) throws SVNException {
        try {
            openRepository();
        } catch (SVNException svne) {
            closeRepository();
            throw svne;
        }
        // fetch user name!
        String author = getUserName();
        return new FSCommitEditor(getRepositoryPath(""), logMessage, author, locks, keepLocks, null, myFSFS, this);
    }

    public SVNLock getLock(String path) throws SVNException {
        try {
            openRepository();
            path = getRepositoryPath(path);
            SVNLock lock = myFSFS.getLockHelper(path, false);
            return lock;
        } finally {
            closeRepository();
        }
    }

    public SVNLock[] getLocks(String path) throws SVNException {
        try {
            openRepository();
            path = getRepositoryPath(path);

            File digestFile = myFSFS.getDigestFileFromRepositoryPath(path);
            final ArrayList locks = new ArrayList();
            ISVNLockHandler handler = new ISVNLockHandler() {

                public void handleLock(String path, SVNLock lock, SVNErrorMessage error) throws SVNException {
                    locks.add(lock);
                }

                public void handleUnlock(String path, SVNLock lock, SVNErrorMessage error) throws SVNException {
                }
            };

            myFSFS.walkDigestFiles(digestFile, handler, false);

            return (SVNLock[]) locks.toArray(new SVNLock[locks.size()]);
        } finally {
            closeRepository();
        }
    }

    public void lock(Map pathsToRevisions, String comment, boolean force, ISVNLockHandler handler) throws SVNException {
        lock(pathsToRevisions, comment, force, false, handler);
    }

    public void lock(Map pathsToRevisions, String comment, boolean force, boolean isDAVComment, ISVNLockHandler handler) throws SVNException {
        try {
            openRepository();
            for (Iterator paths = pathsToRevisions.keySet().iterator(); paths.hasNext();) {
                String path = (String) paths.next();
                Long revision = (Long) pathsToRevisions.get(path);
                String reposPath = getRepositoryPath(path);
                long curRevision = (revision == null || isInvalidRevision(revision.longValue())) ? myFSFS.getYoungestRevision() : revision.longValue();
                SVNLock lock = null;
                SVNErrorMessage error = null;
                try {
                    lock = myFSFS.lockPath(reposPath, null, getUserName(), comment, null, curRevision, force, isDAVComment);
                } catch (SVNException svne) {
                    error = svne.getErrorMessage();
                    if (!FSErrors.isLockError(error)) {
                        throw svne;
                    }
                }
                if (handler != null) {
                    handler.handleLock(reposPath, lock, error);
                }
            }
        } finally {
            closeRepository();
        }
    }

    public void unlock(Map pathToTokens, boolean force, ISVNLockHandler handler) throws SVNException {
        try {
            openRepository();
            for (Iterator paths = pathToTokens.keySet().iterator(); paths.hasNext();) {
                String path = (String) paths.next();
                String token = (String) pathToTokens.get(path);
                String reposPath = getRepositoryPath(path);
                SVNErrorMessage error = null;
                try {
                    myFSFS.unlockPath(reposPath, token, getUserName(), force, true);
                } catch (SVNException svne) {
                    error = svne.getErrorMessage();
                    if (!FSErrors.isUnlockError(error)) {
                        throw svne;
                    }
                }
                if (handler != null) {
                    handler.handleUnlock(reposPath, new SVNLock(reposPath, token, null, null, null, null), error);
                }
            }
        } finally {
            closeRepository();
        }
    }

    public void finishReport() throws SVNException {
        try {
            myReporterContext.drive();
        } finally {
            myReporterContext.dispose();
        }
    }

    public void abortReport() throws SVNException {
        if(myReporterContext != null){
            myReporterContext.dispose();
        }
    }

    public void closeSession() {
    }

    public static boolean isInvalidRevision(long revision) {
        return SVNRepository.isInvalidRevision(revision);
    }

    public static boolean isValidRevision(long revision) {
        return SVNRepository.isValidRevision(revision);
    }

    public void setPath(String path, String lockToken, long revision, boolean startEmpty) throws SVNException {
        setPath(path, lockToken, revision, SVNDepth.INFINITY, startEmpty);
    }

    public void deletePath(String path) throws SVNException {
        myReporterContext.writePathInfoToReportFile(path, null, null, SVNRepository.INVALID_REVISION, false, SVNDepth.INFINITY);
    }

    public void linkPath(SVNURL url, String path, String lockToken, long revision, boolean startEmpty) throws SVNException {
        linkPath(url, path, lockToken, revision, SVNDepth.INFINITY, startEmpty);
    }

    public void linkPath(SVNURL url, String path, String lockToken, long revision, SVNDepth depth, boolean startEmpty) throws SVNException {
        assertValidRevision(revision);
        if (depth == SVNDepth.EXCLUDE) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.REPOS_BAD_ARGS, "Depth 'exclude' not supported for link");
            SVNErrorManager.error(err, SVNLogType.FSFS);
        }

        SVNURL reposRootURL = getRepositoryRoot(false);
        if (url.toDecodedString().indexOf(reposRootURL.toDecodedString()) == -1) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.RA_ILLEGAL_URL, "''{0}''\nis not the same repository as\n''{1}''", new Object[] {
                    url, reposRootURL
            });
            SVNErrorManager.error(err, SVNLogType.FSFS);
        }
        String reposLinkPath = url.toDecodedString().substring(reposRootURL.toDecodedString().length());
        if ("".equals(reposLinkPath)) {
            reposLinkPath = "/";
        }
        myReporterContext.writePathInfoToReportFile(path, reposLinkPath, lockToken, revision, startEmpty, depth);
    }

    public void setPath(String path, String lockToken, long revision, SVNDepth depth, boolean startEmpty) throws SVNException {
        assertValidRevision(revision);
        myReporterContext.writePathInfoToReportFile(path, null, lockToken, revision, startEmpty, depth);
    }

    public FSTranslateReporter beginReport(long revision, SVNURL url, String target, boolean ignoreAncestry,
            boolean sendTextDeltas, boolean sendCopyFromArgs, SVNDepth depth, ISVNEditor editor) throws SVNException {
        openRepository();
        makeReporterContext(revision, target, url, depth, ignoreAncestry, sendTextDeltas, sendCopyFromArgs, editor);
        return new FSTranslateReporter(this);
    }

    public void update(long revision, String target, SVNDepth depth, boolean sendCopyFromArgs,
            ISVNReporterBaton reporter, ISVNEditor editor) throws SVNException {
        try {
            openRepository();
            makeReporterContext(revision, target, null, depth, false, true, sendCopyFromArgs,
                    editor);
            reporter.report(this);
        } finally {
            closeRepository();
        }
    }

    public void update(SVNURL url, long revision, String target, SVNDepth depth,
            ISVNReporterBaton reporter, ISVNEditor editor) throws SVNException {
        try {
            openRepository();
            makeReporterContext(revision, target, url, depth, true, true, false, editor);
            reporter.report(this);
        } finally {
            closeRepository();
        }
    }

    public void diff(SVNURL url, long targetRevision, long revision, String target, boolean ignoreAncestry,
                     SVNDepth depth, boolean getContents, ISVNReporterBaton reporter, ISVNEditor editor) throws SVNException {
        try {
            openRepository();
            makeReporterContext(targetRevision, target, url, depth, ignoreAncestry, getContents,
                    false, editor);
            reporter.report(this);
        } finally {
            closeRepository();
        }
    }

    public void status(long revision, String target, SVNDepth depth, ISVNReporterBaton reporter,
            ISVNEditor editor) throws SVNException {
        try {
            openRepository();
            makeReporterContext(revision, target, null, depth, false, false, false, editor);
            reporter.report(this);
        } finally {
            closeRepository();
        }
    }

	public boolean hasCapability(SVNCapability capability) throws SVNException {
		if (capability == SVNCapability.DEPTH ||
		        capability == SVNCapability.LOG_REVPROPS ||
				capability == SVNCapability.PARTIAL_REPLAY ||
				capability == SVNCapability.COMMIT_REVPROPS) {
			return true;
		} else if (capability == SVNCapability.ATOMIC_REVPROPS) {
		    return false;
		} else if (capability == SVNCapability.MERGE_INFO) {
		    try {
		        getMergeInfoImpl(new String[] { "" }, 0, SVNMergeInfoInheritance.EXPLICIT, false);
		    } catch (SVNException svne) {
		        SVNErrorCode code = svne.getErrorMessage().getErrorCode();
		        if (code == SVNErrorCode.UNSUPPORTED_FEATURE) {
		            return false;
		        } else if (code == SVNErrorCode.FS_NOT_FOUND) {
		            return true;
		        }
		        throw svne;
		    }
		    return true;
		} else if (capability == SVNCapability.INHERITED_PROPS) {
		    return true;
		} else if (capability == SVNCapability.EPHEMERAL_PROPS) {
            return true;
        } else if (capability == SVNCapability.GET_FILE_REVS_REVERSED) {
            return false;
        }
		SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN_CAPABILITY,
				"Don''t know anything about capability ''{0}''", capability);
		SVNErrorManager.error(err, SVNLogType.FSFS);
		return false;
	}

    protected void getInheritedPropertiesImpl(String path, long revision, String propertyName, ISVNInheritedPropertiesHandler handler) throws SVNException {
        try {
            openRepository();
            path = getRepositoryPath(path);

            String parentPath = path;

            final FSRevisionRoot root = myFSFS.createRevisionRoot(revision);
            while(!"/".equals(parentPath) && !"".equals(parentPath)) {
                parentPath = SVNPathUtil.removeTail(parentPath);
                if ("".equals(parentPath)) {
                    parentPath = "/";
                }

                final FSRevisionNode node = root.getRevisionNode(parentPath);
                final SVNProperties properties = myFSFS.getProperties(node);
                if (properties != null && handler != null && !properties.isEmpty()) {
                    if (propertyName != null && properties.containsName(propertyName)) {
                        final SVNProperties singleProperty = new SVNProperties();
                        singleProperty.put(propertyName, properties.getSVNPropertyValue(propertyName));
                        handler.handleInheritedProperites(parentPath, singleProperty);
                    } else if (propertyName == null) {
                        handler.handleInheritedProperites(parentPath, properties);
                    }
                }
            }
        } finally {
            closeRepository();
        }
    }

    void closeRepository() throws SVNException {
        if (myFSFS != null) {
            myFSFS.close();
        }
        unlock();
    }

    protected Map getMergeInfoImpl(String[] paths, long revision, SVNMergeInfoInheritance inherit,
            boolean includeDescendants) throws SVNException {
        try {
            openRepository();
            if (!isValidRevision(revision)) {
                revision = myFSFS.getYoungestRevision();
            }
            FSRevisionRoot root = myFSFS.createRevisionRoot(revision);
            String[] absPaths = new String[paths.length];
            for (int i = 0; i < paths.length; i++) {
                absPaths[i] = getRepositoryPath(paths[i]);
            }
            SVNMergeInfoManager mergeInfoManager = getMergeInfoManager();
            return mergeInfoManager.getMergeInfo(absPaths, root, inherit, includeDescendants);
        } finally {
            closeRepository();
        }
    }

    protected ISVNEditor getCommitEditorInternal(Map locks, boolean keepLocks, SVNProperties revProps, ISVNWorkspaceMediator mediator) throws SVNException {
        try {
            openRepository();
        } catch (SVNException svne) {
            closeRepository();
            throw svne;
        }
        revProps = revProps == null ? new SVNProperties() : revProps;
        if (!revProps.containsName(SVNRevisionProperty.AUTHOR)) {
            revProps.put(SVNRevisionProperty.AUTHOR, getUserName());
        }
        return new FSCommitEditor(getRepositoryPath(""), locks, keepLocks, null, myFSFS, this, revProps);
    }

    protected void replayRangeImpl(long startRevision, long endRevision, long lowRevision, boolean sendDeltas,
            ISVNReplayHandler handler) throws SVNException {
        SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.RA_NOT_IMPLEMENTED);
        SVNErrorManager.error(err, SVNLogType.FSFS);
    }

    protected long getDeletedRevisionImpl(String path, long pegRevision, long endRevision) throws SVNException {
        try {
            openRepository();
            path = getRepositoryPath(path);
            return myFSFS.getDeletedRevision(path, pegRevision, endRevision);
        } finally {
            closeRepository();
        }
    }

    private void openRepository() throws SVNException {
        try {
            openRepositoryRoot();
        } catch (SVNException svne) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.RA_LOCAL_REPOS_OPEN_FAILED, "Unable to connect to a repository at URL ''{0}''", getLocation().toString());
            err.setChildErrorMessage(svne.getErrorMessage());
            SVNErrorManager.error(err.wrap("Unable to open repository ''{0}''", new Object[]{getLocation()}), SVNLogType.FSFS);
        }
    }

    private void openRepositoryRoot() throws SVNException {
        lock();

        String hostName = getLocation().getHost();
        boolean hasCustomHostName = !"".equals(hostName) &&
                                    !"localhost".equalsIgnoreCase(hostName);

        if (!SVNFileUtil.isWindows && hasCustomHostName) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.RA_ILLEGAL_URL, "Local URL ''{0}'' contains unsupported hostname", getLocation().toString());
            SVNErrorManager.error(err, SVNLogType.FSFS);
        }

        String startPath = SVNEncodingUtil.uriDecode(getLocation().getURIEncodedPath());
        String rootPath = FSFS.findRepositoryRoot(hasCustomHostName ? hostName : null, startPath);
        if (rootPath == null) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.RA_LOCAL_REPOS_OPEN_FAILED, "Unable to open repository ''{0}''", getLocation().toString());
            SVNErrorManager.error(err, SVNLogType.FSFS);
        }

        String dirPath = rootPath.replaceFirst("\\|", "\\:");

        myReposRootDir = hasCustomHostName ? new File("\\\\" + hostName, dirPath).getAbsoluteFile() :
                                             new File(dirPath).getAbsoluteFile();
        myFSFS = new FSFS(myReposRootDir);
        myFSFS.setHooksEnabled(isHooksEnabled());
        myFSFS.open();
        setRepositoryCredentials(myFSFS.getUUID(), getLocation().setPath(rootPath, false));
    }

    private Collection getDirEntries(FSRevisionNode parent, SVNURL parentURL, int entryFields) throws SVNException {
        Map entries = parent.getDirEntries(myFSFS);
        Set keys = entries.keySet();
        Iterator dirEntries = keys.iterator();
        Collection dirEntriesList = new LinkedList();
        while (dirEntries.hasNext()) {
            String name = (String) dirEntries.next();
            FSEntry repEntry = (FSEntry) entries.get(name);
            if (repEntry != null) {
                dirEntriesList.add(buildDirEntry(repEntry, parentURL, null, entryFields));
            }
        }
        return dirEntriesList;
    }

    private SVNProperties collectProperties(FSRevisionNode revNode) throws SVNException {
        SVNProperties properties = new SVNProperties();
        SVNProperties versionedProps = revNode.getProperties(myFSFS);
        if (versionedProps != null && versionedProps.size() > 0) {
            properties.putAll(versionedProps);
        }
        SVNProperties metaprops = null;
        try {
            metaprops = myFSFS.compoundMetaProperties(revNode.getCreatedRevision());
        } catch (SVNException svne) {
            //
        }
        if (metaprops != null && metaprops.size() > 0) {
            properties.putAll(metaprops);
        }
        return properties;
    }

    private SVNDirEntry buildDirEntry(FSEntry repEntry, SVNURL parentURL, FSRevisionNode entryNode, int entryFields) throws SVNException {
        entryNode = entryNode == null ? myFSFS.getRevisionNode(repEntry.getId()) : entryNode;

        SVNNodeKind kind = null;
        if ((entryFields & SVNDirEntry.DIRENT_KIND) != 0) {
            kind = repEntry.getType();
        }

        long size = 0;
        if ((entryFields & SVNDirEntry.DIRENT_SIZE) != 0) {
            if (entryNode.getType() == SVNNodeKind.FILE) {
                size = entryNode.getFileLength();
            }
        }

        boolean hasProps = false;
        if ((entryFields & SVNDirEntry.DIRENT_HAS_PROPERTIES) != 0) {
            SVNProperties props = entryNode.getProperties(myFSFS);
            hasProps = props != null && props.size() > 0;
        }

        String lastAuthor = null;
        String log = null;
        Date lastCommitDate = null;
        long revision = SVNRepository.INVALID_REVISION;
        if ((entryFields & SVNDirEntry.DIRENT_TIME) != 0 ||
                (entryFields & SVNDirEntry.DIRENT_LAST_AUTHOR) != 0 ||
                (entryFields & SVNDirEntry.DIRENT_CREATED_REVISION) != 0 ||
                (entryFields & SVNDirEntry.DIRENT_COMMIT_MESSAGE) != 0) {
            revision = repEntry.getId().getRevision();
            SVNProperties revProps = myFSFS.getRevisionProperties(repEntry.getId().getRevision());
            if (revProps != null && revProps.size() > 0) {
                lastAuthor = revProps.getStringValue(SVNRevisionProperty.AUTHOR);
                log = revProps.getStringValue(SVNRevisionProperty.LOG);
                String timeString = revProps.getStringValue(SVNRevisionProperty.DATE);
                lastCommitDate = timeString != null ? SVNDate.parseDateString(timeString) : null;
            }
        }

        SVNURL entryURL = parentURL.appendPath(repEntry.getName(), false);
        SVNDirEntry dirEntry = new SVNDirEntry(entryURL, getRepositoryRoot(false), repEntry.getName(), kind, size, hasProps, revision, lastCommitDate, lastAuthor, log);
        dirEntry.setRelativePath(repEntry.getName());
        return dirEntry;
    }

    private void makeReporterContext(long targetRevision, String target, SVNURL switchURL,
            SVNDepth depth, boolean ignoreAncestry, boolean textDeltas, boolean sendCopyFromArgs,
            ISVNEditor editor) throws SVNException {
        if (depth == SVNDepth.EXCLUDE) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.REPOS_BAD_ARGS, "Request depth 'exclude' not supported");
            SVNErrorManager.error(err, SVNLogType.FSFS);
        }

        target = target == null ? "" : target;

        if (!isValidRevision(targetRevision)) {
            targetRevision = myFSFS.getYoungestRevision();
        }

        String switchPath = null;

        if (switchURL != null) {
            SVNURL reposRootURL = getRepositoryRoot(false);

            if (switchURL.toDecodedString().indexOf(reposRootURL.toDecodedString()) == -1) {
                SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.RA_ILLEGAL_URL, "''{0}''\nis not the same repository as\n''{1}''", new Object[] {
                        switchURL, getRepositoryRoot(false)
                });
                SVNErrorManager.error(err, SVNLogType.FSFS);
            }

            switchPath = switchURL.toDecodedString().substring(reposRootURL.toDecodedString().length());

            if ("".equals(switchPath)) {
                switchPath = "/";
            }
        }

        String anchor = getRepositoryPath("");
        String fullTargetPath = switchPath != null ? switchPath : SVNPathUtil.getAbsolutePath(SVNPathUtil.append(anchor, target));

        if (myReporterContext == null) {
            myReporterContext = new FSUpdateContext(this, myFSFS, targetRevision,
                                                    SVNFileUtil.createTempFile("report", ".tmp"),
                                                    target, fullTargetPath,
                                                    switchURL == null ? false : true,
                                                    depth, ignoreAncestry, textDeltas,
                                                    sendCopyFromArgs, editor);
        } else {
            myReporterContext.reset(this, myFSFS, targetRevision, SVNFileUtil.createTempFile("report", ".tmp"),
                                    target, fullTargetPath, switchURL == null ? false : true, depth,
                                    ignoreAncestry, textDeltas, sendCopyFromArgs, editor);
        }
    }

    private String getUserName() throws SVNException {
        if (getLocation().getUserInfo() != null && getLocation().getUserInfo().trim().length() > 0) {
            return getLocation().getUserInfo();
        }
        if (getAuthenticationManager() != null) {
            try {
                String realm = getRepositoryUUID(true);
                ISVNAuthenticationManager authManager = getAuthenticationManager();
                SVNAuthentication auth = authManager.getFirstAuthentication(ISVNAuthenticationManager.USERNAME, realm, getLocation());

                while (auth != null) {
                    String userName = auth.getUserName();
                    if (userName == null) {
                        // anonymous.
                        return null;
                    }
                    if ("".equals(userName.trim())) {
                        userName = System.getProperty("user.name");
                    }
                    auth = new SVNUserNameAuthentication(userName, auth.isStorageAllowed(), getLocation(), false);
                    if (userName != null && !"".equals(userName.trim())) {
                        BasicAuthenticationManager.acknowledgeAuthentication(true, ISVNAuthenticationManager.USERNAME, realm, null, auth, myLocation, authManager);
                        return auth.getUserName();
                    }
                    SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.AUTHN_CREDS_UNAVAILABLE, "Empty user name is not allowed");
                    BasicAuthenticationManager.acknowledgeAuthentication(false, ISVNAuthenticationManager.USERNAME, realm, err, auth, myLocation, authManager);
                    auth = authManager.getNextAuthentication(ISVNAuthenticationManager.USERNAME, realm, getLocation());
                }
                // auth manager returned null - that is cancellation.
                SVNErrorManager.cancel("Authentication cancelled", SVNLogType.FSFS);
            } catch (SVNCancelException e) {
                throw e;
            } catch (SVNAuthenticationException e) {
                // no more credentials, use system user name.
            } catch (SVNException e) {
                // generic error.
                throw e;
            }
        }
        // anonymous
        return null;
    }

    private FSLocationsFinder getLocationsFinder() {
        if (myLocationsFinder == null) {
            myLocationsFinder = new FSLocationsFinder(getFSFS());
        } else {
            myLocationsFinder.reset(getFSFS());
        }

        return myLocationsFinder;
    }

    private SVNMergeInfoManager getMergeInfoManager() {
        if (myMergeInfoManager == null) {
            myMergeInfoManager = new SVNMergeInfoManager();
        }
        return myMergeInfoManager;
    }

    private FSLog getLogDriver(String[] absPaths, long limit, long histStart, long histEnd,
            boolean isDescendingOrder, boolean discoverChangedPaths, boolean strictNode,
            boolean includeMergedRevisions, String[] revPropNames, ISVNLogEntryHandler handler) {
        if (myLogDriver == null) {
            myLogDriver = new FSLog(myFSFS, absPaths, limit, histStart, histEnd, isDescendingOrder,
                    discoverChangedPaths, strictNode, includeMergedRevisions, revPropNames, handler);
        } else {
            myLogDriver.reset(myFSFS, absPaths, limit, histStart, histEnd, isDescendingOrder,
                    discoverChangedPaths, strictNode, includeMergedRevisions, revPropNames, handler);
        }
        return myLogDriver;
    }
}