* Copyright (C) 2007-2020 Crafter Software Corporation. All Rights Reserved.
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3 as published by
 * the Free Software Foundation.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.

package org.craftercms.studio.impl.v1.repository;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.text.StrLookup;
import org.apache.commons.lang3.text.StrMatcher;
import org.apache.commons.lang3.text.StrSubstitutor;
import org.craftercms.studio.api.v1.log.Logger;
import org.craftercms.studio.api.v1.log.LoggerFactory;

 * Implementation of {@link FileVisitor} that replaces values in the found files
 * @author joseross
 * @since 3.1.4
public class StrSubstitutorVisitor implements FileVisitor<Path> {

    private static final Logger logger = LoggerFactory.getLogger(StrSubstitutorVisitor.class);

    public static final StrMatcher PREFIX = StrMatcher.stringMatcher("${plugin:");

    protected StrSubstitutor strSubstitutor;

    public StrSubstitutorVisitor(Map<String, String> variables) {
        Map<String, String> escapedVars = new HashMap<>(variables.size());
        variables.forEach((key, value) -> escapedVars.put(key, StringEscapeUtils.escapeXml10(value)));
        strSubstitutor = new StrSubstitutor(StrLookup.mapLookup(escapedVars), PREFIX, StrSubstitutor.DEFAULT_SUFFIX,

    public FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attrs) {
        return FileVisitResult.CONTINUE;

    public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
        logger.debug("Replacing parameters in file: {0}", file);
        try (InputStream inputStream = Files.newInputStream(file)) {
            String originalContent = IOUtils.toString(inputStream);
            String updatedContent = strSubstitutor.replace(originalContent);
            if (!StringUtils.equals(originalContent, updatedContent)) {
                logger.debug("Updating file {}", file);
                Files.write(file, updatedContent.getBytes(StandardCharsets.UTF_8));
            return FileVisitResult.CONTINUE;
        } catch (IOException e) {
            logger.error("Error reading file {0}", e, file);
            throw e;

    public FileVisitResult visitFileFailed(final Path file, final IOException exc) throws IOException {
        logger.error("Error reading file at {0}", exc, file);
        throw exc;

    public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) {
        return FileVisitResult.CONTINUE;