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

import java.io.File;
import java.io.OutputStream;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;

import org.tmatesoft.svn.core.SVNCommitInfo;
import org.tmatesoft.svn.core.SVNDepth;
import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNLock;
import org.tmatesoft.svn.core.SVNNodeKind;
import org.tmatesoft.svn.core.SVNProperty;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.SVNPropertyValue;
import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
import org.tmatesoft.svn.core.internal.util.SVNDate;
import org.tmatesoft.svn.core.internal.util.SVNURLUtil;
import org.tmatesoft.svn.core.internal.wc.admin.SVNAdminArea;
import org.tmatesoft.svn.core.internal.wc.admin.SVNAdminAreaInfo;
import org.tmatesoft.svn.core.internal.wc.admin.SVNEntry;
import org.tmatesoft.svn.core.internal.wc.admin.SVNWCAccess;
import org.tmatesoft.svn.core.io.ISVNEditor;
import org.tmatesoft.svn.core.io.diff.SVNDiffWindow;
import org.tmatesoft.svn.core.wc.ISVNOptions;
import org.tmatesoft.svn.core.wc.ISVNStatusHandler;
import org.tmatesoft.svn.core.wc.SVNRevision;
import org.tmatesoft.svn.core.wc.SVNStatus;
import org.tmatesoft.svn.core.wc.SVNStatusType;
import org.tmatesoft.svn.core.wc.SVNWCUtil;



/**
 * @version 1.3
 * @author  TMate Software Ltd.
 */
public class SVNRemoteStatusEditor extends SVNStatusEditor implements ISVNEditor, ISVNStatusHandler {
    
    private boolean myIsRootOpen;
    private SVNStatus myAnchorStatus;
    
    private DirectoryInfo myDirectoryInfo;
    private FileInfo myFileInfo;

    public SVNRemoteStatusEditor(ISVNOptions options, SVNWCAccess wcAccess, SVNAdminAreaInfo info, boolean noIgnore, boolean reportAll, SVNDepth depth, ISVNStatusHandler handler) throws SVNException {
        super(options, wcAccess, info, noIgnore, reportAll, depth, handler);
        myAnchorStatus = createStatus(info.getAnchor().getRoot());
    }

    @Override
    public void setRepositoryInfo(SVNURL root, Map repositoryLocks) {
        super.setRepositoryInfo(root, repositoryLocks);
        if (myAnchorStatus.getRepositoryRootURL() == null) {
            myAnchorStatus.setRepositoryRootURL(myRepositoryRoot);
        }
        if (myAnchorStatus.getURL() != null && myAnchorStatus.getRepositoryRootURL() != null) {
            myAnchorStatus.setRepositoryRelativePath(SVNURLUtil.getRelativeURL(myAnchorStatus.getRepositoryRootURL(), 
                    myAnchorStatus.getURL(), false));
        }

    }

    public void openRoot(long revision) throws SVNException {
        myIsRootOpen = true;
        myDirectoryInfo = new DirectoryInfo(null, null);
    }

    public void deleteEntry(String path, long revision) throws SVNException {
        File file = getAnchor().getFile(path);
        SVNEntry entry = getWCAccess().getVersionedEntry(file, false);

        File dirPath;
        String name;
        if (entry.getKind() == SVNNodeKind.DIR) {
            dirPath = file;
            name = "";
        } else {
            dirPath = file.getParentFile();
            name = file.getName();
        }
        SVNAdminArea dir = null;
        try {
            dir = getWCAccess().retrieve(dirPath);
        } catch (SVNException e) {
            SVNFileType type = SVNFileType.getType(file);
            if (type == SVNFileType.NONE && e.getErrorMessage().getErrorCode() == SVNErrorCode.WC_NOT_LOCKED) {
                return;
            }
            throw e;
        }
        
        if (dir.getEntry(name, false) != null) {
            tweakStatusHash(myDirectoryInfo, myDirectoryInfo, file, SVNStatusType.STATUS_DELETED, SVNStatusType.STATUS_NONE, null, SVNRevision.create(revision));
            // set entry node kind
            SVNStatus status = (SVNStatus) myDirectoryInfo.myChildrenStatuses.get(file);
            if (status != null) {
                status.setRemoteStatus(null, null, null, dir.getEntry(name, false).getKind());
            }
        }
        if (myDirectoryInfo.myParent != null && !hasTarget()) {
            tweakStatusHash(myDirectoryInfo.myParent, myDirectoryInfo, myDirectoryInfo.myPath, SVNStatusType.STATUS_MODIFIED, SVNStatusType.STATUS_NONE, null, null);
        } else if (!hasTarget() && myDirectoryInfo.myParent == null) {
            myDirectoryInfo.myIsContentsChanged = true;
        }
    }

