<commit_before>/*
 * This library is part of OpenCms -
 * the Open Source Content Management System
 *
 * Copyright (c) Alkacon Software GmbH (http://www.alkacon.com)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * For further information about Alkacon Software, please see the
 * company website: http://www.alkacon.com
 *
 * For further information about OpenCms, please see the
 * project website: http://www.opencms.org
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.opencms.workplace.tools.modules;

import org.opencms.ade.configuration.CmsADEManager;
import org.opencms.ade.configuration.formatters.CmsFormatterConfigurationCache;
import org.opencms.configuration.CmsConfigurationCopyResource;
import org.opencms.db.CmsExportPoint;
import org.opencms.file.CmsFile;
import org.opencms.file.CmsObject;
import org.opencms.file.CmsProject;
import org.opencms.file.CmsProperty;
import org.opencms.file.CmsResource;
import org.opencms.file.CmsResourceFilter;
import org.opencms.file.CmsVfsResourceNotFoundException;
import org.opencms.file.types.A_CmsResourceType;
import org.opencms.file.types.CmsResourceTypeFolder;
import org.opencms.file.types.CmsResourceTypeUnknown;
import org.opencms.file.types.CmsResourceTypeXmlContainerPage;
import org.opencms.file.types.I_CmsResourceType;
import org.opencms.i18n.CmsLocaleManager;
import org.opencms.i18n.CmsVfsBundleManager;
import org.opencms.loader.CmsLoaderException;
import org.opencms.lock.CmsLock;
import org.opencms.main.CmsException;
import org.opencms.main.CmsLog;
import org.opencms.main.I_CmsEventListener;
import org.opencms.main.OpenCms;
import org.opencms.module.CmsModule;
import org.opencms.module.Messages;
import org.opencms.report.A_CmsReportThread;
import org.opencms.report.I_CmsReport;
import org.opencms.util.CmsStringUtil;
import org.opencms.util.CmsUUID;
import org.opencms.workplace.CmsWorkplace;
import org.opencms.workplace.explorer.CmsExplorerTypeSettings;
import org.opencms.xml.CmsXmlException;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.logging.Log;

import com.google.common.base.Function;
import com.google.common.base.Functions;

/**
 * The report thread to clone a module.<p> 
 */
public class CmsCloneModuleThread extends A_CmsReportThread {

    /** 
     * String replacement function.<p>
     */
    class ReplaceAll implements Function<String, String> {

        /** Regex to match. */
        private String m_from;

        /** Replacement for the regex. */
        private String m_to;

        /**
         * Creates a new instance.<p>
         * 
         * @param from the regex to match  
         * @param to the replacement 
         */
        public ReplaceAll(String from, String to) {

            m_from = from;
            m_to = to;
        }

        /**
         * @see com.google.common.base.Function#apply(java.lang.Object)
         */
        public String apply(String input) {

            return input.replaceAll(m_from, m_to);
        }

    }

    /** The icon path. */
    public static final String ICON_PATH = CmsWorkplace.VFS_PATH_RESOURCES + CmsWorkplace.RES_PATH_FILETYPES;

    /** Classes folder within the module. */
    public static final String PATH_CLASSES = "classes/";

    /** The log object for this class. */
    private static final Log LOG = CmsLog.getLog(CmsCloneModuleThread.class);

    /** The clone module information. */
    private CmsCloneModuleInfo m_cloneInfo;

    /**
     * Constructor.<p>
     * 
     * @param cms the cms context
     * @param cloneInfo the clone module information
     */
    protected CmsCloneModuleThread(CmsObject cms, CmsCloneModuleInfo cloneInfo) {

        super(cms, cloneInfo.getName());
        m_cloneInfo = cloneInfo;
        initHtmlReport(cms.getRequestContext().getLocale());
    }

    /**
     * Returns a list of all module names.<p>
     * 
     * @return a list of all module names
     */
    public List<String> getAllModuleNames() {

        List<String> sortedModuleNames = new ArrayList<String>(OpenCms.getModuleManager().getModuleNames());
        java.util.Collections.sort(sortedModuleNames);
        return sortedModuleNames;
    }

    /**
     * @see org.opencms.report.A_CmsReportThread#getReportUpdate()
     */
    @Override
    public String getReportUpdate() {

        return getReport().getReportUpdate();
    }

