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

import org.tmatesoft.svn.core.*;
import org.tmatesoft.svn.core.internal.util.*;
import org.tmatesoft.svn.core.internal.wc.*;
import org.tmatesoft.svn.core.wc.*;
import org.tmatesoft.svn.util.SVNDebugLog;
import org.tmatesoft.svn.util.SVNLogType;

import java.io.*;
import java.util.*;


/**
 * @version 1.3
 * @author  TMate Software Ltd.
 */
public abstract class SVNAdminArea {

    protected static final String ADM_KILLME = "KILLME";
    private static volatile boolean ourIsCleanupSafe;

    protected Map myBaseProperties;
    protected Map myProperties;
    protected Map myWCProperties;
    protected Map myEntries;
    protected boolean myWasLocked;

    private ISVNCommitParameters myCommitParameters;
    private Map myRevertProperties;
    private File myDirectory;
    private SVNWCAccess myWCAccess;
    private File myAdminRoot;
    private int myWCFormatVersion;

    public static synchronized void setSafeCleanup(boolean safe) {
        ourIsCleanupSafe = safe;
    }

    public static synchronized boolean isSafeCleanup() {
        return ourIsCleanupSafe;
    }

    public abstract boolean isLocked() throws SVNException;

    public abstract boolean isVersioned();

    protected abstract boolean isEntryPropertyApplicable(String name);

    public abstract boolean lock(boolean stealLock) throws SVNException;

    public abstract boolean unlock() throws SVNException;

    public abstract SVNVersionedProperties getBaseProperties(String name) throws SVNException;

    public abstract SVNVersionedProperties getRevertProperties(String name) throws SVNException;

    public abstract SVNVersionedProperties getWCProperties(String name) throws SVNException;

    public abstract SVNVersionedProperties getProperties(String name) throws SVNException;

    public abstract void saveVersionedProperties(SVNLog log, boolean close) throws SVNException;

    public abstract void installProperties(String name, SVNProperties baseProps, SVNProperties workingProps,
            SVNLog log, boolean writeBaseProps, boolean close) throws SVNException;

    public abstract void saveWCProperties(boolean close) throws SVNException;

    public abstract void saveEntries(boolean close) throws SVNException;

    public abstract String getThisDirName();

    public abstract boolean hasPropModifications(String entryName) throws SVNException;

    public abstract boolean hasProperties(String entryName) throws SVNException;

    public abstract SVNAdminArea createVersionedDirectory(File dir, String url, String rootURL, String uuid, long revNumber, boolean createMyself, SVNDepth depth) throws SVNException;

    public abstract void postCommit(String fileName, long revisionNumber, boolean implicit, boolean rerun, SVNErrorCode errorCode) throws SVNException;

    public abstract void handleKillMe() throws SVNException;

    public abstract boolean hasTreeConflict(String name) throws SVNException;

    public abstract SVNTreeConflictDescription getTreeConflict(String name) throws SVNException;

    public abstract void addTreeConflict(SVNTreeConflictDescription conflict) throws SVNException;

    public abstract SVNTreeConflictDescription deleteTreeConflict(String name) throws SVNException;

    public abstract void setFileExternalLocation(String name, SVNURL url, SVNRevision pegRevision, SVNRevision revision, SVNURL reposRootURL) throws SVNException;

    public abstract int getFormatVersion();

    public void updateURL(String rootURL, boolean recursive) throws SVNException {
        SVNWCAccess wcAccess = getWCAccess();
        for (Iterator ents = entries(false); ents.hasNext();) {
            SVNEntry entry = (SVNEntry) ents.next();
            if (!getThisDirName().equals(entry.getName()) && entry.isDirectory() && recursive) {
                SVNAdminArea childDir = wcAccess.retrieve(getFile(entry.getName()));
                if (childDir != null) {
                    String childURL = SVNPathUtil.append(rootURL, SVNEncodingUtil.uriEncode(entry.getName()));
                    childDir.updateURL(childURL, recursive);
                }
                continue;
            }
            entry.setURL(getThisDirName().equals(entry.getName()) ? rootURL : SVNPathUtil.append(
                    rootURL, SVNEncodingUtil.uriEncode(entry.getName())));
        }
        saveEntries(false);
    }

    public boolean hasTextModifications(String name, boolean forceComparision) throws SVNException {
        return hasTextModifications(name, forceComparision, true, false);
    }

    public boolean hasTextModifications(String name, boolean forceComparison, boolean compareTextBase, boolean compareChecksum) throws SVNException {
        File textFile = getFile(name);
        SVNFileType fileType = SVNFileType.getType(textFile);
        if (!(fileType == SVNFileType.FILE || fileType == SVNFileType.SYMLINK)) {
            return false;
        }
        if (fileType == SVNFileType.SYMLINK) {
        	forceComparison = true;
        }
        SVNEntry entry = null;
        if (!forceComparison) {
            boolean compare = false;
            try {
                entry = getEntry(name, false);
            } catch (SVNException svne) {
                compare = true;
            }

            if (!compare && entry == null) {
                compare = true;
            }

            if (!compare && isEntryPropertyApplicable(SVNProperty.WORKING_SIZE)) {
                if (entry.getWorkingSize() != SVNProperty.WORKING_SIZE_UNKNOWN &&
                    SVNFileUtil.getFileLength(textFile) != entry.getWorkingSize()) {
                    compare = true;
                }
            }

            if (!compare) {
                String textTime = entry.getTextTime();
                if (textTime == null) {
                    compare = true;
                } else {
                    long textTimeAsLong = SVNDate.parseDateAsMilliseconds(textTime);
                    long tstamp = SVNFileUtil.getFileLastModified(textFile);
                    if (textTimeAsLong != tstamp ) {
                        compare = true;
                    }
                }
            }

            if (!compare) {
                return false;
            }
        }

        File baseFile = getBaseFile(name, false);
        if (!baseFile.isFile()) {
            return true;
        }

        boolean differs = compareAndVerify(textFile, baseFile, compareTextBase, compareChecksum);
        if (!differs && isLocked()) {
            Map attributes = new SVNHashMap();
            attributes.put(SVNProperty.WORKING_SIZE, Long.toString(SVNFileUtil.getFileLength(textFile)));
            attributes.put(SVNProperty.TEXT_TIME, SVNDate.formatDate(new Date(SVNFileUtil.getFileLastModified(textFile))));
            modifyEntry(name, attributes, true, false);
        }
        return differs;
    }

    public boolean hasVersionedFileTextChanges(File file, File baseFile, boolean compareTextBase) throws SVNException {
        return compareAndVerify(file, baseFile, compareTextBase, false);
    }

    public String getRelativePath(SVNAdminArea anchor) {
        String absoluteAnchor = anchor.getRoot().getAbsolutePath();
        String ownAbsolutePath = getRoot().getAbsolutePath();
        String relativePath = ownAbsolutePath.substring(absoluteAnchor.length());

        relativePath = relativePath.replace(File.separatorChar, '/');
        if (relativePath.startsWith("/")) {
            relativePath = relativePath.substring(1);
        }
        if (relativePath.endsWith("/")) {
            relativePath = relativePath.substring(0, relativePath.length() - 1);
        }
        return relativePath;
    }

    public boolean tweakEntry(String name, String newURL, String reposRoot, long newRevision, boolean remove) throws SVNException {
        boolean rewrite = false;
        SVNEntry entry = getEntry(name, true);
        if (entry == null) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_NOT_FOUND, "No such entry: ''{0}''", name);
            SVNErrorManager.error(err, SVNLogType.WC);
        }

        if (newURL != null && (entry.getURL() == null || !newURL.equals(entry.getURL()))) {
            rewrite = true;
            entry.setURL(newURL);
        }

        if (reposRoot != null && (entry.getRepositoryRootURL() == null || !reposRoot.equals(entry.getRepositoryRoot()))
                && entry.getURL() != null && SVNPathUtil.isAncestor(reposRoot, entry.getURL())) {
            boolean setReposRoot = true;
            if (getThisDirName().equals(entry.getName())) {
                for (Iterator entries = entries(true); entries.hasNext();) {
                    SVNEntry childEntry = (SVNEntry) entries.next();
                    if (childEntry.getRepositoryRoot() == null && childEntry.getURL() != null &&
                            !SVNPathUtil.isAncestor(reposRoot, entry.getURL())) {
                        setReposRoot = false;
                        break;
                    }
                }
            }
            if (setReposRoot) {
                rewrite = true;
                entry.setRepositoryRoot(reposRoot);
            }
        }

        if (newRevision >= 0 &&
                !entry.isScheduledForAddition() &&
                !entry.isScheduledForReplacement() &&
                !entry.isCopied() &&
                entry.getRevision() != newRevision) {
            rewrite = true;
            entry.setRevision(newRevision);
        }

        if (remove && (entry.isDeleted() || (entry.isAbsent() && entry.getRevision() != newRevision))) {
            deleteEntry(name);
            rewrite = true;
        }
        return rewrite;
    }

    public boolean isKillMe() {
        return getAdminFile(ADM_KILLME).isFile();
    }

    public boolean markResolved(String name, boolean text, boolean props, SVNConflictChoice conflictChoice) throws SVNException {
        SVNEntry entry = getEntry(name, true);
        if (entry == null) {
            return false;
        }

        String autoResolveSource = null;
        File autoResolveSourceFile = null;
        boolean removeSource = false;
        if (conflictChoice == SVNConflictChoice.BASE) {
            autoResolveSource = entry.getConflictOld();
        } else if (conflictChoice == SVNConflictChoice.MINE_FULL) {
            autoResolveSource = entry.getConflictWorking();
        } else if (conflictChoice == SVNConflictChoice.THEIRS_FULL) {
            autoResolveSource = entry.getConflictNew();
        } else if (conflictChoice == SVNConflictChoice.THEIRS_CONFLICT || conflictChoice == SVNConflictChoice.MINE_CONFLICT) {
            if (entry.getConflictOld() != null && entry.getConflictNew() != null && entry.getConflictWorking() != null) {
                String conflictOld = entry.getConflictOld();
                String conflictNew = entry.getConflictNew();
                String conflictWorking = entry.getConflictWorking();

                ISVNMergerFactory factory = myWCAccess.getOptions().getMergerFactory();

                File conflictOldFile = SVNPathUtil.isAbsolute(conflictOld) ? new File(conflictOld) : getFile(conflictOld);
                File conflictNewFile = SVNPathUtil.isAbsolute(conflictNew) ? new File(conflictNew) : getFile(conflictNew);
                File conflictWorkingFile = SVNPathUtil.isAbsolute(conflictWorking) ? new File(conflictWorking) : getFile(conflictWorking);

                byte[] conflictStart = ("<<<<<<< " + conflictWorking).getBytes();
                byte[] conflictEnd = (">>>>>>> " + conflictNew).getBytes();
                byte[] separator = ("=======").getBytes();

                ISVNMerger merger = factory.createMerger(conflictStart, separator, conflictEnd);
                SVNDiffConflictChoiceStyle style = conflictChoice == SVNConflictChoice.THEIRS_CONFLICT ? SVNDiffConflictChoiceStyle.CHOOSE_LATEST :
                    SVNDiffConflictChoiceStyle.CHOOSE_MODIFIED;
                if (merger instanceof DefaultSVNMerger) {
                    DefaultSVNMerger defaultMerger = (DefaultSVNMerger) merger;
                    defaultMerger.setDiffConflictStyle(style);
                }

                autoResolveSourceFile = SVNAdminUtil.createTmpFile(this);

                SVNMergeFileSet mergeFileSet = new SVNMergeFileSet(this, null, conflictOldFile, conflictWorkingFile, name, conflictNewFile,
                        autoResolveSourceFile, null, null);

                String localLabel = ".working";
                String baseLabel = ".old";
                String latestLabel = ".new";

                mergeFileSet.setMergeLabels(baseLabel, localLabel, latestLabel);
                merger.mergeText(mergeFileSet, false, null);
                mergeFileSet.dispose();
                removeSource = true;
            }
        } else if (conflictChoice != SVNConflictChoice.MERGED) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.INCORRECT_PARAMS, "Invalid 'conflict_result' argument");
            SVNErrorManager.error(err, SVNLogType.DEFAULT);
        }

        if (autoResolveSource != null) {
            autoResolveSourceFile = getFile(autoResolveSource);
        }

        if (autoResolveSourceFile != null) {
            SVNFileUtil.copyFile(autoResolveSourceFile, getFile(name), false);
            if (removeSource) {
                SVNFileUtil.deleteFile(autoResolveSourceFile);
            }
        }

        if (!text && !props) {
            return false;
        }

        boolean filesDeleted = false;
        boolean updateEntry = false;
        if (text && entry.getConflictOld() != null) {
            File file = getFile(entry.getConflictOld());
            filesDeleted |= file.isFile();
            updateEntry = true;
            SVNFileUtil.deleteFile(file);
        }
        if (text && entry.getConflictNew() != null) {
            File file = getFile(entry.getConflictNew());
            filesDeleted |= file.isFile();
            updateEntry = true;
            SVNFileUtil.deleteFile(file);
        }
        if (text && entry.getConflictWorking() != null) {
            File file = getFile(entry.getConflictWorking());
            filesDeleted |= file.isFile();
            updateEntry = true;
            SVNFileUtil.deleteFile(file);
        }
        if (props && entry.getPropRejectFile() != null) {
            File file = getFile(entry.getPropRejectFile());
            filesDeleted |= file.isFile();
            updateEntry = true;
            SVNFileUtil.deleteFile(file);
        }
        if (updateEntry) {
            if (text) {
                entry.setConflictOld(null);
                entry.setConflictNew(null);
                entry.setConflictWorking(null);
            }
            if (props) {
                entry.setPropRejectFile(null);
            }
            saveEntries(false);
        }
        return filesDeleted;
    }

    public void restoreFile(String name) throws SVNException {
        SVNVersionedProperties props = getProperties(name);
        SVNEntry entry = getEntry(name, true);
        boolean special = props.getPropertyValue(SVNProperty.SPECIAL) != null;

        File src = getBaseFile(name, false);
        File dst = getFile(name);
        SVNTranslator.translate(this, name, SVNFileUtil.getBasePath(src), SVNFileUtil.getBasePath(dst), true);

        boolean executable = props.getPropertyValue(SVNProperty.EXECUTABLE) != null;
        boolean needsLock = props.getPropertyValue(SVNProperty.NEEDS_LOCK) != null;
        if (needsLock) {
            SVNFileUtil.setReadonly(dst, entry.getLockToken() == null);
        }
        if (executable) {
            SVNFileUtil.setExecutable(dst, true);
        }

        markResolved(name, true, false, SVNConflictChoice.MERGED);

        long tstamp;
        if (myWCAccess.getOptions().isUseCommitTimes() && !special) {
            entry.setTextTime(entry.getCommittedDate());
            tstamp = SVNDate.parseDate(entry.getCommittedDate()).getTime();
            SVNFileUtil.setLastModified(dst, tstamp);
        } else {
            tstamp = System.currentTimeMillis();
            SVNFileUtil.setLastModified(dst, tstamp);
            entry.setTextTime(SVNDate.formatDate(new Date(dst.lastModified())));
        }
        saveEntries(false);
    }

    public SVNStatusType mergeProperties(String name, SVNProperties serverBaseProps, SVNProperties propDiff,
    		String localLabel, String latestLabel, boolean baseMerge, boolean dryRun, SVNLog log) throws SVNException {
        SVNVersionedProperties working = getProperties(name);
        SVNVersionedProperties base = getBaseProperties(name);
        return mergeProperties(name, serverBaseProps, base.asMap(), working.asMap(), propDiff, localLabel,
                latestLabel, baseMerge, dryRun, log);
    }

    public SVNStatusType mergeProperties(String name, SVNProperties serverBaseProps, SVNProperties baseProps,
            SVNProperties workingProps, SVNProperties propDiff, String localLabel, String latestLabel,
            boolean baseMerge, boolean dryRun, SVNLog log) throws SVNException {
        localLabel = localLabel == null ? "(modified)" : localLabel;
        latestLabel = latestLabel == null ? "(latest)" : latestLabel;

        byte[] conflictStart = ("<<<<<<< " + localLabel).getBytes();
        byte[] conflictEnd = (">>>>>>> " + latestLabel).getBytes();
        byte[] separator = ("=======").getBytes();

        ISVNMergerFactory factory = myWCAccess.getOptions().getMergerFactory();
        ISVNMerger merger = factory.createMerger(conflictStart, separator, conflictEnd);
        propDiff = propDiff == null ? new SVNProperties() : propDiff;

        SVNMergeResult result = merger.mergeProperties(name, workingProps, baseProps, serverBaseProps,
                propDiff, this, log, baseMerge, dryRun);

        return result.getMergeStatus();
    }

    public SVNStatusType mergeText(String localPath, File base, File latest, File copyFromText, String localLabel,
            String baseLabel, String latestLabel, SVNProperties propChanges, boolean dryRun, SVNDiffOptions options,
            SVNLog log) throws SVNException {
        SVNEntry entry = getEntry(localPath, false);
        if (entry == null && copyFromText == null) {
            return SVNStatusType.MISSING;
        }

        boolean saveLog = log == null;
        log = log == null ? getLog() : log;

        SVNVersionedProperties props = getProperties(localPath);
        String mimeType = null;
        if (propChanges != null && propChanges.containsName(SVNProperty.MIME_TYPE)) {
            mimeType = propChanges.getStringValue(SVNProperty.MIME_TYPE);
        } else if (copyFromText == null) {
            mimeType = props.getStringPropertyValue(SVNProperty.MIME_TYPE);
        }

        localLabel = localLabel == null ? ".working" : localLabel;
        baseLabel = baseLabel == null ? ".old" : baseLabel;
        latestLabel = latestLabel == null ? ".new" : latestLabel;

        byte[] conflictStart = ("<<<<<<< " + localLabel).getBytes();
        byte[] conflictEnd = (">>>>>>> " + latestLabel).getBytes();
        byte[] separator = ("=======").getBytes();
        ISVNMergerFactory factory = myWCAccess.getOptions().getMergerFactory();
        ISVNMerger merger = factory.createMerger(conflictStart, separator, conflictEnd);

        String workingText = localPath;
        if (copyFromText != null) {
            String copyFromTextPath = copyFromText.getAbsolutePath().replace(File.separatorChar, '/');
            String thisPath = getRoot().getAbsolutePath().replace(File.separatorChar, '/');
            workingText = copyFromTextPath.substring(thisPath.length());
            if (workingText.startsWith("/")) {
                workingText = workingText.substring(1);
            }
        }

        File tmpTarget = SVNTranslator.detranslateWorkingCopy(this, workingText, propChanges, false);
        base = SVNTranslator.maybeUpdateTargetEOLs(this, base, propChanges);
        File resultFile = SVNAdminUtil.createTmpFile(this);

        SVNMergeFileSet mergeFileSet = new SVNMergeFileSet(this, log, base, tmpTarget, localPath, latest,
                resultFile, copyFromText, mimeType);

        mergeFileSet.setMergeLabels(baseLabel, localLabel, latestLabel);

	    SVNMergeResult mergeResult;
	    try {
	        mergeResult = merger.mergeText(mergeFileSet, dryRun, options);
	    }
	    finally {
		    if (dryRun) {
		        SVNFileUtil.deleteFile(resultFile);
		    }
	    }
        mergeFileSet.dispose();

        if (saveLog) {
            log.save();
        }

        return mergeResult.getMergeStatus();
    }

    public InputStream getBaseFileForReading(String name, boolean tmp) throws SVNException {
        String path = tmp ? "tmp/" : "";
        path += "text-base/" + name + ".svn-base";
        File baseFile = getAdminFile(path);
        return SVNFileUtil.openFileForReading(baseFile, SVNLogType.WC);
    }

    public OutputStream getBaseFileForWriting(String name) throws SVNException {
        final String fileName = name;
        final File tmpFile = getBaseFile(name, true);
        try {
            final OutputStream os = SVNFileUtil.openFileForWriting(tmpFile);
            return new OutputStream() {
                private String myName = fileName;
                private File myTmpFile = tmpFile;

                public void write(int b) throws IOException {
                    os.write(b);
                }

                public void write(byte[] b) throws IOException {
                    os.write(b);
                }

                public void write(byte[] b, int off, int len) throws IOException {
                    os.write(b, off, len);
                }

                public void close() throws IOException {
                    os.close();
                    File baseFile = getBaseFile(myName, false);
                    try {
                        SVNFileUtil.rename(myTmpFile, baseFile);
                    } catch (SVNException e) {
                        throw new IOException(e.getMessage());
                    }
                    SVNFileUtil.setReadonly(baseFile, true);
                }
            };
        } catch (SVNException svne) {
            SVNErrorMessage err = svne.getErrorMessage().wrap("Your .svn/tmp directory may be missing or corrupt; run 'svn cleanup' and try again");
            SVNErrorManager.error(err, SVNLogType.WC);
        }
        return null;
    }

    public String getPropertyTime(String name) {
        String path = getThisDirName().equals(name) ? "dir-props" : "props/" + name + ".svn-work";
        File file = getAdminFile(path);
        return SVNDate.formatDate(new Date(SVNFileUtil.getFileLastModified(file)));
    }

    public SVNLog getLog() {
        int index = 0;
        File logFile = null;
        File tmpFile = null;
        while (true) {
            logFile = getAdminFile("log" + (index == 0 ? "" : "." + index));
            if (logFile.exists()) {
                index++;
                continue;
            }
            tmpFile = getAdminFile("tmp/log" + (index == 0 ? "" : "." + index));
            return new SVNLogImpl(logFile, tmpFile, this);
        }
    }

    public void runLogs() throws SVNException {
        runLogs(false);
    }

    public void runLogs(boolean rerun) throws SVNException {
        SVNLogRunner runner = new SVNLogRunner(rerun);
        int index = 0;
        SVNLog log = null;
        runner.logStarted(this);
        try {
            File logFile = null;
            while (true) {
                if (getWCAccess() != null) {
                    getWCAccess().checkCancelled();
                }
                logFile = getAdminFile("log" + (index == 0 ? "" : "." + index));
                log = new SVNLogImpl(logFile, null, this);
                if (log.exists()) {
                    log.run(runner);
                    markLogProcessed(logFile);
                    index++;
                    continue;
                }
                break;
            }
        } catch (Throwable e) {
            runner.logFailed(this);
            if (e instanceof SVNException) {
                throw (SVNException) e;
            } else if (e instanceof Error) {
                throw (Error) e;
            }
            throw new SVNException(SVNErrorMessage.create(SVNErrorCode.UNKNOWN), e);
        }
        runner.logCompleted(this);
        // delete all logs, there shoudn't be left unprocessed.
        File[] logsFiles = getAdminDirectory().listFiles();
        if (logsFiles != null) {
            for (int i = 0; i < logsFiles.length; i++) {
                if (logsFiles[i].getName().startsWith("log") && logsFiles[i].isFile()) {
                    SVNFileUtil.deleteFile(logsFiles[i]);
                }
            }
        }
    }

    public void removeFromRevisionControl(String name, boolean deleteWorkingFiles, boolean reportInstantError) throws SVNException {
        getWCAccess().checkCancelled();
        boolean isFile = !getThisDirName().equals(name);
        boolean leftSomething = false;
        SVNEntry entry = getVersionedEntry(name, true);
        if (isFile) {
            File path = getFile(name);
            boolean wcSpecial = getProperties(name).getPropertyValue(SVNProperty.SPECIAL) != null;
            boolean localSpecial = !SVNFileUtil.symlinksSupported() ? false : SVNFileType.getType(path) == SVNFileType.SYMLINK;
            boolean textModified = false;
            if (wcSpecial || !localSpecial) {
                textModified = hasTextModifications(name, false);
                if (reportInstantError && textModified) {
                    SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_LEFT_LOCAL_MOD, "File ''{0}'' has local modifications", path);
                    SVNErrorManager.error(err, SVNLogType.WC);
                }
            }
            SVNPropertiesManager.deleteWCProperties(this, name, false);
            deleteEntry(name);
            saveEntries(false);

            SVNFileUtil.deleteFile(getFile(SVNAdminUtil.getTextBasePath(name, false)));
            SVNFileUtil.deleteFile(getFile(SVNAdminUtil.getPropBasePath(name, entry.getKind(), false)));
            SVNFileUtil.deleteFile(getFile(SVNAdminUtil.getPropPath(name, entry.getKind(), false)));
            if (deleteWorkingFiles) {
                if (textModified || (!wcSpecial && localSpecial)) {
                    SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_LEFT_LOCAL_MOD);
                    SVNErrorManager.error(err, SVNLogType.WC);
                } else if (myCommitParameters == null || myCommitParameters.onFileDeletion(path)) {
                    SVNFileUtil.deleteFile(path);
                }
            }
        } else {
            SVNEntry dirEntry = getEntry(getThisDirName(), false);
            dirEntry.setIncomplete(true);
            saveEntries(false);
            SVNPropertiesManager.deleteWCProperties(this, getThisDirName(), false);
            for(Iterator entries = entries(false); entries.hasNext();) {
                SVNEntry nextEntry = (SVNEntry) entries.next();
                String entryName = getThisDirName().equals(nextEntry.getName()) ? null : nextEntry.getName();
                if (nextEntry.isFile()) {
                    try {
                        removeFromRevisionControl(entryName, deleteWorkingFiles, reportInstantError);
                    } catch (SVNException e) {
                        if (e.getErrorMessage().getErrorCode() == SVNErrorCode.WC_LEFT_LOCAL_MOD) {
                            if (reportInstantError) {
                                throw e;
                            }
                            leftSomething = true;
                        } else {
                            throw e;
                        }
                    }
                } else if (entryName != null && nextEntry.isDirectory()) {
                    File entryPath = getFile(entryName);
                    if (getWCAccess().isMissing(entryPath) || nextEntry.getDepth() == SVNDepth.EXCLUDE) {
                        deleteEntry(entryName);
                    } else {
                        try {
                            SVNAdminArea entryArea = getWCAccess().retrieve(entryPath);
                            entryArea.removeFromRevisionControl(getThisDirName(), deleteWorkingFiles, reportInstantError);
                        } catch (SVNException e) {
                            if (e.getErrorMessage().getErrorCode() == SVNErrorCode.WC_LEFT_LOCAL_MOD) {
                                if (reportInstantError) {
                                    throw e;
                                }
                                leftSomething = true;
                            } else {
                                throw e;
                            }
                        }
                    }
                }
            }
            if (!getWCAccess().isWCRoot(getRoot())) {
                SVNEntry dirEntryInParent = getWCAccess().retrieve(getRoot().getParentFile()).getEntry(getRoot().getName(), false);
                if (dirEntryInParent.getDepth() != SVNDepth.EXCLUDE) {
                    getWCAccess().retrieve(getRoot().getParentFile()).deleteEntry(getRoot().getName());
                    getWCAccess().retrieve(getRoot().getParentFile()).saveEntries(false);
                }
            }
            destroyAdminArea();
            if (deleteWorkingFiles && !leftSomething) {
                if ((myCommitParameters == null || myCommitParameters.onDirectoryDeletion(getRoot()))
                        && !getRoot().delete()) {
                    // shouldn't throw exception when directory was intentionally left non-empty.
                    leftSomething = true;
                }
            }

        }
        if (leftSomething && myCommitParameters == null) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_LEFT_LOCAL_MOD);
            SVNErrorManager.error(err, SVNLogType.WC);
        }
    }

    public void extendLockToTree() throws SVNException {
        final boolean writeLock = isLocked();
        ISVNEntryHandler entryHandler = new ISVNEntryHandler() {
            public void handleEntry(File path, SVNEntry entry) throws SVNException {
                if (entry.isDirectory() && !entry.getName().equals(getThisDirName())) {
                    try {
                        SVNAdminArea area = getWCAccess().probeTry(path, isLocked(), SVNWCAccess.INFINITE_DEPTH);
                        if (writeLock && area != null && !area.isLocked()) {
                            area.lock(false);
                        }
                    } catch (SVNException svne) {
                        if (svne.getErrorMessage().getErrorCode() != SVNErrorCode.WC_LOCKED) {
                            throw svne;
                        }
                    }
                }
            }

            public void handleError(File path, SVNErrorMessage error) throws SVNException {
                SVNErrorManager.error(error, SVNLogType.WC);
            }
        };

        getWCAccess().walkEntries(getRoot(), entryHandler, false, SVNDepth.INFINITY);
    }

    public void foldScheduling(String name, Map attributes, boolean force) throws SVNException {
        if (!attributes.containsKey(SVNProperty.SCHEDULE) || force) {
            return;
        }
        String schedule = (String) attributes.get(SVNProperty.SCHEDULE);
        schedule = "".equals(schedule) ? null : schedule;

        SVNEntry entry = getEntry(name, true);
        if (entry == null) {
            if (SVNProperty.SCHEDULE_ADD.equals(schedule)) {
                return;
            }
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_SCHEDULE_CONFLICT, "''{0}'' is not under version control", name);
            SVNErrorManager.error(err, SVNLogType.WC);
        }

        SVNEntry thisDirEntry = getEntry(getThisDirName(), true);
        if (!getThisDirName().equals(entry.getName()) && thisDirEntry.isScheduledForDeletion()) {
            if (SVNProperty.SCHEDULE_ADD.equals(schedule)) {
                SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_SCHEDULE_CONFLICT, "Can''t add ''{0}'' to deleted directory; try undeleting its parent directory first", name);
                SVNErrorManager.error(err, SVNLogType.WC);
            } else if (SVNProperty.SCHEDULE_REPLACE.equals(schedule)) {
                SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_SCHEDULE_CONFLICT, "Can''t replace ''{0}'' in deleted directory; try undeleting its parent directory first", name);
                SVNErrorManager.error(err, SVNLogType.WC);
            }
        }

        if (entry.isAbsent() && SVNProperty.SCHEDULE_ADD.equals(schedule)) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_SCHEDULE_CONFLICT, "''{0}'' is marked as absent, so it cannot be scheduled for addition", name);
            SVNErrorManager.error(err, SVNLogType.WC);
        }

        if (SVNProperty.SCHEDULE_ADD.equals(entry.getSchedule())) {
            if (SVNProperty.SCHEDULE_DELETE.equals(schedule)) {
                if (!entry.isDeleted()) {
                    deleteEntry(name);
                } else {
                    attributes.put(SVNProperty.SCHEDULE, null);
                }
            } else {
                attributes.remove(SVNProperty.SCHEDULE);
            }
        } else if (SVNProperty.SCHEDULE_DELETE.equals(entry.getSchedule())) {
            if (SVNProperty.SCHEDULE_DELETE.equals(schedule)) {
                attributes.remove(SVNProperty.SCHEDULE);
            } else if (SVNProperty.SCHEDULE_ADD.equals(schedule)) {
                attributes.put(SVNProperty.SCHEDULE, SVNProperty.SCHEDULE_REPLACE);
            }
        } else if (SVNProperty.SCHEDULE_REPLACE.equals(entry.getSchedule())) {
            if (SVNProperty.SCHEDULE_DELETE.equals(schedule)) {
                attributes.put(SVNProperty.SCHEDULE, SVNProperty.SCHEDULE_DELETE);
            } else if (SVNProperty.SCHEDULE_ADD.equals(schedule) || SVNProperty.SCHEDULE_REPLACE.equals(schedule)) {
                attributes.remove(SVNProperty.SCHEDULE);
            }
        } else {
            if (SVNProperty.SCHEDULE_ADD.equals(schedule) && !entry.isDeleted()) {
                SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_SCHEDULE_CONFLICT, "Entry ''{0}'' is already under version control", name);
                SVNErrorManager.error(err, SVNLogType.WC);
            } else if (schedule == null) {
                attributes.remove(SVNProperty.SCHEDULE);
            }
        }
    }

    public SVNEntry modifyEntry(String name, Map attributes, boolean save, boolean force) throws SVNException {
        if (name == null) {
            name = getThisDirName();
        }

        boolean deleted = false;
        if (attributes.containsKey(SVNProperty.SCHEDULE)) {
            SVNEntry entryBefore = getEntry(name, true);
            foldScheduling(name, attributes, force);
            SVNEntry entryAfter = getEntry(name, true);
            if (entryBefore != null && entryAfter == null) {
                deleted = true;
            }
        }

        SVNEntry entry = null;
        if (!deleted) {
            entry = getEntry(name, true);
            if (entry == null) {
                entry = addEntry(name);
            }
            for (Iterator atts = attributes.keySet().iterator(); atts.hasNext();) {
                String attName = (String) atts.next();
                if (!isEntryPropertyApplicable(attName)) {
                    atts.remove();
                    continue;
                }
            }
            entry.applyChanges(attributes);

            if (!entry.isDirectory()) {
                SVNEntry rootEntry = getEntry(getThisDirName(), true);
                if (rootEntry != null) {
                    if (!SVNRevision.isValidRevisionNumber(entry.getRevision())) {
                        entry.setRevision(rootEntry.getRevision());
                    }
                    if (entry.getURL() == null) {
                        entry.setParentURL(rootEntry.getURL());
                    }
                    if (entry.getRepositoryRoot() == null) {
                        entry.setRepositoryRoot(rootEntry.getRepositoryRoot());
                    }
                    if (entry.getUUID() == null && !entry.isScheduledForAddition() && !entry.isScheduledForReplacement()) {
                        entry.setUUID(rootEntry.getUUID());
                    }
                    if (isEntryPropertyApplicable(SVNProperty.CACHABLE_PROPS)) {
                        if (entry.getCachableProperties() == null) {
                            entry.setCachableProperties(rootEntry.getCachableProperties());
                        }
                    }
                }
            }

            if (attributes.containsKey(SVNProperty.SCHEDULE)) {
                if (entry.isScheduledForDeletion()) {
                    entry.setCopied(false);
                    entry.setCopyFromRevision(-1);
                    entry.setCopyFromURL(null);
                } else {
                    entry.setKeepLocal(false);
                }
            }
        }

        if (save) {
            saveEntries(false);
        }
        return entry;
    }

    public void deleteEntry(String name) throws SVNException {
        Map entries = loadEntries();
        if (entries != null) {
            entries.remove(name);
        }
    }

    public SVNEntry getEntry(String name, boolean hidden) throws SVNException {
        Map entries = loadEntries();
        if (entries != null && entries.containsKey(name)) {
            SVNEntry entry = (SVNEntry)entries.get(name);
            if (!hidden && entry.isHidden()) {
                return null;
            }
            return entry;
        }
        return null;
    }

    public SVNEntry getVersionedEntry(String name, boolean hidden) throws SVNException {
        SVNEntry entry = getEntry(name, hidden);
        if (entry == null) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_NOT_FOUND, "''{0}'' is not under version control", getFile(name));
            SVNErrorManager.error(err, SVNLogType.WC);
        }
        return entry;
    }

    public SVNEntry addEntry(String name) throws SVNException {
        Map entries = loadEntries();
        if (entries == null) {
            myEntries = new SVNHashMap();
            entries = myEntries;
        }

        SVNEntry entry = entries.containsKey(name) ? (SVNEntry) entries.get(name) : new SVNEntry16(this, name);
        entries.put(name, entry);
        return entry;
    }

    public Iterator entries(boolean hidden) throws SVNException {
        Map entries = loadEntries();
        if (entries == null) {
            return Collections.EMPTY_LIST.iterator();
        }
        List copy = new ArrayList(entries.values());
        if (!hidden) {
            for (Iterator iterator = copy.iterator(); iterator.hasNext();) {
                SVNEntry entry = (SVNEntry) iterator.next();
                if (entry.isHidden()) {
                    iterator.remove();
                }
            }
        }
        return copy.iterator();
    }

    public Map getEntries() throws SVNException {
        return loadEntries();
    }

    public void cleanup() throws SVNException {
        getWCAccess().checkCancelled();
        for(Iterator entries = entries(false); entries.hasNext();) {
            SVNEntry entry = (SVNEntry) entries.next();
            if (entry.getKind() == SVNNodeKind.DIR && !getThisDirName().equals(entry.getName())) {
                File childDir = getFile(entry.getName());
                if(childDir.isDirectory()) {
                    try {
                        SVNAdminArea child = getWCAccess().open(childDir, true, true, 0);
                        child.cleanup();
                    } catch (SVNException e) {
                        if (e instanceof SVNCancelException) {
                            throw e;
                        }
                        if (isSafeCleanup()) {
                            continue;
                        }
                        throw e;
                    }
                }
            } else {
                hasPropModifications(entry.getName());
                if (entry.getKind() == SVNNodeKind.FILE) {
                    hasTextModifications(entry.getName(), false);
                }
            }
        }
        if (isKillMe()) {
            removeFromRevisionControl(getThisDirName(), true, false);
        } else {
            runLogs(true);
        }
        SVNFileUtil.deleteAll(getAdminFile("tmp"), false);
    }


    public boolean hasTextConflict(String name) throws SVNException {
        SVNEntry entry = getEntry(name, false);
        if (entry == null || entry.getKind() != SVNNodeKind.FILE) {
            return false;
        }
        boolean conflicted = false;
        if (entry.getConflictNew() != null) {
            conflicted = SVNFileType.getType(getFile(entry.getConflictNew())) == SVNFileType.FILE;
        }
        if (!conflicted && entry.getConflictWorking() != null) {
            conflicted = SVNFileType.getType(getFile(entry.getConflictWorking())) == SVNFileType.FILE;
        }
        if (!conflicted && entry.getConflictOld() != null) {
            conflicted = SVNFileType.getType(getFile(entry.getConflictOld())) == SVNFileType.FILE;
        }
        return conflicted;
    }

    public boolean hasPropConflict(String name) throws SVNException {
        SVNEntry entry = getEntry(name, false);
        if (entry != null && entry.getPropRejectFile() != null) {
            return SVNFileType.getType(getFile(entry.getPropRejectFile())) == SVNFileType.FILE;
        }
        return false;
    }

    public File getRoot() {
        return myDirectory;
    }

    public File getAdminTempDirectory() {
        return getAdminFile("tmp");
    }

    public File getAdminDirectory() {
        return myAdminRoot;
    }

    public File getAdminFile(String name) {
        return new File(getAdminDirectory(), name);
    }

    public File getFile(String name) {
        if (name == null) {
            return null;
        }
        return new File(getRoot(), name);
    }

    public SVNWCAccess getWCAccess() {
        return myWCAccess;
    }

    public void setWCAccess(SVNWCAccess wcAccess) {
        myWCAccess = wcAccess;
    }

    public void closeVersionedProperties() {
        myProperties = null;
        myBaseProperties = null;
    }

    public void closeWCProperties() {
        myWCProperties = null;
    }

    public void closeEntries() {
        myEntries = null;
    }

    public File getBaseFile(String name, boolean tmp) {
        String path = tmp ? "tmp/" : "";
        path += "text-base/" + name + ".svn-base";
        return getAdminFile(path);
    }

    public int getWorkingCopyFormatVersion() {
        return myWCFormatVersion;
    }

    public void setWorkingCopyFormatVersion(int wcFormatVersion) {
        myWCFormatVersion = wcFormatVersion;
    }

    public void close() {

    }

    protected abstract void writeEntries(Writer writer) throws IOException, SVNException;

    protected abstract Map fetchEntries() throws SVNException;

    protected abstract boolean readExtraOptions(BufferedReader reader, SVNEntry entry) throws SVNException, IOException;

    protected abstract int writeExtraOptions(Writer writer, String entryName, SVNEntry entry, int emptyFields) throws SVNException, IOException;

    protected SVNAdminArea(File dir){
        myDirectory = dir;
        myAdminRoot = new File(dir, SVNFileUtil.getAdminDirectoryName());
    }

    public File getBasePropertiesFile(String name, boolean tmp) {
        String path = !tmp ? "" : "tmp/";
        path += getThisDirName().equals(name) ? "dir-prop-base" : "prop-base/" + name + ".svn-base";
        File propertiesFile = getAdminFile(path);
        return propertiesFile;
    }

    public File getRevertPropertiesFile(String name, boolean tmp) {
        String path = !tmp ? "" : "tmp/";
        path += getThisDirName().equals(name) ? "dir-prop-revert" : "prop-base/" + name + ".svn-revert";
        File propertiesFile = getAdminFile(path);
        return propertiesFile;
    }

    public File getPropertiesFile(String name, boolean tmp) {
        String path = !tmp ? "" : "tmp/";
        path += getThisDirName().equals(name) ? "dir-props" : "props/" + name + ".svn-work";
        File propertiesFile = getAdminFile(path);
        return propertiesFile;
    }

    protected Map loadEntries() throws SVNException {
        if (myEntries != null) {
            return myEntries;
        }
        myEntries = fetchEntries();
        if (myEntries != null) {
            resolveDefaults(myEntries);
        }
        return myEntries;
    }

    protected Map getBasePropertiesStorage(boolean create) {
        if (myBaseProperties == null && create) {
            myBaseProperties = new SVNHashMap();
        }
        return myBaseProperties;
    }

    protected Map getRevertPropertiesStorage(boolean create) {
        if (myRevertProperties == null && create) {
            myRevertProperties = new SVNHashMap();
        }
        return myRevertProperties;
    }

    protected Map getPropertiesStorage(boolean create) {
        if (myProperties == null && create) {
            myProperties = new SVNHashMap();
        }
        return myProperties;
    }

    protected Map getWCPropertiesStorage(boolean create) {
        if (myWCProperties == null && create) {
            myWCProperties = new SVNHashMap();
        }
        return myWCProperties;
    }

    public static String asString(String[] array, String delimiter) {
        String str = null;
        if (array != null) {
            str = "";
            for (int i = 0; i < array.length; i++) {
                str += array[i];
                if (i < array.length - 1) {
                    str += delimiter;
                }
            }
        }
        return str;
    }

    public static String[] fromString(String str, String delimiter) {
        if (str == null) {
            return new String[0];
        }
        LinkedList list = new LinkedList();
        int startInd = 0;
        int ind = -1;
        while ((ind = str.indexOf(delimiter, startInd)) != -1) {
            list.add(str.substring(startInd, ind));
            startInd = ind;
            while (startInd < str.length() && str.charAt(startInd) == ' '){
                startInd++;
            }
        }
        if (startInd < str.length()) {
            list.add(str.substring(startInd));
        }
        return (String[])list.toArray(new String[list.size()]);
    }

    public void commit(String target, SVNCommitInfo info, SVNProperties wcPropChanges,
            boolean removeLock, boolean recursive, boolean removeChangelist,
            Collection explicitCommitPaths, ISVNCommitParameters params) throws SVNException {
        SVNAdminArea anchor = getWCAccess().retrieve(getWCAccess().getAnchor());
        String path = getRelativePath(anchor);
        path = getThisDirName().equals(target) ? path : SVNPathUtil.append(path, target);
        if (!explicitCommitPaths.contains(path)) {
            // if this item is explicitly copied -> skip it.
            SVNEntry entry = getEntry(target, true);
            if (entry != null && entry.getCopyFromURL() != null) {
                return;
            }
            if (entry != null && entry.isFile() && entry.isScheduledForAddition()) {
                // if this implicit item was scheduled for addition, 
                // but wasn't actually committed, then skip it.
                File committedFile = getBaseFile(target, true);
                if (!committedFile.isFile()) {
                    return;
                }
            }
        }

        SVNLog log = getLog();
        String checksum = null;
        if (!getThisDirName().equals(target)) {
            log.logRemoveRevertFile(target, this, true);
            log.logRemoveRevertFile(target, this, false);

            File baseFile = getBaseFile(target, true);
            SVNFileType baseType = SVNFileType.getType(baseFile);
            if (baseType == SVNFileType.NONE) {
                baseFile = getBaseFile(target, false);
                baseType = SVNFileType.getType(baseFile);
            }
            if (baseType == SVNFileType.FILE) {
                checksum = SVNFileUtil.computeChecksum(baseFile);
            }
            recursive = false;
        }

        SVNProperties command = new SVNProperties();
        if (info != null) {
            command.put(SVNLog.NAME_ATTR, target);
            command.put(SVNProperty.shortPropertyName(SVNProperty.COMMITTED_REVISION), Long.toString(info.getNewRevision()));
            command.put(SVNProperty.shortPropertyName(SVNProperty.COMMITTED_DATE), SVNDate.formatDate(info.getDate()));
            command.put(SVNProperty.shortPropertyName(SVNProperty.LAST_AUTHOR), info.getAuthor());
            log.addCommand(SVNLog.MODIFY_ENTRY, command, false);
            command.clear();
        }
        if (checksum != null) {
            command.put(SVNLog.NAME_ATTR, target);
            command.put(SVNProperty.shortPropertyName(SVNProperty.CHECKSUM), checksum);
            log.addCommand(SVNLog.MODIFY_ENTRY, command, false);
            command.clear();
        }
        if (removeLock) {
            command.put(SVNLog.NAME_ATTR, target);
            log.addCommand(SVNLog.DELETE_LOCK, command, false);
            command.clear();
        }
        if (removeChangelist) {
            command.put(SVNLog.NAME_ATTR, target);
            log.addCommand(SVNLog.DELETE_CHANGELIST, command, false);
            command.clear();
        }
        command.put(SVNLog.NAME_ATTR, target);
        command.put(SVNLog.REVISION_ATTR, info == null ? null : Long.toString(info.getNewRevision()));
        if (!explicitCommitPaths.contains(path)) {
            command.put("implicit", "true");
        }
        log.addCommand(SVNLog.COMMIT, command, false);
        command.clear();
        if (wcPropChanges != null && !wcPropChanges.isEmpty()) {
            for (Iterator propNames = wcPropChanges.nameSet().iterator(); propNames.hasNext();) {
                String propName = (String) propNames.next();
                SVNPropertyValue propValue = wcPropChanges.getSVNPropertyValue(propName);
                command.put(SVNLog.NAME_ATTR, target);
                command.put(SVNLog.PROPERTY_NAME_ATTR, propName);
                command.put(SVNLog.PROPERTY_VALUE_ATTR, propValue);
                log.addCommand(SVNLog.MODIFY_WC_PROPERTY, command, false);
                command.clear();
            }
        }
        log.save();
        runLogs();

        if (recursive) {
            for (Iterator ents = entries(true); ents.hasNext();) {
                SVNEntry entry = (SVNEntry) ents.next();
                if (entry.isThisDir()) {
                    continue;
                }
                if (entry.getDepth() == SVNDepth.EXCLUDE) {
                    continue;
                }
                if (entry.getKind() == SVNNodeKind.DIR) {
                    File childPath = getFile(entry.getName());
                    SVNAdminArea childDir = getWCAccess().retrieve(childPath);
                    if (childDir != null) {
                        childDir.commit(getThisDirName(), info, null, removeLock, true, removeChangelist, explicitCommitPaths, params);
                    }
                } else {
                    if (entry.isScheduledForDeletion()) {
                        SVNEntry parentEntry = getEntry(getThisDirName(), true);
                        if (parentEntry.isScheduledForReplacement()) {
                            continue;
                        }
                    }
                    commit(entry.getName(), info, null, removeLock, false, removeChangelist, explicitCommitPaths, params);
                }
            }
        }
    }

    public void walkThisDirectory(ISVNEntryHandler handler, boolean showHidden, SVNDepth depth) throws SVNException {
        File thisDir = getRoot();

        SVNEntry thisEntry = getEntry(getThisDirName(), showHidden);
        if (thisEntry == null) {
            handler.handleError(thisDir, SVNErrorMessage.create(SVNErrorCode.ENTRY_NOT_FOUND,
                    "Directory ''{0}'' has no THIS_DIR entry", thisDir));
            return;
        }

        try {
            handler.handleEntry(thisDir, thisEntry);
        } catch (SVNException svne) {
            handler.handleError(thisDir, svne.getErrorMessage());
        }

        if (depth == SVNDepth.EMPTY) {
            return;
        }

        for (Iterator entries = entries(showHidden); entries.hasNext();) {
            getWCAccess().checkCancelled();

            SVNEntry entry = (SVNEntry) entries.next();
            if (getThisDirName().equals(entry.getName())) {
                continue;
            }

            File childPath = getFile(entry.getName());
            if (entry.isFile() || depth.compareTo(SVNDepth.IMMEDIATES) >= 0) {
                try {
                    handler.handleEntry(childPath, entry);
                } catch (SVNException svne) {
                    handler.handleError(childPath, svne.getErrorMessage());
                }
            }
            if (entry.isDirectory() && !entry.isHidden() && depth.compareTo(SVNDepth.IMMEDIATES) >= 0) {
                SVNAdminArea childArea = null;
                SVNDepth depthBelowHere = depth;
                if (depth == SVNDepth.IMMEDIATES) {
                    depthBelowHere = SVNDepth.EMPTY;
                }
                try {
                    childArea = getWCAccess().retrieve(childPath);
                } catch (SVNException svne) {
                    handler.handleError(childPath, svne.getErrorMessage());
                }
                if (childArea != null) {
                    childArea.walkThisDirectory(handler, showHidden, depthBelowHere);
                }
            }
        }
    }

    public void setCommitParameters(ISVNCommitParameters commitParameters) {
        myCommitParameters = commitParameters;
    }

    protected void setLocked(boolean locked) {
        myWasLocked = locked;
    }

    private void destroyAdminArea() throws SVNException {
        if (!isLocked()) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_NOT_LOCKED, "Write-lock stolen in ''{0}''", getRoot());
            SVNErrorManager.error(err, SVNLogType.WC);
        }
        SVNFileUtil.deleteAll(getAdminDirectory(), getWCAccess());
        getWCAccess().closeAdminArea(getRoot());
    }

    private static void markLogProcessed(File logFile) throws SVNException {
        SVNFileUtil.setReadonly(logFile, false);
        OutputStream os = null;
        try {
            os = SVNFileUtil.openFileForWriting(logFile);
        } finally {
            if (os != null) {
                SVNFileUtil.closeFile(os);
            }
        }
    }

    private boolean compareAndVerify(File text, File baseFile, boolean compareTextBase, boolean checksum) throws SVNException {
        SVNVersionedProperties versionedProperties = getProperties(text.getName());
        String charsetProp = versionedProperties.getStringPropertyValue(SVNProperty.CHARSET);
        String mimeType = versionedProperties.getStringPropertyValue(SVNProperty.MIME_TYPE);
        String charset = SVNTranslator.getCharset(charsetProp, mimeType, text.getPath(), getWCAccess().getOptions());
        String eolStyle = versionedProperties.getStringPropertyValue(SVNProperty.EOL_STYLE);
        String keywords = versionedProperties.getStringPropertyValue(SVNProperty.KEYWORDS);
        boolean special = versionedProperties.getStringPropertyValue(SVNProperty.SPECIAL) != null && SVNFileUtil.symlinksSupported();

        if (special) {
            compareTextBase = true;
        }

        boolean needsTranslation = charset != null || eolStyle != null || keywords != null || special;
        SVNChecksumInputStream checksumStream = null;
        SVNEntry entry = null;

        if (checksum || needsTranslation) {
            InputStream baseStream = null;
            InputStream textStream = null;
            entry = getVersionedEntry(text.getName(), true);
            try {
                baseStream = SVNFileUtil.openFileForReading(baseFile, SVNLogType.WC);
                textStream = special ? null : SVNFileUtil.openFileForReading(text, SVNLogType.WC);
                if (checksum) {
                    if (entry.getChecksum() != null) {
                        checksumStream = new SVNChecksumInputStream(baseStream, SVNChecksumInputStream.MD5_ALGORITHM);
                        baseStream = checksumStream;
                    }
                }
                File pathToTranslate = text;
                if (compareTextBase && needsTranslation) {
                    if (!special) {
                        Map keywordsMap = SVNTranslator.computeKeywords(keywords, null, entry.getRepositoryRoot(), entry.getAuthor(), entry.getCommittedDate(), entry.getRevision() + "", getWCAccess().getOptions());
                        byte[] eols = SVNTranslator.getBaseEOL(eolStyle);
                        textStream = SVNTranslator.getTranslatingInputStream(textStream, charset, eols, true, keywordsMap, false);
                        pathToTranslate = text;
                    } else {
                        String linkPath = SVNFileUtil.getSymlinkName(text);
                        if (linkPath == null) {
                            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "Cannot detranslate symbolic link ''{0}''; file does not exist or not a symbolic link", text);
                            SVNErrorManager.error(err, SVNLogType.DEFAULT);
                        }
                        String symlinkContents = "link " + linkPath;
                        textStream = new ByteArrayInputStream(symlinkContents.getBytes());
                    }
                } else if (needsTranslation) {
                    Map keywordsMap = SVNTranslator.computeKeywords(keywords, entry.getURL(), entry.getRepositoryRoot(), entry.getAuthor(), entry.getCommittedDate(), entry.getRevision() + "", getWCAccess().getOptions());
                    byte[] eols = SVNTranslator.getEOL(eolStyle, getWCAccess().getOptions());
                    baseStream = SVNTranslator.getTranslatingInputStream(baseStream, charset, eols, false, keywordsMap, true);
                    pathToTranslate = baseFile;
                }
                byte[] buffer1 = new byte[8192];
                byte[] buffer2 = new byte[8192];
                try {
                    while(true) {
                        int r1 = SVNFileUtil.readIntoBuffer(baseStream, buffer1, 0, buffer1.length);
                        int r2 = SVNFileUtil.readIntoBuffer(textStream, buffer2, 0, buffer2.length);
                        r1 = r1 == -1 ? 0 : r1;
                        r2 = r2 == -1 ? 0 : r2;
                        if (r1 != r2) {
                            return true;
                        } else if (r1 == 0) {
                            return false;
                        }
                        for(int i = 0; i < r1; i++) {
                            if (buffer1[i] != buffer2[i]) {
                                return true;
                            }
                        }
                    }
                } catch (IOException e) {
                    SVNTranslator.translationError(pathToTranslate, e);
                }
            } finally {
                SVNFileUtil.closeFile(baseStream);
                SVNFileUtil.closeFile(textStream);
            }
        } else {
            return !SVNFileUtil.compareFiles(text, baseFile, null);
        }
        if (entry != null && checksumStream != null)  {
            if (!entry.getChecksum().equals(checksumStream.getDigest())) {
                SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.WC_CORRUPT_TEXT_BASE, "Checksum mismatch indicates corrupt text base: ''{0}''\n" +
                        "   expected: {1}\n" +
                        "     actual: {2}\n", new Object[] {baseFile, entry.getChecksum(), checksumStream.getDigest()});
                SVNErrorManager.error(err, SVNLogType.WC);
            }
        }
        return false;
    }

    private static void resolveDefaults(Map entries) throws SVNException {
        SVNEntry defaultEntry = (SVNEntry) entries.get("");
        if (defaultEntry == null) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_NOT_FOUND, "Missing default entry");
            SVNErrorManager.error(err, SVNLogType.WC);
        }
        if (defaultEntry.getRevision() < 0) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_MISSING_REVISION, "Default entry has no revision number");
            SVNErrorManager.error(err, SVNLogType.WC);
        }
        if (defaultEntry.getURL() == null) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_MISSING_URL, "Default entry is missing no URL");
            SVNErrorManager.error(err, SVNLogType.WC);
        }
        for(Iterator names = entries.keySet().iterator(); names.hasNext();) {
            String name = (String) names.next();
            SVNEntry entry = (SVNEntry) entries.get(name);
            if (entry == null || entry == defaultEntry || entry.isDirectory()) {
                continue;
            } else if (entry.isFile()) {
                if (entry.getRevision() < 0) {
                    entry.setRevision(defaultEntry.getRevision());
                }
                if (entry.getURL() == null) {
                    entry.setURL(SVNPathUtil.append(defaultEntry.getURL(), SVNEncodingUtil.uriEncode(entry.getName())));
                }
                if (entry.getUUID() == null && !(entry.isScheduledForAddition() || entry.isScheduledForReplacement())) {
                    entry.setUUID(defaultEntry.getUUID());
                }
                if (entry.getCachableProperties() == null) {
                    entry.setCachableProperties(defaultEntry.getCachableProperties());
                }
            }
        }
    }

    protected abstract SVNVersionedProperties formatBaseProperties(SVNProperties srcProperties);

    protected abstract SVNVersionedProperties formatProperties(SVNEntry entry, SVNProperties srcProperties);

    protected void createFormatFile(File formatFile, boolean createMyself) throws SVNException {
        OutputStream os = null;
        try {
            formatFile = createMyself ? getAdminFile("format") : formatFile;
            os = SVNFileUtil.openFileForWriting(formatFile);
            os.write(String.valueOf(getFormatVersion()).getBytes("UTF-8"));
            os.write('\n');
        } catch (IOException e) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e.getLocalizedMessage());
            SVNErrorManager.error(err, e, SVNLogType.WC);
        } finally {
            SVNFileUtil.closeFile(os);
        }
    }

    public SVNAdminArea formatWC(SVNAdminArea adminArea) throws SVNException {
        File logFile = adminArea.getAdminFile("log");
        SVNFileType type = SVNFileType.getType(logFile);
        if (type == SVNFileType.FILE) {
            SVNDebugLog.getDefaultLog().logFine(SVNLogType.WC,
                    "Changing working copy format failed: found a log file at '" + logFile + "'");
            return adminArea;
        }

        SVNLog log = getLog();
        SVNProperties command = new SVNProperties();
        command.put(SVNLog.FORMAT_ATTR, String.valueOf(getFormatVersion()));
        log.addCommand(SVNLog.UPGRADE_FORMAT, command, false);
        command.clear();

        setWCAccess(adminArea.getWCAccess());
        myEntries = new SVNHashMap();
        Map basePropsCache = getBasePropertiesStorage(true);
        Map propsCache = getPropertiesStorage(true);

        for (Iterator entries = adminArea.entries(true); entries.hasNext();) {
            SVNEntry entry = (SVNEntry) entries.next();
            SVNEntry newEntry = new SVNEntry16(createEntryHashMap(entry.asMap()), this, entry.getName());
            myEntries.put(entry.getName(), newEntry);

            if (entry.getKind() != SVNNodeKind.FILE && !adminArea.getThisDirName().equals(entry.getName())) {
                continue;
            }

            SVNVersionedProperties srcBaseProps = adminArea.getBaseProperties(entry.getName());
            SVNVersionedProperties dstBaseProps = formatBaseProperties(srcBaseProps.asMap());
            basePropsCache.put(entry.getName(), dstBaseProps);
            dstBaseProps.setModified(true);

            SVNVersionedProperties srcProps = adminArea.getProperties(entry.getName());
            SVNVersionedProperties dstProps = formatProperties(entry, srcProps.asMap());
            propsCache.put(entry.getName(), dstProps);
            dstProps.setModified(true);

            handleCharsetProperty(adminArea, log, newEntry, dstBaseProps);
            handlePropTime(log, newEntry);

            SVNVersionedProperties wcProps = adminArea.getWCProperties(entry.getName());
            if (wcProps != null) {
                log.logChangedWCProperties(entry.getName(), wcProps.asMap());
            }
        }
        saveVersionedProperties(log, true);
        log.save();

        if (getFormatVersion() != SVNXMLAdminAreaFactory.WC_FORMAT) {
            SVNFileUtil.deleteFile(getAdminFile("README.txt"));
            SVNFileUtil.deleteFile(getAdminFile("empty-file"));
            SVNFileUtil.deleteAll(getAdminFile("wcprops"), true);
            SVNFileUtil.deleteAll(getAdminFile("tmp/wcprops"), true);
            SVNFileUtil.deleteAll(getAdminFile("dir-wcprops"), true);
            SVNFileUtil.deleteAll(getAdminFile("all-wcprops"), true);
        } else {
            getAdminFile("wcprops").mkdir();
            getAdminFile("tmp/wcprops").mkdir();
            SVNFileUtil.createEmptyFile(getAdminFile("empty-file"));
            SVNAdminUtil.createReadmeFile(getAdminDirectory());
        }

        runLogs();
        return this;
    }

    private void handleCharsetProperty(SVNAdminArea adminArea, SVNLog log, SVNEntry entry, SVNVersionedProperties baseProps) throws SVNException {
        SVNProperties command = new SVNProperties();
        SVNPropertyValue charsetProp = baseProps.getPropertyValue(SVNProperty.CHARSET);
        String mimeType = baseProps.getStringPropertyValue(SVNProperty.MIME_TYPE);
        String currentCharset = charsetProp == null ? null : charsetProp.getString();
        currentCharset = SVNTranslator.getCharset(currentCharset, mimeType, getAdminFile(entry.getName()).toString(), getWCAccess().getOptions());
        if (currentCharset != null && !SVNProperty.isUTF8(currentCharset)) {
            File detranslatedFile = SVNAdminUtil.createTmpFile(this, "detranslated", ".tmp", true);
            String detranslatedPath = SVNPathUtil.getRelativePath(getRoot().getAbsolutePath(), detranslatedFile.getAbsolutePath());
            File tmpCharsetPropFile = SVNAdminUtil.createTmpFile(this, "props", ".tmp", true);
            String tmpCharsetPropPath = SVNPathUtil.getRelativePath(getRoot().getAbsolutePath(), tmpCharsetPropFile.getAbsolutePath());

            if (getFormatVersion() == SVNAdminArea15Factory.WC_FORMAT) {
                baseProps.setPropertyValue(SVNProperty.CHARSET, SVNPropertyValue.create("UTF-8"));
                SVNWCProperties propFile = new SVNWCProperties(tmpCharsetPropFile, tmpCharsetPropPath);
                propFile.setProperties(baseProps.asMap());
                baseProps.setPropertyValue(SVNProperty.CHARSET, charsetProp);

                File tmpBaseFile = SVNAdminUtil.createTmpFile(this, entry.getName(), ".tmp", true);
                String tmpBasePath = SVNPathUtil.getRelativePath(getRoot().getAbsolutePath(), tmpBaseFile.getAbsolutePath());
                command.put(SVNLog.NAME_ATTR, SVNAdminUtil.getPropBasePath(entry.getName(), SVNNodeKind.FILE, false));
                command.put(SVNLog.DEST_ATTR, tmpBasePath);
                log.addCommand(SVNLog.COPY, command, false);
                command.clear();

                command.put(SVNLog.NAME_ATTR, tmpCharsetPropPath);
                command.put(SVNLog.DEST_ATTR, SVNAdminUtil.getPropBasePath(entry.getName(), SVNNodeKind.FILE, false));
                log.addCommand(SVNLog.MOVE, command, false);
                command.clear();

                command.put(SVNLog.NAME_ATTR, entry.getName());
                command.put(SVNLog.DEST_ATTR, detranslatedPath);
                log.addCommand(SVNLog.COPY_AND_DETRANSLATE, command, false);
                command.clear();

                command.put(SVNLog.DEST_ATTR, SVNAdminUtil.getPropBasePath(entry.getName(), SVNNodeKind.FILE, false));
                command.put(SVNLog.NAME_ATTR, tmpBasePath);
                log.addCommand(SVNLog.MOVE, command, false);
                command.clear();

                command.put(SVNLog.NAME_ATTR, detranslatedPath);
                command.put(SVNLog.DEST_ATTR, entry.getName());
                log.addCommand(SVNLog.COPY_AND_TRANSLATE, command, false);
                command.clear();

                command.put(SVNLog.NAME_ATTR, detranslatedPath);
                log.addCommand(SVNLog.DELETE, command, false);
                command.clear();
            } else if (adminArea.getFormatVersion() == SVNAdminArea15Factory.WC_FORMAT) {
                command.put(SVNLog.NAME_ATTR, entry.getName());
                command.put(SVNLog.DEST_ATTR, detranslatedPath);
                log.addCommand(SVNLog.COPY_AND_DETRANSLATE, command, false);
                command.clear();

                baseProps.setPropertyValue(SVNProperty.CHARSET, SVNPropertyValue.create("UTF-8"));
                SVNWCProperties propFile = new SVNWCProperties(tmpCharsetPropFile, tmpCharsetPropPath);
                propFile.setProperties(baseProps.asMap());
                baseProps.setPropertyValue(SVNProperty.CHARSET, charsetProp);

                command.put(SVNLog.NAME_ATTR, tmpCharsetPropPath);
                command.put(SVNLog.DEST_ATTR, SVNAdminUtil.getPropBasePath(entry.getName(), SVNNodeKind.FILE, false));
                log.addCommand(SVNLog.MOVE, command, false);
                command.clear();

                command.put(SVNLog.NAME_ATTR, detranslatedPath);
                command.put(SVNLog.DEST_ATTR, entry.getName());
                log.addCommand(SVNLog.COPY_AND_TRANSLATE, command, false);
                command.clear();

                command.put(SVNLog.NAME_ATTR, detranslatedPath);
                log.addCommand(SVNLog.DELETE, command, false);
                command.clear();
            }
        }
    }

    private void handlePropTime(SVNLog log, SVNEntry entry) throws SVNException {
        if (getFormatVersion() == SVNXMLAdminAreaFactory.WC_FORMAT) {
            return;
        }
        SVNProperties command = new SVNProperties();
        command.put(SVNLog.NAME_ATTR, entry.getName());
        command.put(SVNProperty.shortPropertyName(SVNProperty.PROP_TIME), SVNDate.formatDate(new Date(0), true));
        log.addCommand(SVNLog.MODIFY_ENTRY, command, false);
    }

    public void postUpgradeFormat(int format) throws SVNException {
        if (format == getFormatVersion()) {
            createFormatFile(null, true);
            return;
        }
        SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN,
                "Unexpected format number:\n" +
                "   expected: {0}\n" +
                "     actual: {1}",
                new Object[] { new Integer(getFormatVersion()), new Integer(format) });
        SVNErrorManager.error(err, SVNLogType.WC);
    }
    
    protected SVNHashMap createEntryHashMap(Map map) {
        if (getObjectsPool() != null) {
            return new SVNEntryHashMap(map, getObjectsPool());
        }
        return new SVNHashMap(map);
    }
    
    public SVNObjectsPool getObjectsPool() {
        return myWCAccess != null ? myWCAccess.getObjectsPool() : null;
    }
}
