FileTool.java

/*
 * Copyright (c) 2011-2024 Alex Tunyk <alex at tunyk.com>.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * See the NOTICE file distributed with this work for additional information
 * regarding copyright ownership.
 */
package com.tunyk.mvn.plugins.htmlcompressor;

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.regex.Matcher;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.json.JSONException;
import org.json.JSONObject;

/**
 * The Class FileTool.
 */
public class FileTool {

    /** The root dir path. */
    private String rootDirPath;

    /** The file extensions. */
    private String[] fileExtensions;

    /** The recursive. */
    private boolean recursive;

    /** The file encoding. */
    private Charset fileEncoding;

    /**
     * Instantiates a new file tool.
     *
     * @param rootDir
     *            the root dir
     * @param fileExtensions
     *            the file ext
     * @param recursive
     *            the recursive
     *
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     */
    public FileTool(String rootDir, String[] fileExtensions, boolean recursive) throws IOException {
        this.setRootDirPath(rootDir);
        this.fileExtensions = fileExtensions;
        this.recursive = recursive;
    }

    /**
     * Gets the files.
     *
     * @return the files
     *
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     */
    public ConcurrentMap<String, String> getFiles() throws IOException {
        ConcurrentMap<String, String> map = new ConcurrentHashMap<>();
        Path rootDir = Path.of(rootDirPath);
        List<Path> paths;
        try (Stream<Path> walk = Files.walk(rootDir)) {
            paths = walk.map(Path::normalize).filter(Files::isRegularFile)
                    .filter(path -> Arrays.stream(fileExtensions).anyMatch(path.getFileName().toString()::endsWith))
                    .collect(Collectors.toList());
        }
        int truncationIndex = 0;
        for (Path path : paths) {
            String normalizedFilePath = path.toFile().getCanonicalPath().replace("\\", "/");
            if (truncationIndex == 0) {
                truncationIndex = normalizedFilePath.indexOf(rootDirPath) + rootDirPath.length() + 1;
            }
            String key = normalizedFilePath.substring(truncationIndex);
            String value = Files.readString(path, getFileEncoding());
            map.put(key, value);
        }
        return map;
    }

    /**
     * Write files.
     *
     * @param map
     *            the map
     * @param targetDir
     *            the target dir
     *
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     */
    public void writeFiles(Map<String, String> map, String targetDir) throws IOException {
        for (Entry<String, String> entry : map.entrySet()) {
            Path path = Path.of(targetDir + '/' + entry.getKey());
            Files.createDirectories(path.getParent());
            Files.writeString(path, entry.getValue(), getFileEncoding());
        }
    }

    /**
     * Write to json file.
     *
     * @param map
     *            the map
     * @param targetFile
     *            the target file
     * @param integrationCode
     *            the integration code
     *
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     * @throws JSONException
     *             the JSON exception
     */
    public void writeToJsonFile(Map<String, String> map, String targetFile, String integrationCode)
            throws IOException, JSONException {
        String replacePattern = "%s";
        Path path = Path.of(targetFile);
        JSONObject json = new JSONObject();
        for (Entry<String, String> entry : map.entrySet()) {
            json.put(entry.getKey(), entry.getValue());
        }
        if (integrationCode == null) {
            integrationCode = replacePattern;
        }
        if (integrationCode.indexOf(replacePattern) == -1) {
            integrationCode += replacePattern;
        }
        String contents = integrationCode.replaceFirst(replacePattern, Matcher.quoteReplacement(json.toString()));
        Files.createDirectories(path.getParent());
        Files.writeString(path, contents, getFileEncoding());
    }

    /**
     * Human readable byte count.
     *
     * @param bytes
     *            the bytes
     * @param systemOfUnits
     *            the systemOfUnits
     *
     * @return the string
     */
    // TODO JWL 4/22/2023 Didn't see a good way to handle as it gets flagged to remove unnecessary cast if I fix this
    // per error-prone, so ignoring it
    @SuppressWarnings("LongDoubleConversion")
    public static String humanReadableByteCount(long bytes, boolean systemOfUnits) {
        int unit = systemOfUnits ? 1000 : 1024;
        if (bytes < unit) {
            return bytes + " B";
        }
        int exp = (int) (Math.log(bytes) / Math.log(unit));
        String pre = (systemOfUnits ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (systemOfUnits ? "" : "i");
        return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre);
    }

    /**
     * Gets the elapsed HMS time.
     *
     * @param elapsedTime
     *            the elapsed time
     *
     * @return the elapsed HMS time
     */
    public static String getElapsedHMSTime(long elapsedTime) {
        String format = String.format("%%0%dd", 2);
        elapsedTime = elapsedTime / 1000;
        String seconds = String.format(format, elapsedTime % 60);
        String minutes = String.format(format, (elapsedTime % 3600) / 60);
        String hours = String.format(format, elapsedTime / 3600);
        return hours + ":" + minutes + ":" + seconds;
    }

    /**
     * Gets the root dir path.
     *
     * @return the root dir path
     */
    public String getRootDirPath() {
        return rootDirPath;
    }

    /**
     * Sets the root dir path.
     *
     * @param rootDirPath
     *            the new root dir path
     *
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     */
    public void setRootDirPath(String rootDirPath) throws IOException {
        File file = new File(rootDirPath);
        this.rootDirPath = file.getCanonicalPath().replace("\\", "/").replaceAll("/$", "");
    }

    /**
     * Gets the file extensions.
     *
     * @return the file extensions
     */
    public String[] getFileExtensions() {
        return fileExtensions;
    }

    /**
     * Sets the file extensions.
     *
     * @param fileExtensions
     *            the new file extensions
     */
    public void setFileExtensions(String[] fileExtensions) {
        this.fileExtensions = fileExtensions;
    }

    /**
     * Checks if is recursive.
     *
     * @return true, if is recursive
     */
    public boolean isRecursive() {
        return recursive;
    }

    /**
     * Sets the recursive.
     *
     * @param recursive
     *            the new recursive
     */
    public void setRecursive(boolean recursive) {
        this.recursive = recursive;
    }

    /**
     * Gets the file encoding.
     *
     * @return the file encoding
     */
    public Charset getFileEncoding() {
        return fileEncoding == null ? Charset.defaultCharset() : fileEncoding;
    }

    /**
     * Sets the file encoding.
     *
     * @param fileEncoding
     *            the new file encoding
     */
    public void setFileEncoding(Charset fileEncoding) {
        this.fileEncoding = fileEncoding == null ? Charset.defaultCharset() : fileEncoding;
    }
}