    /**
     * @see java.lang.Thread#run()
     */
    @Override
    public void run() {

        CmsModule sourceModule = OpenCms.getModuleManager().getModule(m_cloneInfo.getSourceModuleName());

        // clone the module object
        CmsModule targetModule = (CmsModule)sourceModule.clone();
        targetModule.setName(m_cloneInfo.getName());
        targetModule.setNiceName(m_cloneInfo.getNiceName());
        targetModule.setDescription(m_cloneInfo.getDescription());
        targetModule.setAuthorEmail(m_cloneInfo.getAuthorEmail());
        targetModule.setAuthorName(m_cloneInfo.getAuthorName());
        targetModule.setGroup(m_cloneInfo.getGroup());
        targetModule.setActionClass(m_cloneInfo.getActionClass());

        CmsObject cms = getCms();
        CmsProject currentProject = cms.getRequestContext().getCurrentProject();
        try {
            CmsProject workProject = cms.createProject(
                "Clone_module_work_project",
                "Clone modulee work project",
                OpenCms.getDefaultUsers().getGroupAdministrators(),
                OpenCms.getDefaultUsers().getGroupAdministrators(),
                CmsProject.PROJECT_TYPE_TEMPORARY);
            cms.getRequestContext().setCurrentProject(workProject);

            // store the module paths
            String sourceModulePath = CmsWorkplace.VFS_PATH_MODULES + sourceModule.getName() + "/";
            String targetModulePath = CmsWorkplace.VFS_PATH_MODULES + targetModule.getName() + "/";

            // store the package name as path part
            String sourcePathPart = sourceModule.getName().replaceAll("\\.", "/");
            String targetPathPart = targetModule.getName().replaceAll("\\.", "/");

            // store the classes folder paths
            String sourceClassesPath = targetModulePath + PATH_CLASSES + sourcePathPart + "/";
            String targetClassesPath = targetModulePath + PATH_CLASSES + targetPathPart + "/";

            // copy the resources
            cms.copyResource(sourceModulePath, targetModulePath);

            // check if we have to create the classes folder
            if (cms.existsResource(sourceClassesPath)) {
                // in the source module a classes folder was defined,
                // now create all sub-folders for the package structure in the new module folder
                createTargetClassesFolder(targetModule, sourceClassesPath, targetModulePath + PATH_CLASSES);
                // delete the origin classes folder
                deleteSourceClassesFolder(targetModulePath, sourcePathPart, targetPathPart);
            }

            // TODO: clone module dependencies

            // adjust the export points
            cloneExportPoints(sourceModule, targetModule, sourcePathPart, targetPathPart);

            // adjust the resource type names and IDs
            Map<String, String> descKeys = new HashMap<String, String>();
            Map<I_CmsResourceType, I_CmsResourceType> resTypeMap = cloneResourceTypes(
                sourceModule,
                targetModule,
                sourcePathPart,
                targetPathPart,
                descKeys);

            // adjust the explorer type names and store referred icons and message keys
            Map<String, String> iconPaths = new HashMap<String, String>();
            cloneExplorerTypes(targetModule, iconPaths, descKeys);

            // rename the icon file names
            cloneExplorerTypeIcons(iconPaths);

            // adjust the module resources
            adjustModuleResources(sourceModule, targetModule, sourcePathPart, targetPathPart, iconPaths);

            // search and replace the localization keys
            if (getCms().existsResource(targetClassesPath)) {
                List<CmsResource> props = cms.readResources(targetClassesPath, CmsResourceFilter.DEFAULT_FILES);
                replacesMessages(descKeys, props);
            }

            int type = OpenCms.getResourceManager().getResourceType(CmsVfsBundleManager.TYPE_XML_BUNDLE).getTypeId();
            CmsResourceFilter filter = CmsResourceFilter.requireType(type);
            List<CmsResource> resources = cms.readResources(targetModulePath, filter);
            replacesMessages(descKeys, resources);
            renameXmlVfsBundles(resources, targetModule, sourceModule.getName());

            List<CmsResource> allModuleResources = cms.readResources(targetModulePath, CmsResourceFilter.ALL);
            replacePath(sourceModulePath, targetModulePath, allModuleResources);

            // search and replace paths
            replaceModuleName();

            // replace formatter paths
            if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_cloneInfo.getFormatterTargetModule())
                && !targetModule.getResourceTypes().isEmpty()) {
                replaceFormatterPaths(targetModule);
            }

            adjustConfigs(targetModule, resTypeMap);

            // now unlock and publish the project
            getReport().println(
                Messages.get().container(Messages.RPT_PUBLISH_PROJECT_BEGIN_0),
                I_CmsReport.FORMAT_HEADLINE);
            cms.unlockProject(workProject.getUuid());
            OpenCms.getPublishManager().publishProject(cms, getReport());
            OpenCms.getPublishManager().waitWhileRunning();

            getReport().println(
                Messages.get().container(Messages.RPT_PUBLISH_PROJECT_END_0),
                I_CmsReport.FORMAT_HEADLINE);

            //  add the imported module to the module manager
            OpenCms.getModuleManager().addModule(cms, targetModule);

            // reinitialize the resource manager with additional module resource types if necessary
            if (targetModule.getResourceTypes() != Collections.EMPTY_LIST) {
                OpenCms.getResourceManager().initialize(cms);
            }
            // reinitialize the workplace manager with additional module explorer types if necessary
            if (targetModule.getExplorerTypes() != Collections.EMPTY_LIST) {
                OpenCms.getWorkplaceManager().addExplorerTypeSettings(targetModule);
            }

            // re-initialize the workplace
            OpenCms.getWorkplaceManager().initialize(cms);
            // fire "clear caches" event to reload all cached resource bundles
            OpenCms.fireCmsEvent(I_CmsEventListener.EVENT_CLEAR_CACHES, new HashMap<String, Object>());

            // following changes will not be published right now, switch back to previous project
            cms.getRequestContext().setCurrentProject(currentProject);

            // change resource types and schema locations
            if (isTrue(m_cloneInfo.getChangeResourceTypes())) {
                changeResourceTypes(resTypeMap);
            }
            // adjust container pages
            CmsObject cloneCms = OpenCms.initCmsObject(cms);
            if (isTrue(m_cloneInfo.getApplyChangesEverywhere())) {
                cloneCms.getRequestContext().setSiteRoot("/");
            }

