docs: private void integrate() throws Exception {
          writePlugins();
  
          // Collect information for each feature id and generate a feature table.
          final FileGenerator fileGen = new FileGenerator(featureTable, pluginTable);
          fileGen.setLogger(logger);
          for (final String currentPlugin : orderPlugins(pluginTable.keySet())) {
              loadPlugin(currentPlugin);
          }
  
          // generate the files from template
          for (final Entry<String, Value> template : templateSet.entrySet()) {
              final File templateFile = new File(ditaDir, template.getKey());
              logger.debug("Process template " + templateFile.getPath());
  //            fileGen.setPluginId(template.getValue().id);
              fileGen.generate(templateFile);
          }
  
          // generate configuration properties
          final Properties configuration = new Properties();
          // image extensions, support legacy property file extension
          final Set<String> imgExts = new HashSet<>();
          for (final String ext : properties.getProperty(CONF_SUPPORTED_IMAGE_EXTENSIONS, "").split(CONF_LIST_SEPARATOR)) {
              final String e = ext.trim();
              if (e.length() != 0) {
                  imgExts.add(e);
              }
          }
          if (featureTable.containsKey(FEAT_IMAGE_EXTENSIONS)) {
              for (final Value ext : featureTable.get(FEAT_IMAGE_EXTENSIONS)) {
                  final String e = ext.value.trim();
                  if (e.length() != 0) {
                      imgExts.add(e);
                  }
              }
          }
          configuration.put(CONF_SUPPORTED_IMAGE_EXTENSIONS, StringUtils.join(imgExts, CONF_LIST_SEPARATOR));
          // extensions
          configuration.put(CONF_SUPPORTED_HTML_EXTENSIONS, readExtensions(FEAT_HTML_EXTENSIONS));
          configuration.put(CONF_SUPPORTED_RESOURCE_EXTENSIONS, readExtensions(FEAT_RESOURCE_EXTENSIONS));
  
          // transtypes
          final String transtypes = featureTable.entrySet().stream()
                  .filter(e -> e.getKey().equals(FEAT_TRANSTYPES))
                  .flatMap(e -> e.getValue().stream().map(val -> val.value))
                  .distinct()
                  .collect(Collectors.joining(CONF_LIST_SEPARATOR));
          configuration.put(CONF_TRANSTYPES, transtypes);
  
          // print transtypes
          final Set<String> printTranstypes = new HashSet<>();
          if (featureTable.containsKey(FEAT_PRINT_TRANSTYPES)) {
              for (final Value ext : featureTable.get(FEAT_PRINT_TRANSTYPES)) {
                  final String e = ext.value.trim();
                  if (e.length() != 0) {
                      printTranstypes.add(e);
                  }
              }
          }
          // support legacy property
          final String printTranstypeValue = properties.getProperty(CONF_PRINT_TRANSTYPES);
          if (printTranstypeValue != null) {
              printTranstypes.addAll(Arrays.asList(printTranstypeValue.split(PARAM_VALUE_SEPARATOR)));
          }
          configuration.put(CONF_PRINT_TRANSTYPES, StringUtils.join(printTranstypes, CONF_LIST_SEPARATOR));
  
          for (final Entry<String, Features> e: pluginTable.entrySet()) {
              final Features f = e.getValue();
              final String name = "plugin."+ e.getKey() + ".dir";
              final List<String> baseDirValues = f.getFeature("dita.basedir-resource-directory");
              if (Boolean.parseBoolean(baseDirValues == null || baseDirValues.isEmpty() ? null : baseDirValues.get(0))) {
                  //configuration.put(name, ditaDir.getAbsolutePath());
                  configuration.put(name, ".");
              } else {
                  configuration.put(name, FileUtils.getRelativePath(new File(ditaDir, "dummy"), f.getPluginDir()).getPath());
              }
          }
          configuration.putAll(getParserConfiguration());
  
          OutputStream out = null;
          try {
              final File outFile = new File(ditaDir, CONFIG_DIR + File.separator + getClass().getPackage().getName() + File.separator + GEN_CONF_PROPERTIES);
              if (!(outFile.getParentFile().exists()) && !outFile.getParentFile().mkdirs()) {
                  throw new RuntimeException("Failed to make directory " + outFile.getParentFile().getAbsolutePath());
              }
              logger.debug("Generate configuration properties " + outFile.getPath());
              out = new BufferedOutputStream(new FileOutputStream(outFile));
              configuration.store(out, "DITA-OT runtime configuration, do not edit manually");
          } catch (final Exception e) {
              throw new RuntimeException("Failed to write configuration properties: " + e.getMessage(), e);
          } finally {
              if (out != null) {
                  try {
                      out.close();
                  } catch (final IOException e) {
                      logger.error(e.getMessage(), e) ;
                  }
              }
          }
  
          // Write messages properties
          final Properties messages = readMessageBundle();
          final File messagesFile = ditaDir.toPath().resolve(CONFIG_DIR).resolve("messages_en_US.properties").toFile();
          try (final OutputStream messagesOut = new FileOutputStream(messagesFile)) {
              messages.store(messagesOut, null);
          }
  
          final Collection<File> jars = featureTable.containsKey(FEAT_LIB_EXTENSIONS)
                  ? relativize(new LinkedHashSet<>(featureTable.get(FEAT_LIB_EXTENSIONS)))
                  : Collections.emptySet();
          writeEnvShell(jars);
          writeEnvBatch(jars);
  
          final Collection<File> libJars = ImmutableList.<File>builder()
                  .addAll(getLibJars())
                  .addAll(jars)
                  .build();
          writeStartcmdShell(libJars);
          writeStartcmdBatch(libJars);
  
          customIntegration();
      }