content: /*
   * This library is part of OpenCms -
   * the Open Source Content Management System
   *
   * Copyright (c) Alkacon Software GmbH & Co. KG (https://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: https://www.alkacon.com
   *
   * For further information about OpenCms, please see the
   * project website: https://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.CmsResourceTypeXmlContent;
  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 org.opencms.xml.content.CmsXmlContent;
  import org.opencms.xml.content.CmsXmlContentFactory;
  
  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 = 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)));
              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)));
              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 targetResources the paths to adjust
       * @param sourceModuleName the source module name
       * @param targetModuleName the target module name
       * @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
       * @return the adjusted paths
       */
      private List<String> adjustModuleResourcePaths(
          List<String> targetResources,
          String sourceModuleName,
          String targetModuleName,
          String sourcePathPart,
          String targetPathPart,
          Map<String, String> iconPaths) {
  
          List<String> newTargetResources = new ArrayList<String>();
          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(sourceModuleName)) {
                  // there is the name in it
                  newTargetResources.add(modRes.replaceAll(sourceModuleName, targetModuleName));
              } 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);
              }
          }
          return newTargetResources;
      }
  
      /**
       * 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 = adjustModuleResourcePaths(
              targetModule.getResources(),
              sourceModule.getName(),
              targetModule.getName(),
              sourcePathPart,
              targetPathPart,
              iconPaths);
          targetModule.setResources(newTargetResources);
  
          List<String> newTargetExcludeResources = adjustModuleResourcePaths(
              targetModule.getExcludeResources(),
              sourceModule.getName(),
              targetModule.getName(),
              sourcePathPart,
              targetPathPart,
              iconPaths);
          targetModule.setExcludeResources(newTargetExcludeResources);
      }
  
      /**
       * 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)) {
              return word.replaceFirst(oldPrefix, newPrefix);
          }
          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());
              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);
                      if (CmsResourceTypeXmlContent.isXmlContent(file)) {
                          CmsXmlContent xmlContent = CmsXmlContentFactory.unmarshal(getCms(), file);
                          xmlContent.setAutoCorrectionEnabled(true);
                          file = xmlContent.correctXmlStructure(getCms());
                      }
                      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(source) && !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.getBigIcon(),
                  m_cloneInfo.getSourceNamePrefix(),
                  m_cloneInfo.getTargetNamePrefix());
              iconPaths.put(expSetting.getIcon(), newIcon);
              iconPaths.put(expSetting.getBigIcon(), newBigIcon);
              String oldExpTypeName = expSetting.getName();
              String newExpTypeName = alterPrefix(
                  oldExpTypeName,
                  m_cloneInfo.getSourceNamePrefix(),
                  m_cloneInfo.getTargetNamePrefix());
              expSetting.setName(newExpTypeName);
  
              expSetting.setKey(expSetting.getKey().replaceFirst(oldExpTypeName, newExpTypeName));
              expSetting.setIcon(
                  alterPrefix(
                      expSetting.getIcon(),
                      m_cloneInfo.getSourceNamePrefix(),
                      m_cloneInfo.getTargetNamePrefix()));
              expSetting.setBigIcon(
                  alterPrefix(
                      expSetting.getBigIcon(),
                      m_cloneInfo.getSourceNamePrefix(),
                      m_cloneInfo.getTargetNamePrefix()));
              expSetting.setInfo(expSetting.getInfo().replaceFirst(oldExpTypeName, newExpTypeName));
          }
      }
  
      /**
       * 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);
  
              } catch (Exception e) {
                  // resource type is unknown, use dummy class to import the module resources
                  targetResType = new CmsResourceTypeUnknown();
                  // write an error to the log
                  LOG.error(
                      org.opencms.configuration.Messages.get().getBundle().key(
                          org.opencms.configuration.Messages.ERR_UNKNOWN_RESTYPE_CLASS_2,
                          className,
                          targetResType.getClass().getName()),
                      e);
              }
          }
          targetModule.setResourceTypes(targetResourceTypes);
          return resourceTypeMapping;
      }
  
      /**
       * Creates the target folder for the module clone.<p>
       *
       * @param targetModule the target module
       * @param sourceClassesPath the source module class path
       * @param targetBaseClassesPath the 'classes' folder of the target module
       *
       * @throws CmsException if something goes wrong
       */
      private void createTargetClassesFolder(
          CmsModule targetModule,
          String sourceClassesPath,
          String targetBaseClassesPath)
      throws CmsException {
  
          StringTokenizer tok = new StringTokenizer(targetModule.getName(), ".");
          int folderId = CmsResourceTypeFolder.getStaticTypeId();
          String targetClassesPath = targetBaseClassesPath;
  
          while (tok.hasMoreTokens()) {
              String folder = tok.nextToken();
              targetClassesPath += folder + "/";
              if (!getCms().existsResource(targetClassesPath)) {
                  getCms().createResource(targetClassesPath, folderId);
              }
          }
          // move exiting content into new classes sub-folder
          List<CmsResource> propertyFiles = getCms().readResources(sourceClassesPath, CmsResourceFilter.ALL);
          for (CmsResource res : propertyFiles) {
              if (!getCms().existsResource(targetClassesPath + res.getName())) {
                  getCms().copyResource(res.getRootPath(), targetClassesPath + res.getName());
              }
          }
      }
  
      /**
       * Deletes the temporarily copied classes files.<p>
       *
       * @param targetModulePath the target module path
       * @param sourcePathPart the path part of the source module
       * @param targetPathPart the target path part
       *
       * @throws CmsException if something goes wrong
       */
      private void deleteSourceClassesFolder(String targetModulePath, String sourcePathPart, String targetPathPart)
      throws CmsException {
  
          String sourceFirstFolder = sourcePathPart.substring(0, sourcePathPart.indexOf('/'));
          String targetFirstFolder = sourcePathPart.substring(0, sourcePathPart.indexOf('/'));
          if (!sourceFirstFolder.equals(targetFirstFolder)) {
              getCms().deleteResource(
                  targetModulePath + PATH_CLASSES + sourceFirstFolder,
                  CmsResource.DELETE_PRESERVE_SIBLINGS);
              return;
          }
          String[] targetPathParts = CmsStringUtil.splitAsArray(targetPathPart, '/');
          String[] sourcePathParts = CmsStringUtil.splitAsArray(sourcePathPart, '/');
          int sourceLength = sourcePathParts.length;
          int diff = 0;
          for (int i = 0; i < targetPathParts.length; i++) {
              if (sourceLength >= i) {
                  if (!targetPathParts[i].equals(sourcePathParts[i])) {
                      diff = i + 1;
                  }
              }
          }
          String topSourceClassesPath = targetModulePath
              + PATH_CLASSES
              + sourcePathPart.substring(0, sourcePathPart.indexOf('/'))
              + "/";
  
          if (diff != 0) {
              topSourceClassesPath = targetModulePath + PATH_CLASSES;
              for (int i = 0; i < diff; i++) {
                  topSourceClassesPath += sourcePathParts[i] + "/";
              }
          }
          getCms().deleteResource(topSourceClassesPath, CmsResource.DELETE_PRESERVE_SIBLINGS);
      }
  
      /**
       * Returns <code>true</code> if form input is selected, checked, on or yes.<p>
       *
       * @param value the value to check
       *
       * @return <code>true</code> if form input is selected, checked, on or yes
       */
      private boolean isTrue(String value) {
  
          if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(value)) {
              if (Boolean.valueOf(value.toLowerCase()).booleanValue()
                  || value.toLowerCase().equals("on")
                  || value.toLowerCase().equals("yes")
                  || value.toLowerCase().equals("checked")
                  || value.toLowerCase().equals("selected")) {
                  return true;
              }
          }
          return false;
      }
  
      /**
       * Locks the current resource.<p>
       *
       * @param cms the current CmsObject
       * @param cmsResource the resource to lock
       *
       * @return <code>true</code> if the given resource was locked was successfully
       *
       * @throws CmsException if some goes wrong
       */
      private boolean lockResource(CmsObject cms, CmsResource cmsResource) throws CmsException {
  
          CmsLock lock = cms.getLock(cms.getSitePath(cmsResource));
          // check the lock
          if ((lock != null)
              && lock.isOwnedBy(cms.getRequestContext().getCurrentUser())
              && lock.isOwnedInProjectBy(
                  cms.getRequestContext().getCurrentUser(),
                  cms.getRequestContext().getCurrentProject())) {
              // prove is current lock from current user in current project
              return true;
          } else if ((lock != null) && !lock.isUnlocked() && !lock.isOwnedBy(cms.getRequestContext().getCurrentUser())) {
              // the resource is not locked by the current user, so can not lock it
              return false;
          } else if ((lock != null)
              && !lock.isUnlocked()
              && lock.isOwnedBy(cms.getRequestContext().getCurrentUser())
              && !lock.isOwnedInProjectBy(
                  cms.getRequestContext().getCurrentUser(),
                  cms.getRequestContext().getCurrentProject())) {
              // prove is current lock from current user but not in current project
              // file is locked by current user but not in current project
  
              cms.changeLock(cms.getSitePath(cmsResource));
          } else if ((lock != null) && lock.isUnlocked()) {
              // lock resource from current user in current project
              cms.lockResource(cms.getSitePath(cmsResource));
          }
          lock = cms.getLock(cms.getSitePath(cmsResource));
          if ((lock != null)
              && lock.isOwnedBy(cms.getRequestContext().getCurrentUser())
              && !lock.isOwnedInProjectBy(
                  cms.getRequestContext().getCurrentUser(),
                  cms.getRequestContext().getCurrentProject())) {
              // resource could not be locked
              return false;
          }
          // resource is locked successfully
          return true;
      }
  
      /**
       * Renames the vfs resource bundle files within the target module according to the new module's name.<p>
       *
       * @param resources the vfs resource bundle files
       * @param targetModule the target module
       * @param name the package name of the source module
       *
       * @return a list of all xml vfs bundles within the given module
       * @throws CmsException if something gows wrong
       */
      private List<CmsResource> renameXmlVfsBundles(List<CmsResource> resources, CmsModule targetModule, String name)
      throws CmsException {
  
          for (CmsResource res : resources) {
              String newName = res.getName().replaceAll(name, targetModule.getName());
              String targetRootPath = CmsResource.getFolderPath(res.getRootPath()) + newName;
              if (!getCms().existsResource(targetRootPath)) {
                  getCms().moveResource(res.getRootPath(), targetRootPath);
              }
          }
          return resources;
  
      }
  
      /**
       * Replaces the referenced formatters within the new XSD files with the new formatter paths.<p>
       *
       * @param targetModule the target module
       *
       * @throws CmsException if something goes wrong
       * @throws UnsupportedEncodingException if the file content could not be read with the determined encoding
       */
      private void replaceFormatterPaths(CmsModule targetModule) throws CmsException, UnsupportedEncodingException {
  
          CmsResource formatterSourceFolder = getCms().readResource(
              "/system/modules/" + m_cloneInfo.getFormatterSourceModule() + "/");
          CmsResource formatterTargetFolder = getCms().readResource(
              "/system/modules/" + m_cloneInfo.getFormatterTargetModule() + "/");
          for (I_CmsResourceType type : targetModule.getResourceTypes()) {
              String schemaPath = type.getConfiguration().get("schema");
              CmsResource res = getCms().readResource(schemaPath);
              CmsFile file = getCms().readFile(res);
              if (CmsResourceTypeXmlContent.isXmlContent(file)) {
                  CmsXmlContent xmlContent = CmsXmlContentFactory.unmarshal(getCms(), file);
                  xmlContent.setAutoCorrectionEnabled(true);
                  file = xmlContent.correctXmlStructure(getCms());
              }
              String encoding = CmsLocaleManager.getResourceEncoding(getCms(), file);
              String content = new String(file.getContents(), encoding);
              content = content.replaceAll(formatterSourceFolder.getRootPath(), formatterTargetFolder.getRootPath());
              file.setContents(content.getBytes(encoding));
              getCms().writeFile(file);
          }
      }
  
      /**
       * Initializes a thread to find and replace all occurrence of the module's path.<p>
       *
       * @throws CmsException in case writing the file fails
       * @throws UnsupportedEncodingException in case of the wrong encoding
  
       */
      private void replaceModuleName() throws CmsException, UnsupportedEncodingException {
  
          CmsResourceFilter filter = CmsResourceFilter.ALL.addRequireFile().addExcludeState(
              CmsResource.STATE_DELETED).addRequireTimerange().addRequireVisible();
          List<CmsResource> resources = getCms().readResources(
              CmsWorkplace.VFS_PATH_MODULES + m_cloneInfo.getName() + "/",
              filter);
          for (CmsResource resource : resources) {
              CmsFile file = getCms().readFile(resource);
              if (CmsResourceTypeXmlContent.isXmlContent(file)) {
                  CmsXmlContent xmlContent = CmsXmlContentFactory.unmarshal(getCms(), file);
                  xmlContent.setAutoCorrectionEnabled(true);
                  file = xmlContent.correctXmlStructure(getCms());
              }
              byte[] contents = file.getContents();
              String encoding = CmsLocaleManager.getResourceEncoding(getCms(), file);
              String content = new String(contents, encoding);
              Matcher matcher = Pattern.compile(m_cloneInfo.getSourceModuleName()).matcher(content);
              if (matcher.find()) {
                  contents = matcher.replaceAll(m_cloneInfo.getName()).getBytes(encoding);
                  if (lockResource(getCms(), file)) {
                      file.setContents(contents);
                      getCms().writeFile(file);
                  }
              }
          }
      }
  
      /**
       * Replaces the paths within all the given resources and removes all UUIDs by an regex.<p>
       *
       * @param sourceModulePath the search path
       * @param targetModulePath the replace path
       * @param resources the resources
       *
       * @throws CmsException if something goes wrong
       * @throws UnsupportedEncodingException if the file content could not be read with the determined encoding
       */
      private void replacePath(String sourceModulePath, String targetModulePath, List<CmsResource> resources)
      throws CmsException, UnsupportedEncodingException {
  
          for (CmsResource resource : resources) {
              if (resource.isFile()) {
                  CmsFile file = getCms().readFile(resource);
                  if (CmsResourceTypeXmlContent.isXmlContent(file)) {
                      CmsXmlContent xmlContent = CmsXmlContentFactory.unmarshal(getCms(), file);
                      xmlContent.setAutoCorrectionEnabled(true);
                      file = xmlContent.correctXmlStructure(getCms());
                  }
                  String encoding = CmsLocaleManager.getResourceEncoding(getCms(), file);
                  String oldContent = new String(file.getContents(), encoding);
                  String newContent = oldContent.replaceAll(sourceModulePath, targetModulePath);
                  Matcher matcher = Pattern.compile(CmsUUID.UUID_REGEX).matcher(newContent);
                  newContent = matcher.replaceAll("");
                  newContent = newContent.replaceAll("<uuid></uuid>", "");
                  if (!oldContent.equals(newContent)) {
                      file.setContents(newContent.getBytes(encoding));
                      if (!resource.getRootPath().startsWith(CmsWorkplace.VFS_PATH_SYSTEM)) {
                          if (lockResource(getCms(), resource)) {
                              getCms().writeFile(file);
                          }
                      } else {
                          getCms().writeFile(file);
                      }
                  }
              }
          }
      }
  
      /**
       * Replaces the messages for the given resources.<p>
       *
       * @param descKeys the replacement mapping
       * @param resources the resources to consult
       *
       * @throws CmsException if something goes wrong
       * @throws UnsupportedEncodingException if the file content could not be read with the determined encoding
  
       */
      private void replacesMessages(Map<String, String> descKeys, List<CmsResource> resources)
      throws CmsException, UnsupportedEncodingException {
  
          for (CmsResource resource : resources) {
              CmsFile file = getCms().readFile(resource);
              String encoding = CmsLocaleManager.getResourceEncoding(getCms(), file);
              String content = new String(file.getContents(), encoding);
              for (Map.Entry<String, String> entry : descKeys.entrySet()) {
                  content = content.replaceAll(entry.getKey(), entry.getValue());
              }
              file.setContents(content.getBytes(encoding));
              getCms().writeFile(file);
          }
      }
  
      /**
       * Reads a file into a string, applies a transformation to the string, and writes the string back to the file.<p>
       *
       * @param resource the resource to transform
       * @param transformation the transformation to apply
       * @throws CmsException if something goes wrong
       * @throws UnsupportedEncodingException in case the encoding is not supported
       */
      private void transformResource(CmsResource resource, Function<String, String> transformation)
      throws CmsException, UnsupportedEncodingException {
  
          CmsFile file = getCms().readFile(resource);
          if (CmsResourceTypeXmlContent.isXmlContent(file)) {
              CmsXmlContent xmlContent = CmsXmlContentFactory.unmarshal(getCms(), file);
              xmlContent.setAutoCorrectionEnabled(true);
              file = xmlContent.correctXmlStructure(getCms());
          }
          String encoding = CmsLocaleManager.getResourceEncoding(getCms(), file);
          String content = new String(file.getContents(), encoding);
          content = transformation.apply(content);
          file.setContents(content.getBytes(encoding));
          lockResource(getCms(), file);
          getCms().writeFile(file);
  
      }
  }
  