    public void addDir(String path, String copyFromPath, long copyFromRevision) throws SVNException {
        myDirectoryInfo = new DirectoryInfo(path, myDirectoryInfo);
        myDirectoryInfo.myIsAdded = true;
        myDirectoryInfo.myParent.myIsContentsChanged = true;
    }

    public void openDir(String path, long revision) throws SVNException {
        myDirectoryInfo = new DirectoryInfo(path, myDirectoryInfo);
    }

    public void changeDirProperty(String name, SVNPropertyValue value) throws SVNException {
        if (!name.startsWith(SVNProperty.SVN_ENTRY_PREFIX)
                && !name.startsWith(SVNProperty.SVN_WC_PREFIX)) {
            myDirectoryInfo.myIsPropertiesChanged = true;
        }
        if (SVNProperty.COMMITTED_REVISION.equals(name) && value != null) {
            try {
                long number = Long.parseLong(value.getString());
                myDirectoryInfo.myRemoteRevision = SVNRevision.create(number);
            } catch (NumberFormatException nfe) {
                myDirectoryInfo.myRemoteRevision = SVNRevision.UNDEFINED;
            }
        } else if (SVNProperty.COMMITTED_DATE.equals(name) && value != null) {
            myDirectoryInfo.myRemoteDate = SVNDate.parseDate(value.getString());
        } else if (SVNProperty.LAST_AUTHOR.equals(name) && value != null) {
            myDirectoryInfo.myRemoteAuthor = value.getString();
        }
    }

    public void closeDir() throws SVNException {
        DirectoryInfo parent = myDirectoryInfo.myParent;
        if (myDirectoryInfo.myIsAdded || myDirectoryInfo.myIsPropertiesChanged || myDirectoryInfo.myIsContentsChanged 
                || (myDirectoryInfo.myRemoteRevision != null && myDirectoryInfo.myRemoteRevision != SVNRevision.UNDEFINED)) {
            SVNStatusType contentsStatus;
            SVNStatusType propertiesStatus;
            if (myDirectoryInfo.myIsAdded) {
                contentsStatus = SVNStatusType.STATUS_ADDED;
                propertiesStatus = myDirectoryInfo.myIsPropertiesChanged ? SVNStatusType.STATUS_ADDED : SVNStatusType.STATUS_NONE;
            } else {
                contentsStatus = myDirectoryInfo.myIsContentsChanged ? SVNStatusType.STATUS_MODIFIED : SVNStatusType.STATUS_NONE;
                propertiesStatus = myDirectoryInfo.myIsPropertiesChanged ? SVNStatusType.STATUS_MODIFIED : SVNStatusType.STATUS_NONE;
            }
            if (parent != null) {
                tweakStatusHash(parent, myDirectoryInfo, myDirectoryInfo.myPath, contentsStatus, propertiesStatus, null, null);
            }
        }
        if (parent != null && myDirectoryInfo.myDepth != SVNDepth.EXCLUDE) {
            boolean wasDeleted = false;
            SVNStatus dirStatus = (SVNStatus) parent.myChildrenStatuses.get(myDirectoryInfo.myPath);
            if (dirStatus != null && 
                    (dirStatus.getRemoteContentsStatus() == SVNStatusType.STATUS_DELETED || 
                    dirStatus.getRemoteContentsStatus() == SVNStatusType.STATUS_REPLACED)) {
                wasDeleted = true;
            }
            handleStatusHash(dirStatus != null ? dirStatus.getEntry() : null, myDirectoryInfo.myChildrenStatuses, wasDeleted, myDirectoryInfo.myDepth);
            if (isSendableStatus(dirStatus)) {
                getDefaultHandler().handleStatus(dirStatus);
            }
            parent.myChildrenStatuses.remove(myDirectoryInfo.myPath);
        } else if (parent == null) {
            if (hasTarget()) {
                File targetPath = getAnchor().getFile(getAdminAreaInfo().getTargetName());
                SVNStatus tgtStatus = (SVNStatus) myDirectoryInfo.myChildrenStatuses.get(targetPath);
                if (tgtStatus != null) {
                    if (tgtStatus.getKind() == SVNNodeKind.DIR) {
                        SVNAdminArea dir = getWCAccess().retrieve(targetPath);
                        getDirStatus(null, dir, null, getDepth(), isReportAll(), isNoIgnore(), null, true, getDefaultHandler());
                    } 
                    if (isSendableStatus(tgtStatus)) {
                        getDefaultHandler().handleStatus(tgtStatus);
                    }
                }
            } else {
                handleStatusHash(myAnchorStatus.getEntry(), myDirectoryInfo.myChildrenStatuses, false, getDepth());
                if (myDirectoryInfo != null && myDirectoryInfo.myParent == null) {
                    tweakAnchorStatus(myDirectoryInfo);
                }
                if (isSendableStatus(myAnchorStatus)) {
                    getDefaultHandler().handleStatus(myAnchorStatus);
                }
            }
        }
        myDirectoryInfo = myDirectoryInfo.myParent;
    }

