/*
 * ====================================================================
 * Copyright (c) 2004-2012 TMate Software Ltd.  All rights reserved.
 *
 * This software is licensed as described in the file COPYING, which
 * you should have received as part of this distribution.  The terms
 * are also available at http://svnkit.com/license.html
 * If newer versions of this license are posted there, you may use a
 * newer version instead, at your option.
 * ====================================================================
 */

package org.tmatesoft.svn.core.io;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarInputStream;
import java.util.regex.Pattern;

import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNErrorMessage;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNProperties;
import org.tmatesoft.svn.core.SVNRevisionProperty;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.internal.db.SVNSqlJetDb;
import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory;
import org.tmatesoft.svn.core.internal.io.fs.FSFS;
import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryFactory;
import org.tmatesoft.svn.core.internal.io.fs.FSRepresentationCacheUtil;
import org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl;
import org.tmatesoft.svn.core.internal.util.SVNDate;
import org.tmatesoft.svn.core.internal.util.SVNHashMap;
import org.tmatesoft.svn.core.internal.util.SVNUUIDGenerator;
import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
import org.tmatesoft.svn.core.internal.wc.SVNFileListUtil;
import org.tmatesoft.svn.core.internal.wc.SVNFileType;
import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
import org.tmatesoft.svn.core.internal.wc.SVNWCProperties;
import org.tmatesoft.svn.core.internal.wc.admin.SVNTranslator;
import org.tmatesoft.svn.core.internal.wc17.db.statement.SVNWCDbStatements;
import org.tmatesoft.svn.util.SVNLogType;

/**
 * <b>SVNRepositoryFactory</b> is an abstract factory that is responsible
 * for creating an appropriate <b>SVNRepository</b> driver specific for the
 * protocol to use.
 *
 * <p>
 * Depending on what protocol a user exactly would like to use
 * to access the repository he should first of all set up an
 * appropriate extension of this factory. So, if the user is going to
 * work with the repository via the custom <i>svn</i>-protocol (or
 * <i>svn+xxx</i>) he initially calls:
 * <pre class="javacode">
 * ...
 * <span class="javakeyword">import</span> org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl;
 * ...
 *     <span class="javacomment">//do it once in your application prior to using the library</span>
 *     <span class="javacomment">//enables working with a repository via the svn-protocol (over svn and svn+ssh)</span>
 *     SVNRepositoryFactoryImpl.setup();
 * ...</pre><br />
 * From this point the <b>SVNRepositoryFactory</b> knows how to create
 * <b>SVNRepository</b> instances specific for the <i>svn</i>-protocol.
 * And further on the user can create an <b>SVNRepository</b> instance:
 * <pre class="javacode">
 *     ...
 *     <span class="javacomment">//creating a new SVNRepository instance</span>
 *     String url = <span class="javastring">"svn://host/path"</span>;
 *     SVNRepository repository = SVNRepositoryFactory.create(SVNURL.parseURIDecoded(url));
 *     ...</pre><br />
 *
 * <table cellpadding="3" cellspacing="1" border="0" width="70%" bgcolor="#999933">
 * <tr bgcolor="#ADB8D9" align="left">
 * <td><b>Supported Protocols</b></td>
 * <td><b>Factory to setup</b></td>
 * </tr>
 * <tr bgcolor="#EAEAEA" align="left">
 * <td>svn://, svn+xxx://</td><td>SVNRepositoryFactoryImpl (<b>org.tmatesoft.svn.core.internal.io.svn</b>)</td>
 * </tr>
 * <tr bgcolor="#EAEAEA" align="left">
 * <td>http://, https://</td><td>DAVRepositoryFactory (<b>org.tmatesoft.svn.core.internal.io.dav</b>)</td>
 * </tr>
 * <tr bgcolor="#EAEAEA" align="left">
 * <td>file:/// (FSFS only)</td><td>FSRepositoryFactory (<b>org.tmatesoft.svn.core.internal.io.fs</b>)</td>
 * </tr>
 * </table>
 *
 * <p>
 * Also <b>SVNRepositoryFactory</b> may be used to create local
 * FSFS-type repositories.
 *
 * @version 1.3
 * @author  TMate Software Ltd.
 * @since   1.2
 * @see     SVNRepository
 * @see     <a target="_top" href="http://svnkit.com/kb/examples/">Examples</a>
 */
public abstract class SVNRepositoryFactory {

    private static final Map myFactoriesMap = new SVNHashMap();
    private static final String REPOSITORY_TEMPLATE_PATH = "/org/tmatesoft/svn/core/io/repository/template.jar";