            if (m_cloneInfo.isRewriteContainerPages()) {
                CmsResourceFilter f = CmsResourceFilter.requireType(CmsResourceTypeXmlContainerPage.getContainerPageTypeId());
                List<CmsResource> allContainerPages = cloneCms.readResources("/", f);
                replacePath(sourceModulePath, targetModulePath, allContainerPages);
            }
        } catch (Throwable e) {
            LOG.error(e.getLocalizedMessage(), e);
            getReport().addError(e);
        } finally {
            cms.getRequestContext().setCurrentProject(currentProject);
        }
    }

    /**
     * Returns <code>true</code> if the module has been created successful.<p>
     * 
     * @return <code>true</code> if the module has been created successful
     */
    public boolean success() {

        return OpenCms.getModuleManager().getModule(m_cloneInfo.getName()) != null;
    }

    /**
     * Adjusts the module configuration file and the formatter configurations.<p>
     * 
     * @param targetModule the target module
     * @param resTypeMap the resource type mapping
     * 
     * @throws CmsException if something goes wrong
     * @throws UnsupportedEncodingException if the file content could not be read with the determined encoding
     */
    private void adjustConfigs(CmsModule targetModule, Map<I_CmsResourceType, I_CmsResourceType> resTypeMap)
    throws CmsException, UnsupportedEncodingException {

        String modPath = CmsWorkplace.VFS_PATH_MODULES + targetModule.getName() + "/";
        CmsObject cms = getCms();
        if (((m_cloneInfo.getSourceNamePrefix() != null) && (m_cloneInfo.getTargetNamePrefix() != null))
            || !m_cloneInfo.getSourceNamePrefix().equals(m_cloneInfo.getTargetNamePrefix())) {
            // replace resource type names in formatter configurations
            List<CmsResource> resources = cms.readResources(
                modPath,
                CmsResourceFilter.requireType(OpenCms.getResourceManager().getResourceType(
                    CmsFormatterConfigurationCache.TYPE_FORMATTER_CONFIG).getTypeId()));
            String source = "<Type><!\\[CDATA\\[" + m_cloneInfo.getSourceNamePrefix();
            String target = "<Type><!\\[CDATA\\[" + m_cloneInfo.getTargetNamePrefix();
            Function<String, String> replaceType = new ReplaceAll(source, target);

            for (CmsResource resource : resources) {
                transformResource(resource, replaceType);
            }
            resources.clear();
        }

        // replace resource type names in module configuration
        try {
            CmsResource config = cms.readResource(
                modPath + CmsADEManager.CONFIG_FILE_NAME,
                CmsResourceFilter.requireType(OpenCms.getResourceManager().getResourceType(
                    CmsADEManager.MODULE_CONFIG_TYPE).getTypeId()));
            Function<String, String> substitution = Functions.identity();
            // compose the substitution functions from simple substitution functions for each type 
            for (Map.Entry<I_CmsResourceType, I_CmsResourceType> mapping : resTypeMap.entrySet()) {
                substitution = Functions.compose(new ReplaceAll(
                    mapping.getKey().getTypeName(),
                    mapping.getValue().getTypeName()), substitution);
            }
            // Either replace prefix in or prepend it to the folder name value 
            Function<String, String> replaceFolderName = new ReplaceAll("(<Folder>[ \n]*<Name><!\\[CDATA\\[)("
                + m_cloneInfo.getSourceNamePrefix()
                + ")?", "$1" + m_cloneInfo.getTargetNamePrefix());
            substitution = Functions.compose(replaceFolderName, substitution);
            transformResource(config, substitution);
        } catch (CmsVfsResourceNotFoundException e) {
            LOG.info(e.getLocalizedMessage(), e);
        }
    }

    /**
     * Adjusts the paths of the module resources from the source path to the target path.<p>
     * 
     * @param sourceModule the source module
     * @param targetModule the target module
     * @param sourcePathPart the path part of the source module
     * @param targetPathPart the path part of the target module
     * @param iconPaths the path where resource type icons are located
     */
    private void adjustModuleResources(
        CmsModule sourceModule,
        CmsModule targetModule,
        String sourcePathPart,
        String targetPathPart,
        Map<String, String> iconPaths) {

        List<String> newTargetResources = new ArrayList<String>();
        List<String> targetResources = targetModule.getResources();
        for (String modRes : targetResources) {
            String nIcon = iconPaths.get(modRes.substring(modRes.lastIndexOf('/') + 1));
            if (nIcon != null) {
                // the referenced resource is an resource type icon, add the new icon path
                newTargetResources.add(ICON_PATH + nIcon);
            } else if (modRes.contains(sourceModule.getName())) {
                // there is the name in it
                newTargetResources.add(modRes.replaceAll(sourceModule.getName(), targetModule.getName()));
            } else if (modRes.contains(sourcePathPart)) {
                // there is a path in it
                newTargetResources.add(modRes.replaceAll(sourcePathPart, targetPathPart));
            } else {
                // there is whether the path nor the name in it
                newTargetResources.add(modRes);
            }
        }
        targetModule.setResources(newTargetResources);
    }

    /**
     * Changes the resource types and the schema locations of existing content.<p>
     * 
     * @param resTypeMap a map containing the source types as keys and the target types as values
     * 
     * @throws CmsException if something goes wrong
     * @throws UnsupportedEncodingException if the file content could not be read with the determined encoding
     */
    private void changeResourceTypes(Map<I_CmsResourceType, I_CmsResourceType> resTypeMap)
    throws CmsException, UnsupportedEncodingException {

        CmsObject cms = getCms();
        CmsObject cloneCms = OpenCms.initCmsObject(cms);

        if (isTrue(m_cloneInfo.getApplyChangesEverywhere())) {
            cloneCms.getRequestContext().setSiteRoot("/");
        }

        for (Map.Entry<I_CmsResourceType, I_CmsResourceType> mapping : resTypeMap.entrySet()) {
            CmsResourceFilter filter = CmsResourceFilter.requireType(mapping.getKey().getTypeId());
            List<CmsResource> resources = cloneCms.readResources("/", filter);
            String sourceSchemaPath = mapping.getKey().getConfiguration().get("schema");
            String targetSchemaPath = mapping.getValue().getConfiguration().get("schema");
            for (CmsResource res : resources) {
                if (lockResource(cms, res)) {
                    CmsFile file = cms.readFile(res);
                    String encoding = CmsLocaleManager.getResourceEncoding(cms, file);
                    String content = new String(file.getContents(), encoding);
                    content = content.replaceAll(sourceSchemaPath, targetSchemaPath);
                    file.setContents(content.getBytes(encoding));
                    try {
                        cms.writeFile(file);
                    } catch (CmsXmlException e) {
                        LOG.error(e.getMessage(), e);
                    }
                    res.setType(mapping.getValue().getTypeId());
                    cms.writeResource(res);
                }
            }
        }
    }

    /**
     * Copies the explorer type icons.<p>
     * 
     * @param iconPaths the path to the location where the icons are located
     * 
     * @throws CmsException if something goes wrong
     */
    private void cloneExplorerTypeIcons(Map<String, String> iconPaths) throws CmsException {

        for (Map.Entry<String, String> entry : iconPaths.entrySet()) {
            String source = ICON_PATH + entry.getKey();
            String target = ICON_PATH + entry.getValue();
            if (!getCms().existsResource(target)) {
                getCms().copyResource(source, target);
            }
        }
    }

    /**
     * Copies the explorer type definitions.<p>
     * 
     * @param targetModule the target module
     * @param iconPaths the path to the location where the icons are located
     * @param descKeys a map that contains a mapping of the explorer type definitions messages
     */
    private void cloneExplorerTypes(CmsModule targetModule, Map<String, String> iconPaths, Map<String, String> descKeys) {

        List<CmsExplorerTypeSettings> targetExplorerTypes = targetModule.getExplorerTypes();
        for (CmsExplorerTypeSettings expSetting : targetExplorerTypes) {
            descKeys.put(
                expSetting.getKey(),
                expSetting.getKey().replaceFirst(m_cloneInfo.getSourceNamePrefix(), m_cloneInfo.getTargetNamePrefix()));
            String newIcon = expSetting.getIcon().replaceFirst(
                m_cloneInfo.getSourceNamePrefix(),
                m_cloneInfo.getTargetNamePrefix());
            String newBigIcon = expSetting.getBigIconIfAvailable().replaceFirst(
                m_cloneInfo.getSourceNamePrefix(),
                m_cloneInfo.getTargetNamePrefix());
            iconPaths.put(expSetting.getIcon(), newIcon);
            iconPaths.put(expSetting.getBigIconIfAvailable(), newBigIcon);
            expSetting.setName(expSetting.getName().replaceFirst(
                m_cloneInfo.getSourceNamePrefix(),
                m_cloneInfo.getTargetNamePrefix()));
            String newResourcePage = expSetting.getNewResourcePage();
            if (newResourcePage != null) {
                expSetting.setNewResourcePage(newResourcePage.replaceFirst(
                    m_cloneInfo.getSourceNamePrefix(),
                    m_cloneInfo.getTargetNamePrefix()));
            }
            expSetting.setKey(expSetting.getKey().replaceFirst(
                m_cloneInfo.getSourceNamePrefix(),
                m_cloneInfo.getTargetNamePrefix()));
            expSetting.setIcon(expSetting.getIcon().replaceFirst(
                m_cloneInfo.getSourceNamePrefix(),
                m_cloneInfo.getTargetNamePrefix()));
            expSetting.setBigIcon(expSetting.getBigIconIfAvailable().replaceFirst(
                m_cloneInfo.getSourceNamePrefix(),
                m_cloneInfo.getTargetNamePrefix()));
            expSetting.setNewResourceUri(expSetting.getNewResourceUri().replaceFirst(
                m_cloneInfo.getSourceNamePrefix(),
                m_cloneInfo.getTargetNamePrefix()));
            expSetting.setInfo(expSetting.getInfo().replaceFirst(
                m_cloneInfo.getSourceNamePrefix(),
                m_cloneInfo.getTargetNamePrefix()));
        }
    }

    /**
     * Clones the export points of the module and adjusts its paths.<p>
     * 
     * @param sourceModule the source module
     * @param targetModule the target module
     * @param sourcePathPart the source path part
     * @param targetPathPart the target path part
     */
    private void cloneExportPoints(
        CmsModule sourceModule,
        CmsModule targetModule,
        String sourcePathPart,
        String targetPathPart) {

        for (CmsExportPoint exp : targetModule.getExportPoints()) {
            if (exp.getUri().contains(sourceModule.getName())) {
                exp.setUri(exp.getUri().replaceAll(sourceModule.getName(), targetModule.getName()));
            }
            if (exp.getUri().contains(sourcePathPart)) {
                exp.setUri(exp.getUri().replaceAll(sourcePathPart, targetPathPart));
            }
        }
    }

    /**
     * Clones/copies the resource types.<p>
     * 
     * @param sourceModule the source module
     * @param targetModule the target module
     * @param sourcePathPart the source path part
     * @param targetPathPart the target path part
     * @param keys the map where to put in the messages of the resource type
     * 
     * @return a map with source resource types as key and the taregt resource types as value
     */
    private Map<I_CmsResourceType, I_CmsResourceType> cloneResourceTypes(
        CmsModule sourceModule,
        CmsModule targetModule,
        String sourcePathPart,
        String targetPathPart,
        Map<String, String> keys) {

        Map<I_CmsResourceType, I_CmsResourceType> resourceTypeMapping = new HashMap<I_CmsResourceType, I_CmsResourceType>();

        List<I_CmsResourceType> targetResourceTypes = new ArrayList<I_CmsResourceType>();
        for (I_CmsResourceType sourceResType : targetModule.getResourceTypes()) {

            // get the class name attribute
            String className = sourceResType.getClassName();
            // create the class instance
            I_CmsResourceType targetResType;
            try {
                if (className != null) {
                    className = className.trim();
                }

                int newId = -1;
                boolean exists = true;
                do {
                    newId = new Random().nextInt((99999)) + 10000;
                    try {
                        OpenCms.getResourceManager().getResourceType(newId);
                    } catch (CmsLoaderException e) {
                        exists = false;
                    }
                } while (exists);

                targetResType = (I_CmsResourceType)Class.forName(className).newInstance();

                for (String mapping : sourceResType.getConfiguredMappings()) {
                    targetResType.addMappingType(mapping);
                }

                targetResType.setAdjustLinksFolder(sourceResType.getAdjustLinksFolder());

                if (targetResType instanceof A_CmsResourceType) {
                    A_CmsResourceType concreteTargetResType = (A_CmsResourceType)targetResType;
                    for (CmsProperty prop : sourceResType.getConfiguredDefaultProperties()) {
                        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(prop.getValue())) {
                            prop.setStructureValue(prop.getStructureValue().replaceAll(
                                sourceModule.getName(),
                                targetModule.getName()).replaceAll(sourcePathPart, targetPathPart));
                            prop.setResourceValue(prop.getResourceValue().replaceAll(
                                sourceModule.getName(),
                                targetModule.getName()).replaceAll(sourcePathPart, targetPathPart));
                        }
                        concreteTargetResType.addDefaultProperty(prop);
                    }
                    for (CmsConfigurationCopyResource conres : sourceResType.getConfiguredCopyResources()) {
                        concreteTargetResType.addCopyResource(
                            conres.getSource(),
                            conres.getTarget(),
                            conres.getTypeString());
                    }
                }

                for (Map.Entry<String, String> entry : sourceResType.getConfiguration().entrySet()) {
                    targetResType.addConfigurationParameter(
                        entry.getKey(),
                        entry.getValue().replaceAll(sourceModule.getName(), targetModule.getName()));
                }

                targetResType.setAdditionalModuleResourceType(true);
                targetResType.initConfiguration(
                    sourceResType.getTypeName().replaceFirst(
                        m_cloneInfo.getSourceNamePrefix(),
                        m_cloneInfo.getTargetNamePrefix()),
                    newId + "",
                    sourceResType.getClassName());

                keys.put(sourceResType.getTypeName(), targetResType.getTypeName());
                targetResourceTypes.add(targetResType);

                resourceTypeMapping.put(sourceResType, targetResType);
<commit_msg>Improved prefix replacement.<commit_after>/*
 * This library is part of OpenCms -
 * the Open Source Content Management System
 *
 * Copyright (c) Alkacon Software GmbH (http://www.alkacon.com)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * For further information about Alkacon Software, please see the
 * company website: http://www.alkacon.com
 *
 * For further information about OpenCms, please see the
 * project website: http://www.opencms.org
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.opencms.workplace.tools.modules;

import org.opencms.ade.configuration.CmsADEManager;
import org.opencms.ade.configuration.formatters.CmsFormatterConfigurationCache;
import org.opencms.configuration.CmsConfigurationCopyResource;
import org.opencms.db.CmsExportPoint;
import org.opencms.file.CmsFile;
import org.opencms.file.CmsObject;
import org.opencms.file.CmsProject;
import org.opencms.file.CmsProperty;
import org.opencms.file.CmsResource;
import org.opencms.file.CmsResourceFilter;
import org.opencms.file.CmsVfsResourceNotFoundException;
import org.opencms.file.types.A_CmsResourceType;
import org.opencms.file.types.CmsResourceTypeFolder;
import org.opencms.file.types.CmsResourceTypeUnknown;
import org.opencms.file.types.CmsResourceTypeXmlContainerPage;
import org.opencms.file.types.I_CmsResourceType;
import org.opencms.i18n.CmsLocaleManager;
import org.opencms.i18n.CmsVfsBundleManager;
import org.opencms.loader.CmsLoaderException;
import org.opencms.lock.CmsLock;
import org.opencms.main.CmsException;
import org.opencms.main.CmsLog;
import org.opencms.main.I_CmsEventListener;
import org.opencms.main.OpenCms;
import org.opencms.module.CmsModule;
import org.opencms.module.Messages;
import org.opencms.report.A_CmsReportThread;
import org.opencms.report.I_CmsReport;
import org.opencms.util.CmsStringUtil;
import org.opencms.util.CmsUUID;
import org.opencms.workplace.CmsWorkplace;
import org.opencms.workplace.explorer.CmsExplorerTypeSettings;
import org.opencms.xml.CmsXmlException;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.logging.Log;

import com.google.common.base.Function;
import com.google.common.base.Functions;

/**
 * The report thread to clone a module.<p> 
 */
public class CmsCloneModuleThread extends A_CmsReportThread {

    /** 
     * String replacement function.<p>
     */
    class ReplaceAll implements Function<String, String> {

        /** Regex to match. */
        private String m_from;

        /** Replacement for the regex. */
        private String m_to;

        /**
         * Creates a new instance.<p>
         * 
         * @param from the regex to match  
         * @param to the replacement 
         */
        public ReplaceAll(String from, String to) {

            m_from = from;
            m_to = to;
        }

        /**
         * @see com.google.common.base.Function#apply(java.lang.Object)
         */
        public String apply(String input) {

            return input.replaceAll(m_from, m_to);
        }

    }

    /** The icon path. */
    public static final String ICON_PATH = CmsWorkplace.VFS_PATH_RESOURCES + CmsWorkplace.RES_PATH_FILETYPES;

    /** Classes folder within the module. */
    public static final String PATH_CLASSES = "classes/";

    /** The log object for this class. */
    private static final Log LOG = CmsLog.getLog(CmsCloneModuleThread.class);

    /** The clone module information. */
    private CmsCloneModuleInfo m_cloneInfo;

    /**
     * Constructor.<p>
     * 
     * @param cms the cms context
     * @param cloneInfo the clone module information
     */
    protected CmsCloneModuleThread(CmsObject cms, CmsCloneModuleInfo cloneInfo) {

        super(cms, cloneInfo.getName());
        m_cloneInfo = cloneInfo;
        initHtmlReport(cms.getRequestContext().getLocale());
    }

    /**
     * Returns a list of all module names.<p>
     * 
     * @return a list of all module names
     */
    public List<String> getAllModuleNames() {

        List<String> sortedModuleNames = new ArrayList<String>(OpenCms.getModuleManager().getModuleNames());
        java.util.Collections.sort(sortedModuleNames);
        return sortedModuleNames;
    }

    /**
     * @see org.opencms.report.A_CmsReportThread#getReportUpdate()
     */
    @Override
    public String getReportUpdate() {

        return getReport().getReportUpdate();
    }

    /**
     * @see java.lang.Thread#run()
     */
    @Override
    public void run() {

        CmsModule sourceModule = OpenCms.getModuleManager().getModule(m_cloneInfo.getSourceModuleName());

        // clone the module object
        CmsModule targetModule = (CmsModule)sourceModule.clone();
        targetModule.setName(m_cloneInfo.getName());
        targetModule.setNiceName(m_cloneInfo.getNiceName());
        targetModule.setDescription(m_cloneInfo.getDescription());
        targetModule.setAuthorEmail(m_cloneInfo.getAuthorEmail());
        targetModule.setAuthorName(m_cloneInfo.getAuthorName());
        targetModule.setGroup(m_cloneInfo.getGroup());
        targetModule.setActionClass(m_cloneInfo.getActionClass());

        CmsObject cms = getCms();
        CmsProject currentProject = cms.getRequestContext().getCurrentProject();
        try {
            CmsProject workProject = cms.createProject(
                "Clone_module_work_project",
                "Clone modulee work project",
                OpenCms.getDefaultUsers().getGroupAdministrators(),
                OpenCms.getDefaultUsers().getGroupAdministrators(),
                CmsProject.PROJECT_TYPE_TEMPORARY);
            cms.getRequestContext().setCurrentProject(workProject);

            // store the module paths
            String sourceModulePath = CmsWorkplace.VFS_PATH_MODULES + sourceModule.getName() + "/";
            String targetModulePath = CmsWorkplace.VFS_PATH_MODULES + targetModule.getName() + "/";

            // store the package name as path part
            String sourcePathPart = sourceModule.getName().replaceAll("\\.", "/");
            String targetPathPart = targetModule.getName().replaceAll("\\.", "/");

            // store the classes folder paths
            String sourceClassesPath = targetModulePath + PATH_CLASSES + sourcePathPart + "/";
            String targetClassesPath = targetModulePath + PATH_CLASSES + targetPathPart + "/";

            // copy the resources
            cms.copyResource(sourceModulePath, targetModulePath);

            // check if we have to create the classes folder
            if (cms.existsResource(sourceClassesPath)) {
                // in the source module a classes folder was defined,
                // now create all sub-folders for the package structure in the new module folder
                createTargetClassesFolder(targetModule, sourceClassesPath, targetModulePath + PATH_CLASSES);
                // delete the origin classes folder
                deleteSourceClassesFolder(targetModulePath, sourcePathPart, targetPathPart);
            }

            // TODO: clone module dependencies

            // adjust the export points
            cloneExportPoints(sourceModule, targetModule, sourcePathPart, targetPathPart);

            // adjust the resource type names and IDs
            Map<String, String> descKeys = new HashMap<String, String>();
            Map<I_CmsResourceType, I_CmsResourceType> resTypeMap = cloneResourceTypes(
                sourceModule,
                targetModule,
                sourcePathPart,
                targetPathPart,
                descKeys);

            // adjust the explorer type names and store referred icons and message keys
            Map<String, String> iconPaths = new HashMap<String, String>();
            cloneExplorerTypes(targetModule, iconPaths, descKeys);

            // rename the icon file names
            cloneExplorerTypeIcons(iconPaths);

            // adjust the module resources
            adjustModuleResources(sourceModule, targetModule, sourcePathPart, targetPathPart, iconPaths);

            // search and replace the localization keys
            if (getCms().existsResource(targetClassesPath)) {
                List<CmsResource> props = cms.readResources(targetClassesPath, CmsResourceFilter.DEFAULT_FILES);
                replacesMessages(descKeys, props);
            }

            int type = OpenCms.getResourceManager().getResourceType(CmsVfsBundleManager.TYPE_XML_BUNDLE).getTypeId();
            CmsResourceFilter filter = CmsResourceFilter.requireType(type);
            List<CmsResource> resources = cms.readResources(targetModulePath, filter);
            replacesMessages(descKeys, resources);
            renameXmlVfsBundles(resources, targetModule, sourceModule.getName());

            List<CmsResource> allModuleResources = cms.readResources(targetModulePath, CmsResourceFilter.ALL);
            replacePath(sourceModulePath, targetModulePath, allModuleResources);

            // search and replace paths
            replaceModuleName();

            // replace formatter paths
            if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_cloneInfo.getFormatterTargetModule())
                && !targetModule.getResourceTypes().isEmpty()) {
                replaceFormatterPaths(targetModule);
            }

            adjustConfigs(targetModule, resTypeMap);

            // now unlock and publish the project
            getReport().println(
                Messages.get().container(Messages.RPT_PUBLISH_PROJECT_BEGIN_0),
                I_CmsReport.FORMAT_HEADLINE);
            cms.unlockProject(workProject.getUuid());
            OpenCms.getPublishManager().publishProject(cms, getReport());
            OpenCms.getPublishManager().waitWhileRunning();

            getReport().println(
                Messages.get().container(Messages.RPT_PUBLISH_PROJECT_END_0),
                I_CmsReport.FORMAT_HEADLINE);

            //  add the imported module to the module manager
            OpenCms.getModuleManager().addModule(cms, targetModule);

            // reinitialize the resource manager with additional module resource types if necessary
            if (targetModule.getResourceTypes() != Collections.EMPTY_LIST) {
                OpenCms.getResourceManager().initialize(cms);
            }
            // reinitialize the workplace manager with additional module explorer types if necessary
            if (targetModule.getExplorerTypes() != Collections.EMPTY_LIST) {
                OpenCms.getWorkplaceManager().addExplorerTypeSettings(targetModule);
            }

            // re-initialize the workplace
            OpenCms.getWorkplaceManager().initialize(cms);
            // fire "clear caches" event to reload all cached resource bundles
            OpenCms.fireCmsEvent(I_CmsEventListener.EVENT_CLEAR_CACHES, new HashMap<String, Object>());

            // following changes will not be published right now, switch back to previous project
            cms.getRequestContext().setCurrentProject(currentProject);

            // change resource types and schema locations
            if (isTrue(m_cloneInfo.getChangeResourceTypes())) {
                changeResourceTypes(resTypeMap);
            }
            // adjust container pages
            CmsObject cloneCms = OpenCms.initCmsObject(cms);
            if (isTrue(m_cloneInfo.getApplyChangesEverywhere())) {
                cloneCms.getRequestContext().setSiteRoot("/");
            }

            if (m_cloneInfo.isRewriteContainerPages()) {
                CmsResourceFilter f = CmsResourceFilter.requireType(CmsResourceTypeXmlContainerPage.getContainerPageTypeId());
                List<CmsResource> allContainerPages = cloneCms.readResources("/", f);
                replacePath(sourceModulePath, targetModulePath, allContainerPages);
            }
        } catch (Throwable e) {
            LOG.error(e.getLocalizedMessage(), e);
            getReport().addError(e);
        } finally {
            cms.getRequestContext().setCurrentProject(currentProject);
        }
    }

    /**
     * Returns <code>true</code> if the module has been created successful.<p>
     * 
     * @return <code>true</code> if the module has been created successful
     */
    public boolean success() {

        return OpenCms.getModuleManager().getModule(m_cloneInfo.getName()) != null;
    }

    /**
     * Adjusts the module configuration file and the formatter configurations.<p>
     * 
     * @param targetModule the target module
     * @param resTypeMap the resource type mapping
     * 
     * @throws CmsException if something goes wrong
     * @throws UnsupportedEncodingException if the file content could not be read with the determined encoding
     */
    private void adjustConfigs(CmsModule targetModule, Map<I_CmsResourceType, I_CmsResourceType> resTypeMap)
    throws CmsException, UnsupportedEncodingException {

        String modPath = CmsWorkplace.VFS_PATH_MODULES + targetModule.getName() + "/";
        CmsObject cms = getCms();
        if (((m_cloneInfo.getSourceNamePrefix() != null) && (m_cloneInfo.getTargetNamePrefix() != null))
            || !m_cloneInfo.getSourceNamePrefix().equals(m_cloneInfo.getTargetNamePrefix())) {
            // replace resource type names in formatter configurations
            List<CmsResource> resources = cms.readResources(
                modPath,
                CmsResourceFilter.requireType(OpenCms.getResourceManager().getResourceType(
                    CmsFormatterConfigurationCache.TYPE_FORMATTER_CONFIG).getTypeId()));
            String source = "<Type><!\\[CDATA\\[" + m_cloneInfo.getSourceNamePrefix();
            String target = "<Type><!\\[CDATA\\[" + m_cloneInfo.getTargetNamePrefix();
            Function<String, String> replaceType = new ReplaceAll(source, target);

            for (CmsResource resource : resources) {
                transformResource(resource, replaceType);
            }
            resources.clear();
        }

        // replace resource type names in module configuration
        try {
            CmsResource config = cms.readResource(
                modPath + CmsADEManager.CONFIG_FILE_NAME,
                CmsResourceFilter.requireType(OpenCms.getResourceManager().getResourceType(
                    CmsADEManager.MODULE_CONFIG_TYPE).getTypeId()));
            Function<String, String> substitution = Functions.identity();
            // compose the substitution functions from simple substitution functions for each type 
            for (Map.Entry<I_CmsResourceType, I_CmsResourceType> mapping : resTypeMap.entrySet()) {
                substitution = Functions.compose(new ReplaceAll(
                    mapping.getKey().getTypeName(),
                    mapping.getValue().getTypeName()), substitution);
            }
            // Either replace prefix in or prepend it to the folder name value 
            Function<String, String> replaceFolderName = new ReplaceAll("(<Folder>[ \n]*<Name><!\\[CDATA\\[)("
                + m_cloneInfo.getSourceNamePrefix()
                + ")?", "$1" + m_cloneInfo.getTargetNamePrefix());
            substitution = Functions.compose(replaceFolderName, substitution);
            transformResource(config, substitution);
        } catch (CmsVfsResourceNotFoundException e) {
            LOG.info(e.getLocalizedMessage(), e);
        }
    }

    /**
     * Adjusts the paths of the module resources from the source path to the target path.<p>
     * 
     * @param sourceModule the source module
     * @param targetModule the target module
     * @param sourcePathPart the path part of the source module
     * @param targetPathPart the path part of the target module
     * @param iconPaths the path where resource type icons are located
     */
    private void adjustModuleResources(
        CmsModule sourceModule,
        CmsModule targetModule,
        String sourcePathPart,
        String targetPathPart,
        Map<String, String> iconPaths) {

        List<String> newTargetResources = new ArrayList<String>();
        List<String> targetResources = targetModule.getResources();
        for (String modRes : targetResources) {
            String nIcon = iconPaths.get(modRes.substring(modRes.lastIndexOf('/') + 1));
            if (nIcon != null) {
                // the referenced resource is an resource type icon, add the new icon path
                newTargetResources.add(ICON_PATH + nIcon);
            } else if (modRes.contains(sourceModule.getName())) {
                // there is the name in it
                newTargetResources.add(modRes.replaceAll(sourceModule.getName(), targetModule.getName()));
            } else if (modRes.contains(sourcePathPart)) {
                // there is a path in it
                newTargetResources.add(modRes.replaceAll(sourcePathPart, targetPathPart));
            } else {
                // there is whether the path nor the name in it
                newTargetResources.add(modRes);
            }
        }
        targetModule.setResources(newTargetResources);
    }

    /**
     * Manipulates a string by cutting of a prefix, if present, and adding a new prefix.
     * 
     * @param word the string to be manipulated
     * @param oldPrefix the old prefix that should be replaced
     * @param newPrefix the new prefix that is added
     * @return the manipulated string
     */
    private String alterPrefix(String word, String oldPrefix, String newPrefix) {

        if (word.startsWith(oldPrefix)) {
            word.replaceFirst(oldPrefix, newPrefix);
            return word;
        }
        return (newPrefix + word);
    }

    /**
     * Changes the resource types and the schema locations of existing content.<p>
     * 
     * @param resTypeMap a map containing the source types as keys and the target types as values
     * 
     * @throws CmsException if something goes wrong
     * @throws UnsupportedEncodingException if the file content could not be read with the determined encoding
     */
    private void changeResourceTypes(Map<I_CmsResourceType, I_CmsResourceType> resTypeMap)
    throws CmsException, UnsupportedEncodingException {

        CmsObject cms = getCms();
        CmsObject cloneCms = OpenCms.initCmsObject(cms);

        if (isTrue(m_cloneInfo.getApplyChangesEverywhere())) {
            cloneCms.getRequestContext().setSiteRoot("/");
        }

        for (Map.Entry<I_CmsResourceType, I_CmsResourceType> mapping : resTypeMap.entrySet()) {
            CmsResourceFilter filter = CmsResourceFilter.requireType(mapping.getKey().getTypeId());
            List<CmsResource> resources = cloneCms.readResources("/", filter);
            String sourceSchemaPath = mapping.getKey().getConfiguration().get("schema");
            String targetSchemaPath = mapping.getValue().getConfiguration().get("schema");
            for (CmsResource res : resources) {
                if (lockResource(cms, res)) {
                    CmsFile file = cms.readFile(res);
                    String encoding = CmsLocaleManager.getResourceEncoding(cms, file);
                    String content = new String(file.getContents(), encoding);
                    content = content.replaceAll(sourceSchemaPath, targetSchemaPath);
                    file.setContents(content.getBytes(encoding));
                    try {
                        cms.writeFile(file);
                    } catch (CmsXmlException e) {
                        LOG.error(e.getMessage(), e);
                    }
                    res.setType(mapping.getValue().getTypeId());
                    cms.writeResource(res);
                }
            }
        }
    }

    /**
     * Copies the explorer type icons.<p>
     * 
     * @param iconPaths the path to the location where the icons are located
     * 
     * @throws CmsException if something goes wrong
     */
    private void cloneExplorerTypeIcons(Map<String, String> iconPaths) throws CmsException {

        for (Map.Entry<String, String> entry : iconPaths.entrySet()) {
            String source = ICON_PATH + entry.getKey();
            String target = ICON_PATH + entry.getValue();
            if (!getCms().existsResource(target)) {
                getCms().copyResource(source, target);
            }
        }
    }

    /**
     * Copies the explorer type definitions.<p>
     * 
     * @param targetModule the target module
     * @param iconPaths the path to the location where the icons are located
     * @param descKeys a map that contains a mapping of the explorer type definitions messages
     */
    private void cloneExplorerTypes(CmsModule targetModule, Map<String, String> iconPaths, Map<String, String> descKeys) {

        List<CmsExplorerTypeSettings> targetExplorerTypes = targetModule.getExplorerTypes();
        for (CmsExplorerTypeSettings expSetting : targetExplorerTypes) {
            descKeys.put(
                expSetting.getKey(),
                alterPrefix(expSetting.getKey(), m_cloneInfo.getSourceNamePrefix(), m_cloneInfo.getTargetNamePrefix()));
            String newIcon = alterPrefix(
                expSetting.getIcon(),
                m_cloneInfo.getSourceNamePrefix(),
                m_cloneInfo.getTargetNamePrefix());
            String newBigIcon = alterPrefix(
                expSetting.getBigIconIfAvailable(),
                m_cloneInfo.getSourceNamePrefix(),
                m_cloneInfo.getTargetNamePrefix());
            iconPaths.put(expSetting.getIcon(), newIcon);
            iconPaths.put(expSetting.getBigIconIfAvailable(), newBigIcon);
            expSetting.setName(alterPrefix(
                expSetting.getName(),
                m_cloneInfo.getSourceNamePrefix(),
                m_cloneInfo.getTargetNamePrefix()));
            String newResourcePage = expSetting.getNewResourcePage();
            if (newResourcePage != null) {
                expSetting.setNewResourcePage(alterPrefix(
                    newResourcePage,
                    m_cloneInfo.getSourceNamePrefix(),
                    m_cloneInfo.getTargetNamePrefix()));
            }
            expSetting.setKey(alterPrefix(
                expSetting.getKey(),
                m_cloneInfo.getSourceNamePrefix(),
                m_cloneInfo.getTargetNamePrefix()));
            expSetting.setIcon(alterPrefix(
                expSetting.getIcon(),
                m_cloneInfo.getSourceNamePrefix(),
                m_cloneInfo.getTargetNamePrefix()));
            expSetting.setBigIcon(alterPrefix(
                expSetting.getBigIconIfAvailable(),
                m_cloneInfo.getSourceNamePrefix(),
                m_cloneInfo.getTargetNamePrefix()));
            expSetting.setNewResourceUri(alterPrefix(
                expSetting.getNewResourceUri(),
                m_cloneInfo.getSourceNamePrefix(),
                m_cloneInfo.getTargetNamePrefix()));
            expSetting.setInfo(alterPrefix(
                expSetting.getInfo(),
                m_cloneInfo.getSourceNamePrefix(),
                m_cloneInfo.getTargetNamePrefix()));
        }
    }

    /**
     * Clones the export points of the module and adjusts its paths.<p>
     * 
     * @param sourceModule the source module
     * @param targetModule the target module
     * @param sourcePathPart the source path part
     * @param targetPathPart the target path part
     */
    private void cloneExportPoints(
        CmsModule sourceModule,
        CmsModule targetModule,
        String sourcePathPart,
        String targetPathPart) {

        for (CmsExportPoint exp : targetModule.getExportPoints()) {
            if (exp.getUri().contains(sourceModule.getName())) {
                exp.setUri(exp.getUri().replaceAll(sourceModule.getName(), targetModule.getName()));
            }
            if (exp.getUri().contains(sourcePathPart)) {
                exp.setUri(exp.getUri().replaceAll(sourcePathPart, targetPathPart));
            }
        }
    }

    /**
     * Clones/copies the resource types.<p>
     * 
     * @param sourceModule the source module
     * @param targetModule the target module
     * @param sourcePathPart the source path part
     * @param targetPathPart the target path part
     * @param keys the map where to put in the messages of the resource type
     * 
     * @return a map with source resource types as key and the taregt resource types as value
     */
    private Map<I_CmsResourceType, I_CmsResourceType> cloneResourceTypes(
        CmsModule sourceModule,
        CmsModule targetModule,
        String sourcePathPart,
        String targetPathPart,
        Map<String, String> keys) {

        Map<I_CmsResourceType, I_CmsResourceType> resourceTypeMapping = new HashMap<I_CmsResourceType, I_CmsResourceType>();

        List<I_CmsResourceType> targetResourceTypes = new ArrayList<I_CmsResourceType>();
        for (I_CmsResourceType sourceResType : targetModule.getResourceTypes()) {

            // get the class name attribute
            String className = sourceResType.getClassName();
            // create the class instance
            I_CmsResourceType targetResType;
            try {
                if (className != null) {
                    className = className.trim();
                }

                int newId = -1;
                boolean exists = true;
                do {
                    newId = new Random().nextInt((99999)) + 10000;
                    try {
                        OpenCms.getResourceManager().getResourceType(newId);
                    } catch (CmsLoaderException e) {
                        exists = false;
                    }
                } while (exists);

                targetResType = (I_CmsResourceType)Class.forName(className).newInstance();

                for (String mapping : sourceResType.getConfiguredMappings()) {
                    targetResType.addMappingType(mapping);
                }

                targetResType.setAdjustLinksFolder(sourceResType.getAdjustLinksFolder());

                if (targetResType instanceof A_CmsResourceType) {
                    A_CmsResourceType concreteTargetResType = (A_CmsResourceType)targetResType;
                    for (CmsProperty prop : sourceResType.getConfiguredDefaultProperties()) {
                        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(prop.getValue())) {
                            prop.setStructureValue(prop.getStructureValue().replaceAll(
                                sourceModule.getName(),
                                targetModule.getName()).replaceAll(sourcePathPart, targetPathPart));
                            prop.setResourceValue(prop.getResourceValue().replaceAll(
                                sourceModule.getName(),
                                targetModule.getName()).replaceAll(sourcePathPart, targetPathPart));
                        }
                        concreteTargetResType.addDefaultProperty(prop);
                    }
                    for (CmsConfigurationCopyResource conres : sourceResType.getConfiguredCopyResources()) {
                        concreteTargetResType.addCopyResource(
                            conres.getSource(),
                            conres.getTarget(),
                            conres.getTypeString());
                    }
                }

                for (Map.Entry<String, String> entry : sourceResType.getConfiguration().entrySet()) {
                    targetResType.addConfigurationParameter(
                        entry.getKey(),
                        entry.getValue().replaceAll(sourceModule.getName(), targetModule.getName()));
                }

                targetResType.setAdditionalModuleResourceType(true);
                targetResType.initConfiguration(
                    alterPrefix(
                        sourceResType.getTypeName(),
                        m_cloneInfo.getSourceNamePrefix(),
                        m_cloneInfo.getTargetNamePrefix()),
                    newId + "",
                    sourceResType.getClassName());

                keys.put(sourceResType.getTypeName(), targetResType.getTypeName());
                targetResourceTypes.add(targetResType);

                resourceTypeMapping.put(sourceResType, targetResType);

{'fraction_non_alphanumeric': 0.08835662935821337, 'fraction_numerical': 0.0012184720360667724, 'mean_word_length': 2.3675263774912074, 'pattern_counts': {'":': 0, '<': 129, '<?xml version=': 0, '>': 123, 'https://': 0, 'lorem ipsum': 0, 'www.': 6, 'xml': 2}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6649397', 'n_tokens_mistral': 14847, 'n_tokens_neox': 14249, 'n_words': 3305}