/**
* Copyright (c) 2000-present Liferay, Inc. All rights reserved.
*
* 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.
*/
package com.liferay.gradle.plugins.cache.util;
import com.liferay.gradle.util.Validator;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import org.gradle.api.Action;
import org.gradle.api.GradleException;
import org.gradle.api.Project;
import org.gradle.api.UncheckedIOException;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
import org.gradle.internal.hash.HashUtil;
import org.gradle.internal.hash.HashValue;
import org.gradle.process.ExecSpec;
import org.gradle.process.internal.ExecException;
import org.gradle.util.Clock;
/**
* @author Andrea Di Giorgi
*/
public class FileUtil extends com.liferay.gradle.util.FileUtil {
public static SortedSet<File> flattenAndSort(Iterable<File> files)
throws IOException {
final SortedSet<File> sortedFiles = new TreeSet<>(new FileComparator());
for (File file : files) {
if (file.isDirectory()) {
Files.walkFileTree(
file.toPath(),
new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(
Path path,
BasicFileAttributes basicFileAttributes)
throws IOException {
sortedFiles.add(path.toFile());
return FileVisitResult.CONTINUE;
}
});
}
else {
sortedFiles.add(file);
}
}
return sortedFiles;
}
public static String getDigest(File file) {
String digest;
try {
// Ignore EOL character differences between operating systems
List<String> lines = Files.readAllLines(
file.toPath(), StandardCharsets.UTF_8);
digest = Integer.toHexString(lines.hashCode());
}
catch (IOException ioe) {
// File is not a text file
if (_logger.isDebugEnabled()) {
_logger.debug(file + " is not a text file", ioe);
}
HashValue hashValue = HashUtil.sha1(file);
digest = hashValue.asHexString();
}
if (_logger.isInfoEnabled()) {
_logger.info("Digest of " + file + " is " + digest);
}
return digest;
}
public static String getDigest(
Project project, Iterable<File> files, boolean excludeIgnoredFiles) {
Clock clock = null;
if (_logger.isInfoEnabled()) {
clock = new Clock();
}
StringBuilder sb = new StringBuilder();
SortedSet<File> sortedFiles = null;
try {
sortedFiles = flattenAndSort(files);
}
catch (IOException ioe) {
throw new GradleException("Unable to flatten files", ioe);
}
if (excludeIgnoredFiles) {
removeIgnoredFiles(project, sortedFiles);
}
for (File file : sortedFiles) {
if (!file.exists()) {
continue;
}
if (".DS_Store".equals(file.getName())) {
continue;
}
sb.append(getDigest(file));
sb.append(_DIGEST_SEPARATOR);
}
if (sb.length() == 0) {
throw new GradleException("At least one file is required");
}
sb.setLength(sb.length() - 1);
if (_logger.isInfoEnabled() && (clock != null)) {
_logger.info(
"Getting the digest took " + clock.getTimeInMs() + " ms");
}
return sb.toString();
}
public static boolean removeIgnoredFiles(
Project project, SortedSet<File> files) {
if (files.isEmpty()) {
return false;
}
File rootDir = null;
File firstFile = files.first();
if (files.size() == 1) {
rootDir = firstFile.getParentFile();
}
else {
String dirName = StringUtil.getCommonPrefix(
'/', _getCanonicalPath(firstFile),
_getCanonicalPath(files.last()));
if (Validator.isNotNull(dirName)) {
rootDir = new File(dirName);
}
}
if (rootDir == null) {
if (_logger.isWarnEnabled()) {
_logger.warn(
"Unable to remove ignored files, common parent directory " +
"cannot be found");
}
return false;
}
String result = _getGitResult(
project, rootDir, "ls-files", "--cached", "--deleted",
"--exclude-standard", "--modified", "--others", "-z");
if (Validator.isNull(result)) {
if (_logger.isWarnEnabled()) {
_logger.warn(
"Unable to remove ignored files, Git returned an empty " +
"result");
}
return false;
}
String[] committedFileNames = result.split("\\000");
Set<File> committedFiles = new HashSet<>();
for (String fileName : committedFileNames) {
committedFiles.add(new File(rootDir, fileName));
}
return files.retainAll(committedFiles);
}
private static String _getCanonicalPath(File file) {
try {
String canonicalPath = file.getCanonicalPath();
if (File.separatorChar != '/') {
canonicalPath = canonicalPath.replace(File.separatorChar, '/');
}
return canonicalPath;
}
catch (IOException ioe) {
throw new UncheckedIOException(
"Unable to get canonical path of " + file, ioe);
}
}
private static String _getGitResult(
Project project, final File workingDir, final String... args) {
final ByteArrayOutputStream byteArrayOutputStream =
new ByteArrayOutputStream();
try {
project.exec(
new Action<ExecSpec>() {
@Override
public void execute(ExecSpec execSpec) {
execSpec.setArgs(Arrays.asList(args));
execSpec.setExecutable("git");
execSpec.setStandardOutput(byteArrayOutputStream);
execSpec.setWorkingDir(workingDir);
}
});
}
catch (ExecException ee) {
if (_logger.isInfoEnabled()) {
_logger.info(ee.getMessage(), ee);
}
}
return byteArrayOutputStream.toString();
}
private static final char _DIGEST_SEPARATOR = '-';
private static final Logger _logger = Logging.getLogger(FileUtil.class);
private static class FileComparator implements Comparator<File> {
@Override
public int compare(File file1, File file2) {
String canonicalPath1 = _getCanonicalPath(file1);
String canonicalPath2 = _getCanonicalPath(file2);
return canonicalPath1.compareTo(canonicalPath2);
}
}
}