    static {
        FSRepositoryFactory.setup();
        SVNRepositoryFactoryImpl.setup();
        DAVRepositoryFactory.setup();
    }

    protected static void registerRepositoryFactory(String protocol, SVNRepositoryFactory factory) {
        if (protocol != null && factory != null) {
            synchronized (myFactoriesMap) {
                myFactoriesMap.put(protocol, factory);
            }
        }
    }

    protected static boolean hasRepositoryFactory(String protocol) {
        if (protocol != null) {
            synchronized (myFactoriesMap) {
                return myFactoriesMap.get(protocol) != null;
            }
        }
        return false;
    }

    /**
     * Creates an <b>SVNRepository</b> driver according to the protocol that is to be
     * used to access a repository.
     *
     * <p>
     * The protocol is defined as the beginning part of the URL schema. Currently
     * SVNKit supports only <i>svn://</i> (<i>svn+ssh://</i>) and <i>http://</i> (<i>https://</i>)
     * schemas.
     *
     * <p>
     * The created <b>SVNRepository</b> driver can later be <i>"reused"</i> for another
     * location - that is you can switch it to another repository url not to
     * create yet one more <b>SVNRepository</b> object. Use the {@link SVNRepository#setLocation(SVNURL, boolean) SVNRepository.setLocation()}
     * method for this purpose.
     *
     * <p>
     * An <b>SVNRepository</b> driver created by this method uses a default
     * session options driver ({@link ISVNSession#DEFAULT}) which does not
     * allow to keep a single socket connection opened and commit log messages
     * caching.
     *
     * @param  url              a repository location URL
     * @return                  a protocol specific <b>SVNRepository</b> driver
     * @throws SVNException     if there's no implementation for the specified protocol
     *                          (the user may have forgotten to register a specific
     *                          factory that creates <b>SVNRepository</b>
     *                          instances for that protocol or the SVNKit
     *                          library does not support that protocol at all)
     * @see                     #create(SVNURL, ISVNSession)
     * @see                     SVNRepository
     *
     */
    public static SVNRepository create(SVNURL url) throws SVNException {
        return create(url, null);

    }