    public void addFile(String path, String copyFromPath, long copyFromRevision) throws SVNException {
        myFileInfo = new FileInfo(myDirectoryInfo, path, true);
        myDirectoryInfo.myIsContentsChanged = true;
    }

    public void openFile(String path, long revision) throws SVNException {
        myFileInfo = new FileInfo(myDirectoryInfo, path, false);
    }

    public void changeFileProperty(String path, String name, SVNPropertyValue value) throws SVNException {
        if (!name.startsWith(SVNProperty.SVN_ENTRY_PREFIX)
                && !name.startsWith(SVNProperty.SVN_WC_PREFIX)) {
            myFileInfo.myIsPropertiesChanged = true;
        }
        if (SVNProperty.COMMITTED_REVISION.equals(name) && value != null) {
            try {
                long number = Long.parseLong(value.getString());
                myFileInfo.myRemoteRevision = SVNRevision.create(number);
            } catch (NumberFormatException nfe) {
                myFileInfo.myRemoteRevision = SVNRevision.UNDEFINED;
            }
        } else if (SVNProperty.COMMITTED_DATE.equals(name) && value != null) {
            myFileInfo.myRemoteDate = SVNDate.parseDate(value.getString());
        } else if (SVNProperty.LAST_AUTHOR.equals(name) && value != null) {
            myFileInfo.myRemoteAuthor = value.getString();
        }
    }

    public void applyTextDelta(String path, String baseChecksum) throws SVNException {
        myFileInfo.myIsContentsChanged = true;
    }

    public void closeFile(String path, String textChecksum) throws SVNException {
        if (!(myFileInfo.myIsAdded || myFileInfo.myIsPropertiesChanged || myFileInfo.myIsContentsChanged)) {
            return;
        }
        SVNStatusType contentsStatus;
        SVNStatusType propertiesStatus;
        SVNLock remoteLock = null;
        
        if (myFileInfo.myIsAdded) {
            contentsStatus = SVNStatusType.STATUS_ADDED;
            propertiesStatus = myFileInfo.myIsPropertiesChanged ? SVNStatusType.STATUS_ADDED : SVNStatusType.STATUS_NONE;
        } else {
            contentsStatus = myFileInfo.myIsContentsChanged ? SVNStatusType.STATUS_MODIFIED : SVNStatusType.STATUS_NONE;
            propertiesStatus = myFileInfo.myIsPropertiesChanged ? SVNStatusType.STATUS_MODIFIED : SVNStatusType.STATUS_NONE;
        }
        remoteLock = getLock(myFileInfo.myURL);
        tweakStatusHash(myFileInfo, myFileInfo.myPath, contentsStatus, propertiesStatus, remoteLock);
        myFileInfo = null;
    }

    public OutputStream textDeltaChunk(String path, SVNDiffWindow diffWindow) throws SVNException {
        return null;
    }

    public void textDeltaEnd(String path) throws SVNException {
    }

    public SVNCommitInfo closeEdit() throws SVNException {
        if (myIsRootOpen) {
            cleanup();
        } else {
            super.closeEdit();
        }
        return new SVNCommitInfo(getTargetRevision(), null, null);
    }

