/*
 * ====================================================================
 * 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.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;

import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNErrorMessage;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNMergeRange;
import org.tmatesoft.svn.core.SVNMergeRangeList;
import org.tmatesoft.svn.core.SVNNodeKind;
import org.tmatesoft.svn.core.SVNProperties;
import org.tmatesoft.svn.core.SVNProperty;
import org.tmatesoft.svn.core.SVNPropertyValue;
import org.tmatesoft.svn.core.SVNRevisionProperty;
import org.tmatesoft.svn.core.internal.util.SVNHashMap;
import org.tmatesoft.svn.core.internal.util.SVNMergeInfoUtil;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.wc.ISVNEventHandler;
import org.tmatesoft.svn.core.wc.SVNRevision;
import org.tmatesoft.svn.core.wc.admin.ISVNAdminEventHandler;
import org.tmatesoft.svn.core.wc.admin.SVNAdminEvent;
import org.tmatesoft.svn.core.wc.admin.SVNAdminEventAction;
import org.tmatesoft.svn.util.SVNLogType;


/**
 * @version 1.3
 * @author  TMate Software Ltd.
 */
public class DefaultDumpFilterHandler implements ISVNLoadHandler {

    private boolean myIsDoRenumberRevisions;
    private boolean myIsDoExclude;
    private boolean myIsPreserveRevisionProps;
    private boolean myIsDropEmptyRevisions;
    private boolean myIsSkipMissingMergeSources;
    private long myDroppedRevisionsCount;
    private long myLastLiveRevision;
    private OutputStream myOutputStream;
    private Collection myPrefixes;
    private Map myDroppedNodes;
    private Map myRenumberHistory;
    private RevisionBaton myCurrentRevisionBaton;
    private NodeBaton myCurrentNodeBaton;
    private ISVNAdminEventHandler myEventHandler;
    
    public DefaultDumpFilterHandler(OutputStream os, ISVNAdminEventHandler handler, boolean exclude, 
            boolean renumberRevisions, boolean dropEmptyRevisions, boolean preserveRevisionProperties, 
            Collection prefixes, boolean skipMissingMergeSources) {
        myDroppedRevisionsCount = 0;
        myLastLiveRevision = SVNRepository.INVALID_REVISION;
        myOutputStream = os;
        myEventHandler = handler;
        myIsDoExclude = exclude;
        myIsDoRenumberRevisions = renumberRevisions;
        myIsDropEmptyRevisions = dropEmptyRevisions;
        myIsPreserveRevisionProps = preserveRevisionProperties;
        myIsSkipMissingMergeSources = skipMissingMergeSources;
        myPrefixes = prefixes;
        myDroppedNodes = new SVNHashMap();
        myRenumberHistory = new SVNHashMap();
    }
    
    public void reset(OutputStream os, ISVNAdminEventHandler handler, boolean exclude, boolean renumberRevisions, 
            boolean dropEmptyRevisions, boolean preserveRevisionProperties, Collection prefixes, 
            boolean skipMissingMergeSources) {
        myDroppedRevisionsCount = 0;
        myLastLiveRevision = SVNRepository.INVALID_REVISION;
        myOutputStream = os;
        myEventHandler = handler;
        myIsDoExclude = exclude;
        myIsDoRenumberRevisions = renumberRevisions;
        myIsDropEmptyRevisions = dropEmptyRevisions;
        myIsPreserveRevisionProps = preserveRevisionProperties;
        myIsSkipMissingMergeSources = skipMissingMergeSources;
        myPrefixes = prefixes;
        myDroppedNodes.clear();
        myRenumberHistory.clear();
    }
    
    public void closeNode() throws SVNException {
        if (myCurrentNodeBaton.myIsDoSkip) {
            return;
        }
        if (!myCurrentNodeBaton.myHasWritingBegun) {
            outputNode(myCurrentNodeBaton);
        }
        writeDumpData(myOutputStream, "\n\n");
    }

    public void closeRevision() throws SVNException {
        if (myCurrentRevisionBaton != null && !myCurrentRevisionBaton.myHasWritingBegun) {
            outputRevision(myCurrentRevisionBaton);
        }
    }

    public void openNode(Map headers) throws SVNException {
        myCurrentNodeBaton = new NodeBaton();
        String nodePath = (String) headers.get(SVNAdminHelper.DUMPFILE_NODE_PATH);
        String copyFromPath = (String) headers.get(SVNAdminHelper.DUMPFILE_NODE_COPYFROM_PATH);
        if (!nodePath.startsWith("/")) {
            nodePath = "/" + nodePath;    
        }
        if (copyFromPath != null && !copyFromPath.startsWith("/")) {
            copyFromPath = "/" + copyFromPath;
        }
        
        myCurrentNodeBaton.myIsDoSkip = skipPath(nodePath);
        if (myCurrentNodeBaton.myIsDoSkip) {
            myDroppedNodes.put(nodePath, nodePath);
            myCurrentRevisionBaton.myHadDroppedNodes = true;
        } else {
            long textContentLength = getLongFromHeaders(SVNAdminHelper.DUMPFILE_TEXT_CONTENT_LENGTH, headers);
            if (copyFromPath != null && skipPath(copyFromPath)) {
                SVNNodeKind kind = getNodeKindFromHeaders(SVNAdminHelper.DUMPFILE_NODE_KIND, headers);
                if (textContentLength >= 0 && kind == SVNNodeKind.FILE) {
                    headers.remove(SVNAdminHelper.DUMPFILE_NODE_COPYFROM_PATH);
                    headers.remove(SVNAdminHelper.DUMPFILE_NODE_COPYFROM_REVISION);
                    copyFromPath = null;
                } else {
                    SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.INCOMPLETE_DATA, 
                            "Invalid copy source path ''{0}''", copyFromPath);
                    SVNErrorManager.error(err, SVNLogType.FSFS);
                }
            }
            
            myCurrentNodeBaton.myTextContentLength = textContentLength > 0 ? textContentLength : 0;
            myCurrentRevisionBaton.myHasNodes = true;
            if (!myCurrentRevisionBaton.myHasWritingBegun) {
                outputRevision(myCurrentRevisionBaton);
            }
            
            for (Iterator headersIter = headers.keySet().iterator(); headersIter.hasNext();) {
                String header = (String) headersIter.next();
                if (header.equals(SVNAdminHelper.DUMPFILE_CONTENT_LENGTH) || 
                        header.equals(SVNAdminHelper.DUMPFILE_PROP_CONTENT_LENGTH) ||
                        header.equals(SVNAdminHelper.DUMPFILE_TEXT_CONTENT_LENGTH)) {
                    continue;
                }

                String headerValue = (String) headers.get(header);
                if (myIsDoRenumberRevisions && header.equals(SVNAdminHelper.DUMPFILE_NODE_COPYFROM_REVISION)) {
                    long copyFromOriginalRevision = -1;
                    try {
                        copyFromOriginalRevision = Long.parseLong(headerValue);
                    } catch (NumberFormatException nfe) {
                        SVNErrorManager.error(SVNErrorMessage.create(SVNErrorCode.INCOMPLETE_DATA, nfe), SVNLogType.FSFS);
                    }
                    RevisionItem reNumberedCopyFromValue = (RevisionItem) myRenumberHistory.get(new Long(copyFromOriginalRevision));
                    if (reNumberedCopyFromValue == null || 
                            !SVNRevision.isValidRevisionNumber(reNumberedCopyFromValue.myRevision)) {
                        SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.NODE_UNEXPECTED_KIND, 
                                "No valid copyfrom revision in filtered stream");
                        SVNErrorManager.error(err, SVNLogType.FSFS);
                    }
                    writeDumpData(myOutputStream, SVNAdminHelper.DUMPFILE_NODE_COPYFROM_REVISION + ": " +
                            reNumberedCopyFromValue.myRevision + "\n");
                    continue;
                }
                writeDumpData(myOutputStream, header + ": " + headerValue + "\n");
            }
        }
    }

    public void openRevision(Map headers) throws SVNException {
        RevisionBaton revisionBaton = new RevisionBaton();
        revisionBaton.myProperties = new SVNProperties();
        revisionBaton.myOriginalRevision = getLongFromHeaders(SVNAdminHelper.DUMPFILE_REVISION_NUMBER, headers);
        if (myIsDoRenumberRevisions) {
            revisionBaton.myActualRevision = revisionBaton.myOriginalRevision - myDroppedRevisionsCount; 
        } else {
            revisionBaton.myActualRevision = revisionBaton.myOriginalRevision;
        }
        
        revisionBaton.writeToHeader(SVNAdminHelper.DUMPFILE_REVISION_NUMBER + ": " + 
                revisionBaton.myActualRevision + "\n");
        
        for (Iterator headersIter = headers.keySet().iterator(); headersIter.hasNext();) {
            String header = (String) headersIter.next();
            String headerValue = (String) headers.get(header);
            if (header.equals(SVNAdminHelper.DUMPFILE_CONTENT_LENGTH) || 
                    header.equals(SVNAdminHelper.DUMPFILE_PROP_CONTENT_LENGTH) ||
                    header.equals(SVNAdminHelper.DUMPFILE_REVISION_NUMBER)) {
                continue;
            }
            revisionBaton.writeToHeader(header + ": " + headerValue + "\n");
        }
        
        myCurrentRevisionBaton = revisionBaton;
    }

    public void parseTextBlock(InputStream dumpStream, long contentLength, boolean isDelta) throws SVNException {
        if (isDelta) {
            applyTextDelta();
        } else {
            setFullText();
        }
        
        byte[] buffer = null;
        if (contentLength > 0) {
            buffer = new byte[SVNFileUtil.STREAM_CHUNK_SIZE];
            while (contentLength > 0) {
                int numToRead = contentLength > SVNFileUtil.STREAM_CHUNK_SIZE ? 
                        SVNFileUtil.STREAM_CHUNK_SIZE : (int) contentLength;
                int read = 0;
                while(numToRead > 0) {
                    int numRead = -1;
                    try {
                        numRead = dumpStream.read(buffer, read, numToRead);
                    } catch (IOException ioe) {
                        SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, ioe.getMessage());
                        SVNErrorManager.error(err, ioe, SVNLogType.FSFS);
                    }

                    if (numRead < 0) {
                        SVNAdminHelper.generateIncompleteDataError();
                    }
                    read += numRead;
                    numToRead -= numRead;
                }
                            
                if (!myCurrentNodeBaton.myIsDoSkip) {
                    try {
                        myOutputStream.write(buffer, 0, read);
                    } catch (IOException ioe) {
                        SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.STREAM_UNEXPECTED_EOF, 
                                "Unexpected EOF writing contents");
                        SVNErrorManager.error(err, ioe, SVNLogType.FSFS);
                    }
                }

                contentLength -= read;
            }
        }
    }

    public void parseUUID(String uuid) throws SVNException {
        writeDumpData(myOutputStream, SVNAdminHelper.DUMPFILE_UUID + ": " + uuid + "\n\n");
    }

    public void removeNodeProperties() throws SVNException {
        myCurrentNodeBaton.myHasProps = true;
    }

    public void setFullText() throws SVNException {
        if (!myCurrentNodeBaton.myIsDoSkip) {
            myCurrentNodeBaton.myHasText = true;
            if (!myCurrentNodeBaton.myHasWritingBegun) {
                outputNode(myCurrentNodeBaton);
            }
        }
    }

    public void setRevisionProperty(String propertyName, SVNPropertyValue propertyValue) throws SVNException {
        myCurrentRevisionBaton.myHasProps = true;
        if (propertyValue == null) {
            myCurrentRevisionBaton.myProperties.remove(propertyName);
        } else {
            myCurrentRevisionBaton.myProperties.put(propertyName, propertyValue);
        }
    }

    public void setNodeProperty(String propertyName, SVNPropertyValue propertyValue) throws SVNException {
        if (myCurrentNodeBaton.myIsDoSkip) {
            return;
        }
        if (!myCurrentNodeBaton.myHasProps) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNSUPPORTED_FEATURE, 
                    "Delta property block detected - not supported by svndumpfilter");
            SVNErrorManager.error(err, SVNLogType.FSFS);
        }
        if (propertyName.equals(SVNProperty.MERGE_INFO)) {
            Map filteredMergeInfo = adjustMergeInfo(propertyValue);
            propertyValue = SVNPropertyValue.create(SVNMergeInfoUtil.formatMergeInfoToString(filteredMergeInfo, null));
        }
        myCurrentNodeBaton.writeProperty(propertyName, propertyValue);
    }

    public void deleteNodeProperty(String propertyName) throws SVNException {
    }

    public void applyTextDelta() throws SVNException {
    }

    public long getDroppedRevisionsCount() {
        return myDroppedRevisionsCount;
    }

    public Map getRenumberHistory() {
        return myRenumberHistory;
    }

    public Map getDroppedNodes() {
        return myDroppedNodes;
    }

    private void outputRevision(RevisionBaton revisionBaton) throws SVNException {
        revisionBaton.myHasWritingBegun = true;
        if (!myIsPreserveRevisionProps && !revisionBaton.myHasNodes && revisionBaton.myHadDroppedNodes &&
                !myIsDropEmptyRevisions) {
            SVNProperties oldProps = revisionBaton.myProperties;
            revisionBaton.myHasProps = true;
            revisionBaton.myProperties = new SVNProperties();
            revisionBaton.myProperties.put(SVNRevisionProperty.DATE, 
                    oldProps.getSVNPropertyValue(SVNRevisionProperty.DATE));
            revisionBaton.myProperties.put(SVNRevisionProperty.LOG, "This is an empty revision for padding.");
        }
        
        ByteArrayOutputStream propsBuffer = new ByteArrayOutputStream();
        if (revisionBaton.myHasProps) {
            for (Iterator propsIter = revisionBaton.myProperties.nameSet().iterator(); propsIter.hasNext();) {
                String propName = (String) propsIter.next();
                SVNPropertyValue propValue = revisionBaton.myProperties.getSVNPropertyValue(propName);
                writeProperty(propsBuffer, propName, propValue);
            }
            writeDumpData(propsBuffer, "PROPS-END\n");
            revisionBaton.writeToHeader(SVNAdminHelper.DUMPFILE_PROP_CONTENT_LENGTH + ": " + propsBuffer.size() + 
                    "\n");
        }
        
        revisionBaton.writeToHeader(SVNAdminHelper.DUMPFILE_CONTENT_LENGTH + ": " + propsBuffer.size() + "\n\n");
        writeDumpData(propsBuffer, "\n");
        
        if (revisionBaton.myHasNodes || !myIsDropEmptyRevisions || !revisionBaton.myHadDroppedNodes) {
            writeDumpData(myOutputStream, revisionBaton.myHeaderBuffer.toByteArray());
            writeDumpData(myOutputStream, propsBuffer.toByteArray());
            
            if (myIsDoRenumberRevisions) {
                myRenumberHistory.put(new Long(revisionBaton.myOriginalRevision), 
                        new RevisionItem(revisionBaton.myActualRevision, false));
                myLastLiveRevision = revisionBaton.myActualRevision;
            }
            
            String message = MessageFormat.format("Revision {0} committed as {1}.", new Object[] { 
                    String.valueOf(revisionBaton.myOriginalRevision), 
                    String.valueOf(revisionBaton.myActualRevision) });
            dispatchEvent(new SVNAdminEvent(revisionBaton.myActualRevision, revisionBaton.myOriginalRevision, 
                    SVNAdminEventAction.DUMP_FILTER_REVISION_COMMITTED, message));
        } else {
            myDroppedRevisionsCount++;
            if (myIsDoRenumberRevisions) {
                myRenumberHistory.put(new Long(revisionBaton.myOriginalRevision), 
                        new RevisionItem(myLastLiveRevision, true));
            }
            
            String message = MessageFormat.format("Revision {0} skipped.", new Object[] { 
                    String.valueOf(revisionBaton.myOriginalRevision) });
            dispatchEvent(new SVNAdminEvent(revisionBaton.myOriginalRevision, 
                    SVNAdminEventAction.DUMP_FILTER_REVISION_SKIPPED, message));
        }
    }
    
    private void outputNode(NodeBaton nodeBaton) throws SVNException {
        nodeBaton.myHasWritingBegun = true;
        if (nodeBaton.myHasProps) {
            nodeBaton.writeToPropertyBuffer("PROPS-END\n");
            nodeBaton.writeToHeader(SVNAdminHelper.DUMPFILE_PROP_CONTENT_LENGTH + ": " + 
                    nodeBaton.myPropertiesBuffer.size() + "\n");
        }
        if (nodeBaton.myHasText) {
            nodeBaton.writeToHeader(SVNAdminHelper.DUMPFILE_TEXT_CONTENT_LENGTH + ": " + 
                    nodeBaton.myTextContentLength + "\n");
        }
        nodeBaton.writeToHeader(SVNAdminHelper.DUMPFILE_CONTENT_LENGTH + ": " + 
                (nodeBaton.myPropertiesBuffer.size() + nodeBaton.myTextContentLength) + "\n\n");
        writeDumpData(myOutputStream, nodeBaton.myHeaderBuffer.toByteArray());
        writeDumpData(myOutputStream, nodeBaton.myPropertiesBuffer.toByteArray());
    }
    
    private void writeProperty(OutputStream out, String propName, SVNPropertyValue propValue) throws SVNException {
        try {
            writeDumpData(out, "K ");
            byte[] propNameBytes = propName.getBytes("UTF-8");
            writeDumpData(out, String.valueOf(propNameBytes.length));
            writeDumpData(out, "\n");
            writeDumpData(out, propNameBytes);
            
            writeDumpData(out, "\n");
            writeDumpData(out, "V ");
            byte[] propValueBytes = SVNPropertyValue.getPropertyAsBytes(propValue);
            writeDumpData(out, String.valueOf(propValueBytes.length));
            writeDumpData(out, "\n");
            writeDumpData(out, propValueBytes);
            writeDumpData(out, "\n");
        } catch (IOException e) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e.getLocalizedMessage());
            SVNErrorManager.error(err, e, SVNLogType.FSFS);
        } 
    }
    
    private Map adjustMergeInfo(SVNPropertyValue initialValue) throws SVNException {
        Map finalMergeInfo = new TreeMap();
        Map mergeInfo = SVNMergeInfoUtil.parseMergeInfo(new StringBuffer(initialValue.getString()), null);
        for (Iterator mergeInfoIter = mergeInfo.keySet().iterator(); mergeInfoIter.hasNext();) {
            String mergeSource = (String) mergeInfoIter.next();
            SVNMergeRangeList rangeList = (SVNMergeRangeList) mergeInfo.get(mergeSource);
            if (skipPath(mergeSource)) {
                if (myIsSkipMissingMergeSources) {
                    continue;
                }

                SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.INCOMPLETE_DATA, 
                        "Missing merge source path ''{0}''; try with --skip-missing-merge-sources", 
                        mergeSource);
                SVNErrorManager.error(err, SVNLogType.FSFS);
            }
            
            if (myIsDoRenumberRevisions) {
                SVNMergeRange[] ranges = rangeList.getRanges();
                for (int i = 0; i < rangeList.getSize(); i++) {
                    SVNMergeRange range = ranges[i];
                    
                    RevisionItem revItemStart = (RevisionItem) myRenumberHistory.get(new Long(range.getStartRevision()));
                    if (revItemStart == null || !SVNRevision.isValidRevisionNumber(revItemStart.myRevision)) {
                        SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.NODE_UNEXPECTED_KIND, 
                                "No valid revision range 'start' in filtered stream");
                        SVNErrorManager.error(err, SVNLogType.FSFS);
                    }

                    RevisionItem revItemEnd = (RevisionItem) myRenumberHistory.get(new Long(range.getEndRevision()));
                    if (revItemEnd == null || !SVNRevision.isValidRevisionNumber(revItemEnd.myRevision)) {
                        SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.NODE_UNEXPECTED_KIND, 
                                "No valid revision range 'end' in filtered stream");
                        SVNErrorManager.error(err, SVNLogType.FSFS);
                    }
                    
                    range.setStartRevision(revItemStart.myRevision);
                    range.setEndRevision(revItemEnd.myRevision);
                }
                Arrays.sort(ranges);
            }
            finalMergeInfo.put(mergeSource, rangeList);
        }
        return finalMergeInfo;
    }
    
    private SVNNodeKind getNodeKindFromHeaders(String header, Map headers) {
        return SVNNodeKind.parseKind((String) headers.get(header)); 
    }
    
    private long getLongFromHeaders(String header, Map headers) {
        String val = (String) headers.get(header);
        if (val != null) {
            try {
                return Long.parseLong(val);
            } catch (NumberFormatException nfe) {
                //
            }
        }
        return -1;
    }
    
    private void writeDumpData(OutputStream out, String data) throws SVNException {
        try {
            out.write(data.getBytes("UTF-8")); 
        } catch (IOException ioe) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, ioe.getLocalizedMessage());
            SVNErrorManager.error(err, ioe, SVNLogType.FSFS);
        }
    }

    private void writeDumpData(OutputStream out, byte[] bytes) throws SVNException {
        try {
            out.write(bytes); 
        } catch (IOException ioe) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, ioe.getLocalizedMessage());
            SVNErrorManager.error(err, ioe, SVNLogType.FSFS);
        }
    }

    private boolean skipPath(String path) {
        for (Iterator prefixesIter = myPrefixes.iterator(); prefixesIter.hasNext();) {
            String prefix = (String) prefixesIter.next();
            if (path.startsWith(prefix)) {
                return myIsDoExclude;
            }
        }
        return !myIsDoExclude;
    }
    
    private void dispatchEvent(SVNAdminEvent event) throws SVNException {
        if (myEventHandler != null) {
            myEventHandler.handleAdminEvent(event, ISVNEventHandler.UNKNOWN);
        }
    }

    public class RevisionItem {
        long myRevision;
        boolean myWasDropped;
        
        public RevisionItem(long revision, boolean dropped) {
            myRevision = revision;
            myWasDropped = dropped;
        }
        
        public boolean wasDropped() {
            return myWasDropped;
        }
        
        public long getRevision() {
            return myRevision;
        }
    }

    private class RevisionBaton {
        boolean myHasNodes;
        boolean myHasProps;
        boolean myHadDroppedNodes;
        boolean myHasWritingBegun;
        long myOriginalRevision;
        long myActualRevision;
        SVNProperties myProperties;
        ByteArrayOutputStream myHeaderBuffer;
 
        void writeToHeader(String data) throws SVNException {
            if (myHeaderBuffer == null) {
                myHeaderBuffer = new ByteArrayOutputStream();
            }
            writeDumpData(myHeaderBuffer, data);
        }
    }
    
    private class NodeBaton {
        boolean myIsDoSkip;
        boolean myHasProps;
        boolean myHasText;
        boolean myHasWritingBegun;
        long myTextContentLength;
        ByteArrayOutputStream myPropertiesBuffer;
        ByteArrayOutputStream myHeaderBuffer;
        
        public NodeBaton() {
            myPropertiesBuffer = new ByteArrayOutputStream();
            myHeaderBuffer = new ByteArrayOutputStream();
        }
        
        void writeProperty(String propName, SVNPropertyValue propValue) throws SVNException {
            DefaultDumpFilterHandler.this.writeProperty(myPropertiesBuffer, propName, propValue);
        }
        
        void writeToPropertyBuffer(String data) throws SVNException {
            DefaultDumpFilterHandler.this.writeDumpData(myPropertiesBuffer, data);
        }
        
        void writeToHeader(String data) throws SVNException {
            writeDumpData(myHeaderBuffer, data);
        }

    }

}