    /**
     * Creates an <b>SVNRepository</b> driver according to the protocol that is to be
     * used to access a repository.
     *
     * <p>
     * The protocol is defined as the beginning part of the URL schema. Currently
     * SVNKit supports only <i>svn://</i> (<i>svn+ssh://</i>) and <i>http://</i> (<i>https://</i>)
     * schemas.
     *
     * <p>
     * The created <b>SVNRepository</b> driver can later be <i>"reused"</i> for another
     * location - that is you can switch it to another repository url not to
     * create yet one more <b>SVNRepository</b> object. Use the {@link SVNRepository#setLocation(SVNURL, boolean) SVNRepository.setLocation()}
     * method for this purpose.
     *
     * <p>
     * This method allows to customize a session options driver for an <b>SVNRepository</b> driver.
     * A session options driver must implement the <b>ISVNSession</b> interface. It manages socket
     * connections - says whether an <b>SVNRepository</b> driver may use a single socket connection
     * during the runtime, or it should open a new connection per each repository access operation.
     * And also a session options driver may cache and provide commit log messages during the
     * runtime.
     *
     * @param  url              a repository location URL
     * @param  options          a session options driver
     * @return                  a protocol specific <b>SVNRepository</b> driver
     * @throws SVNException     if there's no implementation for the specified protocol
     *                          (the user may have forgotten to register a specific
     *                          factory that creates <b>SVNRepository</b>
     *                          instances for that protocol or the SVNKit
     *                          library does not support that protocol at all)
     * @see                     #create(SVNURL)
     * @see                     SVNRepository
     */
    public static SVNRepository create(SVNURL url, ISVNSession options) throws SVNException {
        String urlString = url.toString();
        synchronized (myFactoriesMap) {
            for(Iterator keys = myFactoriesMap.keySet().iterator(); keys.hasNext();) {
                String key = (String) keys.next();
                if (Pattern.matches(key, urlString)) {
                    return ((SVNRepositoryFactory) myFactoriesMap.get(key)).createRepositoryImpl(url, options);
                }
            }
        }
        if ("file".equalsIgnoreCase(url.getProtocol())) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.RA_LOCAL_REPOS_OPEN_FAILED, "Unable to open repository ''{0}''", new Object[]{url});
            SVNErrorManager.error(err, SVNLogType.NETWORK);
        }
        SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.BAD_URL, "Unable to create SVNRepository object for ''{0}''", url);
        SVNErrorManager.error(err, SVNLogType.NETWORK);
        return null;
    }

    /**
     * Creates a local blank FSFS-type repository.
     * A call to this routine is equivalent to
     * <code>createLocalRepository(path, null, enableRevisionProperties, force)</code>.
     *
     * @param  path                          a repository root location
     * @param  enableRevisionProperties      enables or not revision property
     *                                       modifications
     * @param  force                         forces operation to run
     * @return                               a local URL (file:///) of a newly
     *                                       created repository
     * @throws SVNException
     * @see                                  #createLocalRepository(File, String, boolean, boolean)
     * @since                                1.1
     */
    public static SVNURL createLocalRepository(File path, boolean enableRevisionProperties, boolean force) throws SVNException {
        return createLocalRepository(path, null, enableRevisionProperties, force);
    }

    /**
     * Creates a local blank FSFS-type repository. This is just similar to
     * the Subversion's command: <code>svnadmin create --fs-type=fsfs REPOS_PATH</code>.
     * The resultant repository is absolutely format-compatible with Subversion.
     *
     * <p>
     * If <code>uuid</code> is <span class="javakeyword">null</span> or not 36 chars
     * wide, the method generates a new UUID for the repository. This UUID would have
     * the same format as if it's generated by Subversion itself.
     *
     * <p>
     * If <code>enableRevisionProperties</code> is <span class="javakeyword">true</span>
     * then the method creates a <code>pre-revprop-change</code> executable file inside
     * the <code>"hooks"</code> subdir of the repository tree. This executable file
     * simply returns 0 thus allowing revision property modifications, which are not
     * permitted, unless one puts such a hook into that very directory.
     *
     * <p>
     * If <code>force</code> is <span class="javakeyword">true</span> and <code>path</code> already
     * exists, deletes that path and creates a repository in its place.
     *
     * <p>
     * A call to this routine is equivalent to
     * <code>createLocalRepository(path, uuid, enableRevisionProperties, force, false)</code>.
     *
     * @param  path                          a repository root location
     * @param  uuid                          a repository's uuid
     * @param  enableRevisionProperties      enables or not revision property
     *                                       modifications
     * @param  force                         forces operation to run
     * @return                               a local URL (file:///) of a newly
     *                                       created repository
     * @throws SVNException
     * @see                                  #createLocalRepository(File, String, boolean, boolean, boolean)
     * @since                                1.1
     */
    public static SVNURL createLocalRepository(File path, String uuid, boolean enableRevisionProperties, boolean force) throws SVNException {
        return createLocalRepository(path, uuid, enableRevisionProperties, force, false);
    }

    /**
     * Creates a local blank FSFS-type repository. This is just similar to
     * the Subversion's command: <code>svnadmin create --fs-type=fsfs REPOS_PATH</code>.
     * The resultant repository is absolutely format-compatible with Subversion.
     *
     * <p>
     * If <code>uuid</code> is <span class="javakeyword">null</span> or not 36 chars
     * wide, the method generates a new UUID for the repository. This UUID would have
     * the same format as if it's generated by Subversion itself.
     *
     * <p>
     * If <code>enableRevisionProperties</code> is <span class="javakeyword">true</span>
     * then the method creates a <code>pre-revprop-change</code> executable file inside
     * the <code>"hooks"</code> subdir of the repository tree. This executable file
     * simply returns 0 thus allowing revision property modifications, which are not
     * permitted, unless one puts such a hook into that very directory.
     *
     * <p>
     * If <code>force</code> is <span class="javakeyword">true</span> and <code>path</code> already
     * exists, deletes that path and creates a repository in its place.
     *
     * <p>
     * Set <code>pre14Compatible</code> to <span class="javakeyword">true</span> if you want a new repository
     * to be compatible with pre-1.4 servers.
     *
     * <p>
     * Note: this method is identical to <code>createLocalRepository(path, uuid, enableRevisionProperties, force, pre14Compatible, false)</code>.
     *
     * @param  path                          a repository root location
     * @param  uuid                          a repository's uuid
     * @param  enableRevisionProperties      enables or not revision property
     *                                       modifications
     * @param  force                         forces operation to run
     * @param  pre14Compatible               <span class="javakeyword">true</span> to
     *                                       create a repository with pre-1.4 format
     * @return                               a local URL (file:///) of a newly
     *                                       created repository
     * @throws SVNException
     * @since                                1.1.1
     * @see                                  #createLocalRepository(File, String, boolean, boolean, boolean, boolean)
     */
    public static SVNURL createLocalRepository(File path, String uuid, boolean enableRevisionProperties, boolean force, boolean pre14Compatible) throws SVNException {
        return createLocalRepository(path, uuid, enableRevisionProperties, force, pre14Compatible, false);
    }

    /**
     * Creates a local blank FSFS-type repository. This is just similar to
     * the Subversion's command: <code>svnadmin create --fs-type=fsfs REPOS_PATH</code>.
     * The resultant repository is absolutely format-compatible with Subversion.
     *
     * <p>
     * If <code>uuid</code> is <span class="javakeyword">null</span> or not 36 chars
     * wide, the method generates a new UUID for the repository. This UUID would have
     * the same format as if it's generated by Subversion itself.
     *
     * <p>
     * If <code>enableRevisionProperties</code> is <span class="javakeyword">true</span>
     * then the method creates a <code>pre-revprop-change</code> executable file inside
     * the <code>"hooks"</code> subdir of the repository tree. This executable file
     * simply returns 0 thus allowing revision property modifications, which are not
     * permitted, unless one puts such a hook into that very directory.
     *
     * <p>
     * If <code>force</code> is <span class="javakeyword">true</span> and <code>path</code> already
     * exists, deletes that path and creates a repository in its place.
     *
     * <p>
     * Set <code>pre14Compatible</code> to <span class="javakeyword">true</span> if you want a new repository
     * to be compatible with pre-1.4 servers.
     *
     * <p>
     * Set <code>pre15Compatible</code> to <span class="javakeyword">true</span> if you want a new repository
     * to be compatible with pre-1.5 servers.
     *
     * <p>
     * There must be only one option (either <code>pre14Compatible</code> or <code>pre15Compatible</code>)
     * set to <span class="javakeyword">true</span> at a time.
     *
     * @param  path                          a repository root location
     * @param  uuid                          a repository's uuid
     * @param  enableRevisionProperties      enables or not revision property
     *                                       modifications
     * @param  force                         forces operation to run
     * @param  pre14Compatible               <span class="javakeyword">true</span> to
     *                                       create a repository with pre-1.4 format
     * @param  pre15Compatible               <span class="javakeyword">true</span> to
     *                                       create a repository with pre-1.5 format
     * @return                               a local URL (file:///) of a newly
     *                                       created repository
     * @throws SVNException
     * @since                                1.2
     */
    public static SVNURL createLocalRepository(File path, String uuid, boolean enableRevisionProperties,
            boolean force, boolean pre14Compatible, boolean pre15Compatible) throws SVNException {
        return createLocalRepository(path, uuid, enableRevisionProperties, force, pre14Compatible, pre15Compatible, false);
    }

    /**
     * Creates a local blank FSFS-type repository. This is just similar to
     * the Subversion's command: <code>svnadmin create --fs-type=fsfs REPOS_PATH</code>.
     * The resultant repository is absolutely format-compatible with Subversion.
     *
     * <p>
     * If <code>uuid</code> is <span class="javakeyword">null</span> or not 36 chars
     * wide, the method generates a new UUID for the repository. This UUID would have
     * the same format as if it's generated by Subversion itself.
     *
     * <p>
     * If <code>enableRevisionProperties</code> is <span class="javakeyword">true</span>
     * then the method creates a <code>pre-revprop-change</code> executable file inside
     * the <code>"hooks"</code> subdir of the repository tree. This executable file
     * simply returns 0 thus allowing revision property modifications, which are not
     * permitted, unless one puts such a hook into that very directory.
     *
     * <p>
     * If <code>force</code> is <span class="javakeyword">true</span> and <code>path</code> already
     * exists, deletes that path and creates a repository in its place.
     *
     * <p>
     * Set <code>pre14Compatible</code> to <span class="javakeyword">true</span> if you want a new repository
     * to be compatible with pre-1.4 servers.
     *
     * <p>
     * Set <code>pre15Compatible</code> to <span class="javakeyword">true</span> if you want a new repository
     * to be compatible with pre-1.5 servers.
     *
     * <p>
     * Set <code>pre16Compatible</code> to <span class="javakeyword">true</span> if you want a new repository
     * to be compatible with pre-1.6 servers.
     *
     * <p>
     * There must be only one option (either <code>pre14Compatible</code> or <code>pre15Compatible</code> or
     * <code>pre16Compatible</code>) set to <span class="javakeyword">true</span> at a time.
     *
     * @param  path                          a repository root location
     * @param  uuid                          a repository's uuid
     * @param  enableRevisionProperties      enables or not revision property
     *                                       modifications
     * @param  force                         forces operation to run
     * @param  pre14Compatible               <span class="javakeyword">true</span> to
     *                                       create a repository with pre-1.4 format
     * @param  pre15Compatible               <span class="javakeyword">true</span> to
     *                                       create a repository with pre-1.5 format
     * @param  pre16Compatible               <span class="javakeyword">true</span> to
     *                                       create a repository with pre-1.6 format
     * @return                               a local URL (file:///) of a newly
     *                                       created repository
     * @throws SVNException
     * @since                                1.3
     */
     public static SVNURL createLocalRepository(File path, String uuid, boolean enableRevisionProperties,
            boolean force, boolean pre14Compatible, boolean pre15Compatible, boolean pre16Compatible) throws SVNException {
         return createLocalRepository(path, uuid, enableRevisionProperties,
                 force, pre14Compatible, pre15Compatible, pre16Compatible, false, false);
     }
    public static SVNURL createLocalRepository(File path, String uuid, boolean enableRevisionProperties,
                                               boolean force, boolean pre14Compatible, boolean pre15Compatible, boolean pre16Compatible,
                                               boolean pre17Compatible, boolean with17Compatible) throws SVNException {
        return createLocalRepository(path, uuid, enableRevisionProperties, force, pre14Compatible, pre15Compatible, pre16Compatible,
                pre17Compatible, with17Compatible, true);
    }

     public static SVNURL createLocalRepository(File path, String uuid, boolean enableRevisionProperties,
            boolean force, boolean pre14Compatible, boolean pre15Compatible, boolean pre16Compatible,
            boolean pre17Compatible, boolean with17Compatible, boolean pre10Compatible) throws SVNException {
        SVNFileType fType = SVNFileType.getType(path);
        if (fType != SVNFileType.NONE) {
            if (fType == SVNFileType.DIRECTORY) {
                File[] children = SVNFileListUtil.listFiles(path);
                if ( children != null && children.length != 0) {
                    if (!force) {
                        SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "''{0}'' already exists; use ''force'' to overwrite existing files", path);
                        SVNErrorManager.error(err, SVNLogType.FSFS);
                    } else {
                        SVNFileUtil.deleteAll(path, true);
                    }
                }
            } else {
                if (!force) {
                    SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "''{0}'' already exists; use ''force'' to overwrite existing files", path);
                    SVNErrorManager.error(err, SVNLogType.FSFS);
                } else {
                    SVNFileUtil.deleteAll(path, true);
                }
            }
        }
        //SVNFileUtil.deleteAll(path, true);
        // check if path is inside repository
        File root = FSFS.findRepositoryRoot(path);
        if (root != null) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.REPOS_BAD_ARGS, "''{0}'' is a subdirectory of an existing repository rooted at ''{1}''", new Object[] {path, root});
            SVNErrorManager.error(err, SVNLogType.FSFS);
        }
        if (!path.mkdirs() && !path.exists()) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "Can not create directory ''{0}''", path);
            SVNErrorManager.error(err, SVNLogType.FSFS);
        }
        InputStream is = SVNRepositoryFactory.class.getResourceAsStream(REPOSITORY_TEMPLATE_PATH);
        if (is == null) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "No repository template found; should be part of SVNKit library jar");
            SVNErrorManager.error(err, SVNLogType.FSFS);
        }
        File jarFile = SVNFileUtil.createUniqueFile(path, ".template", ".jar", true);
        OutputStream uuidOS = null;
        OutputStream reposFormatOS = null;
        OutputStream fsFormatOS = null;
        OutputStream txnCurrentOS = null;
        OutputStream minUnpacledOS = null;
        OutputStream currentOS = null;
        try {
            copyToFile(is, jarFile);
            extract(jarFile, path);
            SVNFileUtil.deleteFile(jarFile);
            // translate eols.
            if (!SVNFileUtil.isWindows) {
                translateFiles(path);
                translateFiles(new File(path, "conf"));
                translateFiles(new File(path, "hooks"));
                translateFiles(new File(path, "locks"));
            }
            // create pre-rev-prop.
            if (enableRevisionProperties) {
                if (SVNFileUtil.isWindows) {
                    File hookFile = new File(path, "hooks/pre-revprop-change.bat");
                    OutputStream os = SVNFileUtil.openFileForWriting(hookFile);
                    try {
                        os.write("@echo off".getBytes());
                    } catch (IOException e) {
                        SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "Cannot create pre-rev-prop-change hook file at ''{0}'': {1}",
                                new Object[] {hookFile, e.getLocalizedMessage()});
                        SVNErrorManager.error(err, SVNLogType.FSFS);
                    } finally {
                        SVNFileUtil.closeFile(os);
                    }
                } else {
                    File hookFile = new File(path, "hooks/pre-revprop-change");
                    OutputStream os = null;
                    try {
                        os = SVNFileUtil.openFileForWriting(hookFile);
                        os.write("#!/bin/sh\nexit 0".getBytes("US-ASCII"));
                    } catch (IOException e) {
                        SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "Cannot create pre-rev-prop-change hook file at ''{0}'': {1}",
                                new Object[] {hookFile, e.getLocalizedMessage()});
                        SVNErrorManager.error(err, SVNLogType.FSFS);
                    } finally {
                        SVNFileUtil.closeFile(os);
                    }
                    SVNFileUtil.setExecutable(hookFile, true);
                }
            }
            int fsFormat = FSFS.DB_FORMAT;
            if (pre10Compatible) {
                fsFormat = FSFS.DB_FORMAT_PRE_10;
            }
            if( FSFS.DB_FORMAT_PRE_17_USE_AS_DEFAULT && !with17Compatible ) {
                fsFormat = FSFS.DB_FORMAT_PRE_17;
            }
            if( pre17Compatible) {
                fsFormat = FSFS.DB_FORMAT_PRE_17;
            }
            if (pre14Compatible) {
                File reposFormatFile = new File(path, "format");
                try {
                    reposFormatOS = SVNFileUtil.openFileForWriting(reposFormatFile);
                    String format = String.valueOf(FSFS.REPOSITORY_FORMAT_LEGACY);
                    format += '\n';
                    reposFormatOS.write(format.getBytes("US-ASCII"));
                } catch (IOException e) {
                    SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "Error writing repository format to ''{0}''", reposFormatFile);
                    err.setChildErrorMessage(SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e.getLocalizedMessage()));
                    SVNErrorManager.error(err, SVNLogType.FSFS);
                }
            }
            if (pre16Compatible) {
                fsFormat = 3;
            }
            if (pre14Compatible || pre15Compatible) {
                if (pre14Compatible) {
                    fsFormat = 1;
                } else if (pre15Compatible) {
                    fsFormat = 2;
                } //else
                File fsFormatFile = new File(path, "db/format");
                try {
                    fsFormatOS = SVNFileUtil.openFileForWriting(fsFormatFile);
                    String format = String.valueOf(fsFormat);
                    format += '\n';
                    fsFormatOS.write(format.getBytes("US-ASCII"));
                } catch (IOException e) {
                    SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR,
                            "Error writing fs format to ''{0}''", fsFormatFile);
                    err.setChildErrorMessage(SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e.getLocalizedMessage()));
                    SVNErrorManager.error(err, SVNLogType.FSFS);
                }

                File davSandboxDir = new File(path, FSFS.DAV_DIR);
                davSandboxDir.mkdir();
            } else {
                final File currentTxnLockFile = new File(path, "db/" + FSFS.TXN_CURRENT_LOCK_FILE);
                SVNFileUtil.createEmptyFile(currentTxnLockFile);
            }

            // generate and write UUID.
            File uuidFile = new File(path, "db/uuid");
            if (uuid == null || uuid.length() != 36) {
                uuid = SVNUUIDGenerator.generateUUIDString();
            }
            if (fsFormat >= FSFS.MIN_INSTANCE_UUID_FORMAT) {
                String instanceUuid;
                do {
                    instanceUuid = SVNUUIDGenerator.generateUUIDString();
                } while(instanceUuid.equals(uuid));
                uuid += '\n' + instanceUuid + '\n';
            } else {
                uuid += '\n';
            }

            try {
                uuidOS = SVNFileUtil.openFileForWriting(uuidFile);
                uuidOS.write(uuid.getBytes("US-ASCII"));
            } catch (IOException e) {
                SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "Error writing repository UUID to ''{0}''", uuidFile);
                err.setChildErrorMessage(SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e.getLocalizedMessage()));
                SVNErrorManager.error(err, SVNLogType.FSFS);
            }

            long maxFilesPerDir = 0;
            if (fsFormat >= FSFS.LAYOUT_FORMAT_OPTION_MINIMAL_FORMAT) {
                maxFilesPerDir = FSFS.getDefaultMaxFilesPerDirectory();
                File fsFormatFile = new File(path, "db/format");
                try {
                    fsFormatOS = SVNFileUtil.openFileForWriting(fsFormatFile);
                    String format = String.valueOf(fsFormat) + "\n";
                    if (maxFilesPerDir > 0) {
                        File revFileBefore = new File(path, "db/revs/0");
                        File tmpFile = SVNFileUtil.createUniqueFile(new File(path, "db/revs"), "0", "tmp", true);
                        SVNFileUtil.rename(revFileBefore, tmpFile);
                        File shardRevDir = new File(path, "db/revs/0");
                        shardRevDir.mkdirs();
                        File revFileAfter = new File(shardRevDir, "0");
                        SVNFileUtil.rename(tmpFile, revFileAfter);

                        File revPropFileBefore = new File(path, "db/revprops/0");
                        tmpFile = SVNFileUtil.createUniqueFile(new File(path, "db/revprops"), "0", "tmp", true);
                        SVNFileUtil.rename(revPropFileBefore, tmpFile);
                        File shardRevPropDir = new File(path, "db/revprops/0");
                        shardRevPropDir.mkdirs();
                        File revPropFileAfter = new File(shardRevPropDir, "0");
                        SVNFileUtil.rename(tmpFile, revPropFileAfter);

                        format += "layout sharded " + String.valueOf(maxFilesPerDir) + "\n";
                    } else {
                        format += "layout linear\n";
                    }
                    fsFormatOS.write(format.getBytes("US-ASCII"));
                } catch (IOException e) {
                    SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "Error writing fs format to ''{0}''", fsFormatFile);
                    err.setChildErrorMessage(SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e.getLocalizedMessage()));
                    SVNErrorManager.error(err, SVNLogType.FSFS);
                }
            }

            String currentFileLine = fsFormat >= FSFS.MIN_NO_GLOBAL_IDS_FORMAT ? "0\n" : "0 1 1\n";
            File currentFile = new File(path, "db/" + FSFS.CURRENT_FILE);
            currentOS = SVNFileUtil.openFileForWriting(currentFile);
            try {
                currentOS.write(currentFileLine.getBytes("US-ASCII"));
            } catch (IOException e) {
                SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR,
                        "Can not write to ''{0}'' file: {1}",
                        new Object[]{currentFile.getName(), e.getLocalizedMessage()});
                SVNErrorManager.error(err, e, SVNLogType.FSFS);
            }

            if (fsFormat >= FSFS.MIN_PACKED_FORMAT) {
                File minUnpackedFile = new File(path, "db/" + FSFS.MIN_UNPACKED_REV_FILE);
                SVNFileUtil.createEmptyFile(minUnpackedFile);
                minUnpacledOS = SVNFileUtil.openFileForWriting(minUnpackedFile);
                try {
                    minUnpacledOS.write("0\n".getBytes("US-ASCII"));
                } catch (IOException e) {
                    SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR,
                            "Can not write to ''{0}'' file: {1}",
                            new Object[] { minUnpackedFile.getName(), e.getLocalizedMessage() });
                    SVNErrorManager.error(err, e, SVNLogType.FSFS);
                }
            }

            if (fsFormat >= FSFS.MIN_CURRENT_TXN_FORMAT) {
                File txnCurrentFile = new File(path, "db/" + FSFS.TXN_CURRENT_FILE);
                SVNFileUtil.createEmptyFile(txnCurrentFile);
                txnCurrentOS = SVNFileUtil.openFileForWriting(txnCurrentFile);
                try {
                    txnCurrentOS.write("0\n".getBytes("US-ASCII"));
                } catch (IOException e) {
                    SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR,
                            "Can not write to ''{0}'' file: {1}",
                            new Object[] { txnCurrentFile.getName(), e.getLocalizedMessage() });
                    SVNErrorManager.error(err, e, SVNLogType.FSFS);
                }
            }

            if (fsFormat >= FSFS.MIN_PROTOREVS_DIR_FORMAT) {
                File protoRevsDir = new File(path, "db/txn-protorevs");
                protoRevsDir.mkdirs();
            }

            // set creation date.
            File rev0File = new File(path, maxFilesPerDir > 0 ? "db/revprops/0/0" : "db/revprops/0");
            String date = SVNDate.formatDate(new Date(System.currentTimeMillis()), true);
            Map props = new SVNHashMap();
            props.put(SVNRevisionProperty.DATE, date);
            SVNWCProperties.setProperties(SVNProperties.wrap(props), rev0File, null, SVNWCProperties.SVN_HASH_TERMINATOR);

            setSGID(new File(path, FSFS.DB_DIR));

            if (fsFormat >= FSFS.MIN_REP_SHARING_FORMAT) {
                File repCacheFile = new File(path, FSFS.DB_DIR + "/" + FSFS.REP_CACHE_DB);
                FSRepresentationCacheUtil.create(fsFormat, repCacheFile);
            }

            /* Create the revprops directory. */
            if (fsFormat >= FSFS.MIN_PACKED_REVPROP_FORMAT)
              {
                File minUnpackedRevPropFile = new File(path, FSFS.DB_DIR + "/" + FSFS.MIN_UNPACKED_REV);
                SVNFileUtil.writeToFile(minUnpackedRevPropFile, "0\n", "US-ASCII");
                File revPropFile = new File(path, FSFS.DB_DIR + "/" + FSFS.REVISION_PROPERTIES_DIR + "/" + FSFS.REVISION_PROPERTIES_DB);
                final SVNSqlJetDb revPropDb = SVNSqlJetDb.open( revPropFile, SVNSqlJetDb.Mode.RWCreate );
                try{
                    revPropDb.execStatement(SVNWCDbStatements.REVPROP_CREATE_SCHEMA);
                } finally {
                    revPropDb.close();
                }
              }

        } finally {
            SVNFileUtil.closeFile(uuidOS);
            SVNFileUtil.closeFile(reposFormatOS);
            SVNFileUtil.closeFile(fsFormatOS);
            SVNFileUtil.closeFile(txnCurrentOS);
            SVNFileUtil.closeFile(minUnpacledOS);
            SVNFileUtil.closeFile(currentOS);
            SVNFileUtil.deleteFile(jarFile);
        }
        return SVNURL.fromFile(path);
    }

    protected abstract SVNRepository createRepositoryImpl(SVNURL url, ISVNSession session);

    private static void copyToFile(InputStream is, File dstFile) throws SVNException {
        OutputStream os = null;
        byte[] buffer = new byte[16*1024];
        try {
            os = SVNFileUtil.openFileForWriting(dstFile);
            while(true) {
                int r = is.read(buffer);
                if (r < 0) {
                    break;
                } else if (r == 0) {
                    continue;
                }
                os.write(buffer, 0, r);
            }
        } catch (IOException e) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "Can not copy repository template file to ''{0}''", dstFile);
            err.setChildErrorMessage(SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e.getLocalizedMessage()));
            SVNErrorManager.error(err, SVNLogType.NETWORK);
        } finally {
            SVNFileUtil.closeFile(os);
            SVNFileUtil.closeFile(is);
        }
    }

    private static void extract(File srcFile, File dst) throws SVNException {
        JarInputStream jis = null;
        InputStream is = SVNFileUtil.openFileForReading(srcFile, SVNLogType.NETWORK);
        byte[] buffer = new byte[16*1024];

        JarFile jarFile = null;
        try {
            jarFile = new JarFile(srcFile);
            jis = new JarInputStream(is);
            while(true) {
                JarEntry entry = jis.getNextJarEntry();
                if (entry == null) {
                    break;
                }
                String name = entry.getName();
                File entryFile = new File(dst, name);
                if (entry.isDirectory()) {
                    entryFile.mkdirs();
                } else {
                    InputStream fis = null;
                    OutputStream fos = null;
                    try {
                        fis = new BufferedInputStream(jarFile.getInputStream(entry));
                        fos = SVNFileUtil.openFileForWriting(entryFile);
                        while(true) {
                            int r = fis.read(buffer);
                            if (r < 0) {
                                break;
                            } else if (r == 0) {
                                continue;
                            }
                            fos.write(buffer, 0, r);
                        }
                    } finally {
                        SVNFileUtil.closeFile(fos);
                        SVNFileUtil.closeFile(fis);
                    }
                }
                jis.closeEntry();
            }
        } catch (IOException e) {
            SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "Can not extract repository files from ''{0}'' to ''{1}''",
                    new Object[] {srcFile, dst});
            err.setChildErrorMessage(SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e.getLocalizedMessage()));
            SVNErrorManager.error(err, SVNLogType.NETWORK);
        } finally {
            SVNFileUtil.closeFile(jis);
            SVNFileUtil.closeFile(is);
            if (jarFile != null) {
                try {
                    jarFile.close();
                } catch (IOException e) {
                }
            }
        }
    }

    private static void translateFiles(File directory) throws SVNException {
        File[] children = SVNFileListUtil.listFiles(directory);
        byte[] eol = new byte[] {'\n'};
        for (int i = 0; children != null && i < children.length; i++) {
            File child = children[i];
            File tmpChild = null;
            if (child.isFile()) {
                try {
                    tmpChild = SVNFileUtil.createUniqueFile(directory, ".repos", ".tmp", true);
                    SVNTranslator.translate(child, tmpChild, null, eol, null, false, true);
                    SVNFileUtil.deleteFile(child);
                    SVNFileUtil.rename(tmpChild, child);
                } finally {
                    SVNFileUtil.deleteFile(tmpChild);
                }
            }
        }
    }

    private static void setSGID(File dbDir) {
        SVNFileUtil.setSGID(dbDir);
        File[] dirContents = dbDir.listFiles();
        for(int i = 0; dirContents != null && i < dirContents.length; i++) {
            File child = dirContents[i];
            if (child.isDirectory()) {
                setSGID(child);
            }
        }
    }
}