    public void abortEdit() throws SVNException {
    }

    public void absentDir(String path) throws SVNException {
    }

    public void absentFile(String path) throws SVNException {
    }
    
    private void handleStatusHash(SVNEntry dirEntry, Map hash, boolean deleted, SVNDepth depth) throws SVNException {
        ISVNStatusHandler handler = deleted ? this : getDefaultHandler();
        for(Iterator paths = hash.keySet().iterator(); paths.hasNext();) {
            File path = (File) paths.next();
            SVNStatus status = (SVNStatus) hash.get(path);
            
            if (status.getContentsStatus() != SVNStatusType.STATUS_OBSTRUCTED &&
                status.getContentsStatus() != SVNStatusType.STATUS_MISSING &&
                status.getEntry() != null && status.getKind() == SVNNodeKind.DIR && 
                (depth == SVNDepth.UNKNOWN || depth == SVNDepth.IMMEDIATES || 
                 depth == SVNDepth.INFINITY)) {
                SVNAdminArea dir = getWCAccess().retrieve(path);
                SVNDepth depthMinusOne = depth;
                if (depthMinusOne == SVNDepth.IMMEDIATES) {
                    depthMinusOne = SVNDepth.EMPTY;
                }
                getDirStatus(dirEntry, dir, null, depthMinusOne, isReportAll(), isNoIgnore(), null, true, handler);
            }
            if (deleted) {
                status.setRemoteStatus(SVNStatusType.STATUS_DELETED, null, null, null);
            }
            if (isSendableStatus(status)) {
                handler.handleStatus(status);
            }
        }
    }
    
    private void tweakStatusHash(FileInfo fileInfo, File path, SVNStatusType text, SVNStatusType props, SVNLock lock) throws SVNException {
        Map hash = fileInfo.myParent.myChildrenStatuses;
        SVNStatus status = (SVNStatus) hash.get(fileInfo.myPath);
        if (status == null) {
            if (text != SVNStatusType.STATUS_ADDED) {
                return;
            }
            status = createStatus(path);
            hash.put(fileInfo.myPath, status);
        }
        if (text == SVNStatusType.STATUS_ADDED && status.getRemoteContentsStatus() == SVNStatusType.STATUS_DELETED) {
            text = SVNStatusType.STATUS_REPLACED;
        }
        status.setRemoteStatus(fileInfo.myURL, text, props, lock, fileInfo.myRemoteKind, fileInfo.myRemoteRevision, fileInfo.myRemoteDate, fileInfo.myRemoteAuthor);
        status.setRepositoryRootURL(myRepositoryRoot);
        if (status.getRemoteURL() != null) {
            status.setRepositoryRelativePath(SVNURLUtil.getRelativeURL(myRepositoryRoot, status.getRemoteURL(), false));
        }
    }

    private void tweakStatusHash(DirectoryInfo dirInfo, DirectoryInfo childDir, File path, SVNStatusType text, SVNStatusType props, SVNLock lock, SVNRevision revision) throws SVNException {
        Map hash = dirInfo.myChildrenStatuses;
        SVNStatus status = (SVNStatus) hash.get(path);
        if (status == null) {
            if (text != SVNStatusType.STATUS_ADDED) {
                return;
            }
            status = createStatus(path);
            hash.put(path, status);
        }
        if (text == SVNStatusType.STATUS_ADDED && status.getRemoteContentsStatus() == SVNStatusType.STATUS_DELETED) {
            text = SVNStatusType.STATUS_REPLACED;
        }
        if (text == SVNStatusType.STATUS_DELETED) {
            SVNURL remoteURL = dirInfo.myURL;
            if (childDir != null) {
                if (childDir.myURL != null) {
                    remoteURL = childDir.myURL.appendPath(SVNFileUtil.getFileName(path), false);
                }
            } else {
                if (dirInfo.myURL != null) {
                    remoteURL = dirInfo.myURL.appendPath(SVNFileUtil.getFileName(path), false);
                }
            }
            if (revision == SVNRevision.UNDEFINED) {
                revision = dirInfo.myRemoteRevision;
            }
            status.setRemoteStatus(remoteURL, text, props, lock, SVNNodeKind.NONE, revision, null, null);
        } else if (childDir == null) {
            status.setRemoteStatus(dirInfo.myURL, text, props, lock, dirInfo.myRemoteKind, dirInfo.myRemoteRevision, dirInfo.myRemoteDate, dirInfo.myRemoteAuthor);
        } else {
            status.setRemoteStatus(childDir.myURL, text, props, lock, childDir.myRemoteKind, childDir.myRemoteRevision, childDir.myRemoteDate, childDir.myRemoteAuthor);
        }
        status.setRepositoryRootURL(myRepositoryRoot);
        if (status.getRemoteURL() != null) {
            status.setRepositoryRelativePath(SVNURLUtil.getRelativeURL(myRepositoryRoot, status.getRemoteURL(), false));
        }
    }
    
    private void tweakAnchorStatus(DirectoryInfo anchorInfo) {
        if (anchorInfo != null && (anchorInfo.myIsContentsChanged || anchorInfo.myIsPropertiesChanged)) {
            SVNStatusType text = anchorInfo.myIsContentsChanged ? SVNStatusType.STATUS_MODIFIED : SVNStatusType.STATUS_NONE;
            SVNStatusType props = anchorInfo.myIsPropertiesChanged ? SVNStatusType.STATUS_MODIFIED : SVNStatusType.STATUS_NONE;
            myAnchorStatus.setRemoteStatus(myDirectoryInfo.myURL, text, props, null, SVNNodeKind.DIR,
                    myDirectoryInfo.myRemoteRevision, myDirectoryInfo.myRemoteDate, myDirectoryInfo.myRemoteAuthor);
        }
    }
    
    private boolean isSendableStatus(SVNStatus status) {
        if (status == null) {
            return false;
        }
        if (status.getRemoteContentsStatus() != SVNStatusType.STATUS_NONE) {
            return true;
        }
        if (status.getRemotePropertiesStatus() != SVNStatusType.STATUS_NONE) {
            return true;
        }
        if (status.getRemoteLock() != null) {
            return true;
        }
        if (status.getContentsStatus() == SVNStatusType.STATUS_IGNORED && !isNoIgnore()) {
            return false;
        }
        if (isReportAll()) {
            return true;
        }
        if (status.getContentsStatus() == SVNStatusType.STATUS_UNVERSIONED) {
            return true;
        }
        if (status.getContentsStatus() != SVNStatusType.STATUS_NONE && status.getContentsStatus() != SVNStatusType.STATUS_NORMAL) {
            return true;
        }
        if (status.getPropertiesStatus() != SVNStatusType.STATUS_NONE && status.getPropertiesStatus() != SVNStatusType.STATUS_NORMAL) {
            return true;
        }
        if (status.getTreeConflict() != null) {
            return true;
        }
        return status.isLocked() || status.isSwitched() || 
        status.getLocalLock() != null || status.getChangelistName() != null || 
        status.isFileExternal();
    }
    
    private SVNStatus createStatus(File path) throws SVNException {
        SVNEntry entry = getWCAccess().getEntry(path, false);
        SVNEntry parentEntry = null;
        if (entry != null) {
            SVNAdminArea parentDir = getWCAccess().getAdminArea(path.getParentFile());
            if (parentDir != null) {
                parentEntry = getWCAccess().getEntry(path.getParentFile(), false);
            }
        }
        return assembleStatus(path, entry != null ? getWCAccess().probeRetrieve(path) : null, entry, 
                parentEntry, SVNNodeKind.UNKNOWN, false, true, false);
    }
    
    public void handleStatus(SVNStatus status) throws SVNException {
        status.setRemoteStatus(SVNStatusType.STATUS_DELETED, null, null, null);
        getDefaultHandler().handleStatus(status);
    }
    
    private class DirectoryInfo implements ISVNStatusHandler {
        
        public DirectoryInfo(String path, DirectoryInfo parent) throws SVNException {
            myParent = parent;
            if (myParent != null) {
                myPath = getAnchor().getFile(path);
            } else {
                myPath = getAnchor().getRoot();
            }
            myName = path != null ? SVNPathUtil.tail(path) : null;
            myChildrenStatuses = new TreeMap();
            myURL = computeURL();
            myRemoteRevision = SVNRevision.UNDEFINED;
            myRemoteKind = SVNNodeKind.DIR;

            if (myParent != null) {
                if (myParent.myDepth == SVNDepth.IMMEDIATES) {
                    myDepth = SVNDepth.EMPTY;
                } else if (myParent.myDepth == SVNDepth.FILES || myParent.myDepth == SVNDepth.EMPTY) {
                    myDepth = SVNDepth.EXCLUDE;
                } else if (myParent.myDepth == SVNDepth.UNKNOWN) {
                    myDepth = SVNDepth.UNKNOWN;
                } else {
                    myDepth = SVNDepth.INFINITY;
                }
            } else {
                myDepth = getDepth();
            }
            // this dir's status in parent.
            SVNStatus parentStatus = null;
            if (myParent != null) {
                parentStatus = (SVNStatus) myParent.myChildrenStatuses.get(myPath);
            } else {
                parentStatus = myAnchorStatus;
            }
            if (parentStatus != null) {
                SVNStatusType textStatus = parentStatus.getContentsStatus();
                if (textStatus != SVNStatusType.STATUS_UNVERSIONED &&
                        textStatus != SVNStatusType.STATUS_MISSING &&
                        textStatus != SVNStatusType.STATUS_OBSTRUCTED &&
                        textStatus != SVNStatusType.STATUS_EXTERNAL &&
                        textStatus != SVNStatusType.STATUS_IGNORED &&
                        parentStatus.getKind() == SVNNodeKind.DIR && 
                        (myDepth == SVNDepth.UNKNOWN || 
                         myDepth == SVNDepth.FILES || 
                         myDepth == SVNDepth.IMMEDIATES || 
                         myDepth == SVNDepth.INFINITY)) {
                    SVNAdminArea dir = getWCAccess().getAdminArea(myPath);
                    if (dir != null) {
                        getDirStatus(null, dir, null, SVNDepth.IMMEDIATES, true, true, null, true, this);
                        SVNStatus thisDirStatus = (SVNStatus)myChildrenStatuses.get(myPath);
                        if (thisDirStatus != null && thisDirStatus.getEntry() != null && (
                                myDepth == SVNDepth.UNKNOWN || 
                                myDepth.compareTo(parentStatus.getEntry().getDepth()) > 0)) {
                            myDepth = thisDirStatus.getEntry().getDepth();
                        }
                    }
                }
            }       
        }

        private SVNURL computeURL() throws SVNException {
            if (myURL != null) {
                return myURL;
            }
            if (myName == null) {
                return myAnchorStatus.getURL();
            }
            SVNStatus status = (SVNStatus) myParent.myChildrenStatuses.get(myPath);
            if (status != null && status.getEntry() != null && status.getEntry().getSVNURL() != null) {
                return status.getEntry().getSVNURL();
            }
            SVNURL url = myParent.computeURL();
            return url != null ? url.appendPath(myName, false) : null;
        }

        public void handleStatus(SVNStatus status) throws SVNException {
            myChildrenStatuses.put(status.getFile(), status);
        }

        public File myPath;
        public String myName;
        public SVNURL myURL;
        public DirectoryInfo myParent;
        public SVNDepth myDepth;
        public SVNRevision myRemoteRevision;
        public Date myRemoteDate;
        public String myRemoteAuthor;
        public SVNNodeKind myRemoteKind;
        
        public boolean myIsAdded;
        public boolean myIsPropertiesChanged;
        public boolean myIsContentsChanged;
        
        public Map myChildrenStatuses;
    }

    private class FileInfo {

        public FileInfo(DirectoryInfo parent, String path, boolean added) throws SVNException {
            myPath = getAnchor().getFile(path);
            myName = myPath.getName();
            myParent = parent;
            myURL = myParent.computeURL().appendPath(myName, false);
        
            myRemoteRevision = SVNRevision.UNDEFINED;
            myRemoteKind = SVNNodeKind.FILE;
            
            myIsAdded = added;
        }

        public DirectoryInfo myParent;
        public File myPath;
        public String myName;
        public SVNURL myURL;
        
        public boolean myIsAdded;
        public boolean myIsContentsChanged;
        public boolean myIsPropertiesChanged;
        
        public SVNRevision myRemoteRevision;
        public Date myRemoteDate;
        public String myRemoteAuthor;
        public SVNNodeKind myRemoteKind;
    }
}