All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.metaeffekt.artifact.analysis.utils.FileUtils Maven / Gradle / Ivy

/*
 * Copyright 2021-2024 the original author or authors.
 *
 * 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
 *
 *     http://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.
 */
package com.metaeffekt.artifact.analysis.utils;

import org.apache.commons.io.IOUtils;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.Checksum;
import org.apache.tools.ant.taskdefs.Delete;
import org.apache.tools.ant.taskdefs.Tar;
import org.apache.tools.ant.taskdefs.Zip;
import org.metaeffekt.core.inventory.processor.model.Constants;
import org.mozilla.universalchardet.UniversalDetector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StreamUtils;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.TimeUnit;

public class FileUtils extends org.metaeffekt.core.util.FileUtils {

    private static final Logger LOG = LoggerFactory.getLogger(FileUtils.class);

    public static final AntPathMatcher ANT_PATH_MATCHER = new AntPathMatcher();

    public static final void deleteDir(File dir) {
        if (dir.exists()) {
            Delete delete = new Delete();
            Project project = new Project();
            project.setBaseDir(dir);
            delete.setProject(project);
            delete.setDir(dir);
            delete.execute();
        }
    }

    public static String[] scanDirectoryForFiles(File targetDir, String... includes) {
        DirectoryScanner scanner = new DirectoryScanner();
        scanner.setBasedir(targetDir);
        scanner.setIncludes(includes);
        scanner.setCaseSensitive(false);
        scanner.scan();
        return scanner.getIncludedFiles();
    }

    public static String[] scanDirectoryForFiles(File targetDir, String[] includes, String[] excludes) {
        DirectoryScanner scanner = new DirectoryScanner();
        scanner.setBasedir(targetDir);
        scanner.setIncludes(includes);
        scanner.setExcludes(excludes);
        scanner.setCaseSensitive(false);
        scanner.scan();
        return scanner.getIncludedFiles();
    }

    public static String[] scanDirectoryForFiles(File targetDir, boolean addDefaultExcludes, String... includes) {
        DirectoryScanner scanner = new DirectoryScanner();
        scanner.setBasedir(targetDir);
        scanner.setIncludes(includes);
        scanner.setCaseSensitive(false);
        if (addDefaultExcludes) {
            scanner.addDefaultExcludes();
        }
        scanner.scan();
        return scanner.getIncludedFiles();
    }


    public static String[] scanDirectoryForFolders(File targetDir, String... includes) {
        DirectoryScanner scanner = new DirectoryScanner();
        scanner.setBasedir(targetDir);
        scanner.setIncludes(includes);
        scanner.setCaseSensitive(false);
        scanner.scan();
        return scanner.getIncludedDirectories();
    }

    public static String computeChecksum(File file) {
        Checksum checksum = new Checksum();
        Project p = new Project();
        checksum.setProject(p);
        checksum.setFile(file);
        String checksumVariable = "checksum";
        checksum.setProperty(checksumVariable);
        checksum.execute();
        return p.getProperty(checksumVariable);
    }

    public static String computeSha1Checksum(File file) {
        Checksum checksum = new Checksum();
        Project p = new Project();
        checksum.setProject(p);
        checksum.setFile(file);
        String checksumVariable = "checksum";
        checksum.setProperty(checksumVariable);
        checksum.setAlgorithm("SHA-1");
        checksum.execute();
        return p.getProperty(checksumVariable);
    }

    public static void validateExists(File file) {
        if (!file.exists()) {
            throw new IllegalStateException(String.format("File '%s' does not exist.", file.getAbsolutePath()));
        }
    }

    public static boolean compareFolders(File sourceDir, File targetDir) {
        validateExists(sourceDir);
        validateExists(targetDir);

        HashSet sourceFileSet = fileToSet(sourceDir);
        HashSet targetFileSet = fileToSet(targetDir);

        List messages = new ArrayList<>();

        // compare from source to target
        for (String file : sourceFileSet) {
            if (!targetFileSet.contains(file)) {
                messages.add(String.format("Target folder does not contain file %s", file));
            }
        }

        // compare from target to source
        for (String file : targetFileSet) {
            if (!sourceFileSet.contains(file)) {
                messages.add(String.format("Source folder does not contain file %s", file));
            }
        }

        // compare file content by comparing checksums
        for (String file : sourceFileSet) {
            String sourceCS = computeChecksum(new File(sourceDir, file));
            String targetCS = computeChecksum(new File(targetDir, file));

            if (!sourceCS.equals(targetCS)) {
                messages.add(String.format("Content of file %s differs.", file));
            }
        }

        // print report (full diff)
        for (String message : messages) {
            LOG.info(message);
        }

        return !messages.isEmpty();
    }


    public static HashSet fileToSet(File dir) {
        String[] files = scanDirectoryForFiles(dir, true, "**/*");
        HashSet fileSet = new HashSet<>();
        for (String file : files) {
            fileSet.add(file);
        }
        return fileSet;
    }

    /**
     * Uses the native zip command to zip the file. Uses the -X attribute to create files with deterministic checksum.
     *
     * @param sourceDir     The directory to zip (recursively).
     * @param targetZipFile The target zip file name.
     * @throws IOException If the commands could not be executed.
     */
    public static void zipNative(File sourceDir, File targetZipFile) throws IOException {
        // FIXME: touch all files in source dir to normalize timestamp
        String[] touchArray = {"find", ".", "-type", "f", "-exec", "touch", "-t", "202001010000", "{}", "+"};
        Process touch = Runtime.getRuntime().exec(touchArray, new String[]{}, sourceDir);

        // block until touch is completed
        waitForProcess(touch);

        // run native zip process
        String[] zip = {"zip", "-X", "-r", targetZipFile.getAbsolutePath(), "."};
        Process p = Runtime.getRuntime().exec(zip, new String[]{}, sourceDir);

        // block until zip is completed
        waitForProcess(p);

        // check exit value and write error to System.err in case exit code is not 0
        if (p.exitValue() != 0) {
            StreamUtils.copy(p.getErrorStream(), System.err);
        }
    }

    public static void waitForProcess(Process p) {
        try {
            while (p.isAlive()) {
                p.waitFor(1000, TimeUnit.MILLISECONDS);
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                try {
                    IOUtils.copy(p.getInputStream(), baos);
                } catch (IOException e) {
                    LOG.error("Unable to copy input stream into output stream.", e);
                }
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    /**
     * Uses the native zip command to zip the file. Uses the -X attribute to create files with deterministic checksum.
     *
     * @param sourceDir     The directory to zip (recursively).
     * @param targetZipFile The target zip file name.
     */
    public static void zipAnt(File sourceDir, File targetZipFile) {
        Zip zip = new Zip();
        Project project = new Project();
        project.setBaseDir(sourceDir);
        zip.setProject(project);
        zip.setBasedir(sourceDir);
        zip.setCompress(true);
        zip.setDestFile(targetZipFile);
        zip.setFollowSymlinks(false);
        zip.execute();
    }

    public static void gzip(File sourceDir, File targetZipFile) throws IOException {
        Tar zip = new Tar();
        Project project = new Project();
        project.setBaseDir(sourceDir);
        zip.setBasedir(sourceDir);
        zip.setProject(project);
        zip.setDestFile(targetZipFile);
        zip.setDescription("");
        zip.execute();
    }

    public static boolean matches(String absolutePath, String[] patterns) {
        for (String pattern : patterns) {
            pattern = pattern.trim();

            // Springs AntPatternMatcher requires a trailing "/" to match an absolute path
            pattern = "/" + pattern;
            if (ANT_PATH_MATCHER.match(pattern, absolutePath)) {
                return true;
            }
        }

        // none of the patterns matched
        return false;
    }

    public static void createDirectoryContentChecksumFile(File baseDir, File targetContentChecksumFile) throws IOException {
        final StringBuilder checksumSequence = new StringBuilder();
        final String[] files = FileUtils.scanDirectoryForFiles(baseDir, new String[]{"**/*"}, new String[]{"**/.DS_Store*"});

        Arrays.stream(files).map(fileName -> normalizePathToLinux(fileName)).sorted(String::compareTo).forEach(fileName -> {
            final File file = new File(baseDir, fileName);
            try {
                final String fileChecksum = FileUtils.computeChecksum(file);
                if (checksumSequence.length() > 0) {
                    checksumSequence.append(0);
                }
                checksumSequence.append(fileChecksum);
            } catch (Exception e) {
                if (FileUtils.isSymlink(file)) {
                    LOG.warn("Cannot compute checksum for symbolic link file [{}].", file);
                } else {
                    LOG.warn("Cannot compute checksum for file [{}].", file);
                }
            }
        });
        FileUtils.writeStringToFile(targetContentChecksumFile, checksumSequence.toString(), FileUtils.ENCODING_UTF_8);
    }

    public static File findSingleFile(File baseFile, String pattern) {
        final String[] files = scanDirectoryForFiles(baseFile, pattern);
        if (files.length == 1) {
            return new File(baseFile, files[0]);
        }
        return null;
    }

    public static File findSingleDir(File baseFile, String pattern) {
        final String[] dirs = FileUtils.scanDirectoryForFolders(baseFile, pattern);
        if (dirs.length == 1) {
            return new File(baseFile, dirs[0]);
        }
        return null;
    }

    public static String computeContentChecksum(File file, File tmpFolder) throws IOException {
        final File contentChecksumFile = new File(tmpFolder, file.getName() + ".content.md5");
        createDirectoryContentChecksumFile(file, contentChecksumFile);
        try {
            return FileUtils.computeChecksum(contentChecksumFile);
        } finally {
            deleteQuietly(contentChecksumFile);
        }
    }

    public static void forceMkDirQuietly(File file) {
        try {
            FileUtils.forceMkdir(file);
        } catch (IOException exception) {
            throw new IllegalStateException("Cannot create folder [" + file + "].");
        }
    }

    public static boolean exists(String filePath) {
        if (filePath == null) return false;
        return new File(filePath).exists();
    }

    public static String detectEncoding(File file) throws IOException {
        String encoding = UniversalDetector.detectCharset(file);
        if (encoding == null) {
            // fallback to UTF-8
            encoding = FileUtils.ENCODING_UTF_8;
        }
        return encoding;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy