/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.commons.lang;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.ws.rs.HttpMethod;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.nio.channels.FileChannel;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import static java.nio.file.FileVisitResult.CONTINUE;
import static java.nio.file.FileVisitResult.TERMINATE;
public class IoUtil {
private static final Logger LOG = LoggerFactory.getLogger(IoUtil.class);
private IoUtil() {
}
/** Represents filter what select any file */
public static final FilenameFilter ANY_FILTER = new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return true;
}
};
/** Represent filter, that excludes .git entries. */
public static final FilenameFilter GIT_FILTER = new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return !(".git".equals(name));
}
};
/**
* Reads bytes from input stream and builds a string from them.
*
* @param inputStream
* source stream
* @return string
* @throws java.io.IOException
* if any i/o error occur
*/
public static String readStream(InputStream inputStream) throws IOException {
if (inputStream == null) {
return null;
}
ByteArrayOutputStream bout = new ByteArrayOutputStream();
byte[] buf = new byte[8192];
int r;
while ((r = inputStream.read(buf)) != -1) {
bout.write(buf, 0, r);
}
return bout.toString("UTF-8");
}
/**
* Reads bytes from input stream and builds a string from them. InputStream closed after consumption.
*
* @param inputStream
* source stream
* @return string
* @throws java.io.IOException
* if any i/o error occur
*/
public static String readAndCloseQuietly(InputStream inputStream) throws IOException {
try {
return readStream(inputStream);
} catch (IOException e) {
LOG.error(e.getLocalizedMessage(), e);
throw e;
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
LOG.error(e.getLocalizedMessage(), e);
}
}
}
}
/**
* Looking for resource by given path. If no file exist by this path, method will try to find it in context.
*
* @param resource
* - path to resource
* @return - InputStream of resource
* @throws IOException
*/
public static InputStream getResource(String resource) throws IOException {
File resourceFile = new File(resource);
if (resourceFile.exists() && !resourceFile.isFile()) {
throw new IOException(String.format("%s is not a file. ", resourceFile.getAbsolutePath()));
}
InputStream is = resourceFile.exists() ? new FileInputStream(resourceFile) : IoUtil.class.getResourceAsStream(resource);
if (is == null) {
throw new IOException(String.format("Not found resource: %s", resource));
}
return is;
}
/** Remove directory and all its sub-resources with specified path */
public static boolean removeDirectory(String pathToDir) {
return deleteRecursive(new File(pathToDir));
}
/**
* Remove specified file or directory.
*
* @param fileOrDirectory
* the file or directory to cancel
* @return <code>true</code> if specified File was deleted and <code>false</code> otherwise
*/
public static boolean deleteRecursive(File fileOrDirectory) {
if (fileOrDirectory.isDirectory()) {
File[] list = fileOrDirectory.listFiles();
if (list == null) {
return false;
}
for (File f : list) {
if (!deleteRecursive(f)) {
return false;
}
}
}
if (!fileOrDirectory.delete()) {
if (fileOrDirectory.exists()) {
return false;
}
}
return true;
}
/**
* Remove specified file or directory.
*
* @param fileOrDirectory
* the file or directory to cancel
* @param followLinks
* are symbolic links followed or not?
* @return <code>true</code> if specified File was deleted and <code>false</code> otherwise
*/
public static boolean deleteRecursive(File fileOrDirectory, boolean followLinks) {
if (fileOrDirectory.isDirectory()) {
// If fileOrDirectory represents a symbolic link to a folder,
// do not read a target folder content. Just remove this symbolic link.
if (!followLinks && java.nio.file.Files.isSymbolicLink(fileOrDirectory.toPath())) {
return !fileOrDirectory.exists() || fileOrDirectory.delete();
}
File[] list = fileOrDirectory.listFiles();
if (list == null) {
return false;
}
for (File f : list) {
if (!deleteRecursive(f, followLinks)) {
return false;
}
}
}
if (!fileOrDirectory.delete()) {
if (fileOrDirectory.exists()) {
return false;
}
}
return true;
}
/**
* Download file.
*
* @param parent
* parent directory, may be <code>null</code> then use 'java.io.tmpdir'
* @param prefix
* prefix of temporary file name, may not be <code>null</code> and must be at least three characters long
* @param suffix
* suffix of temporary file name, may be <code>null</code>
* @param url
* URL for download
* @return downloaded file
* @throws java.io.IOException
* if any i/o error occurs
*/
public static File downloadFile(File parent, String prefix, String suffix, URL url) throws IOException {
File file = File.createTempFile(prefix, suffix, parent);
URLConnection conn = null;
final String protocol = url.getProtocol().toLowerCase(Locale.ENGLISH);
try {
conn = url.openConnection();
if ("http".equals(protocol) || "https".equals(protocol)) {
HttpURLConnection http = (HttpURLConnection)conn;
http.setInstanceFollowRedirects(false);
http.setRequestMethod(HttpMethod.GET);
}
try (InputStream input = conn.getInputStream();
FileOutputStream fOutput = new FileOutputStream(file)) {
byte[] b = new byte[8192];
int r;
while ((r = input.read(b)) != -1) {
fOutput.write(b, 0, r);
}
}
} finally {
if (conn != null && ("http".equals(protocol) || "https".equals(protocol))) {
((HttpURLConnection)conn).disconnect();
}
}
return file;
}
/**
* Download file with redirection if got status 301, 302, 303.
* Will useful in case redirection http -> https
*
* @param parent
* parent directory, may be <code>null</code> then use 'java.io.tmpdir'
* @param prefix
* prefix of temporary file name, may not be <code>null</code> and must be at least three characters long
* @param suffix
* suffix of temporary file name, may be <code>null</code>
* @param url
* URL for download
* @return downloaded file
* @throws java.io.IOException
* if any i/o error occurs
*/
public static File downloadFileWithRedirect(File parent, String prefix, String suffix, URL url) throws IOException {
File file = File.createTempFile(prefix, suffix, parent);
URLConnection conn = null;
final String protocol = url.getProtocol().toLowerCase(Locale.ENGLISH);
try {
conn = url.openConnection();
boolean redirect = false;
if ("http".equals(protocol) || "https".equals(protocol)) {
HttpURLConnection http = (HttpURLConnection)conn;
http.setRequestMethod(HttpMethod.GET);
int status = http.getResponseCode();
if (status == HttpURLConnection.HTTP_MOVED_TEMP || status == HttpURLConnection.HTTP_MOVED_PERM ||
status == HttpURLConnection.HTTP_SEE_OTHER) {
redirect = true;
}
if (redirect) {
String newUrl = conn.getHeaderField("Location");
// open the new connection again
http.disconnect();
conn = new URL(newUrl).openConnection();
http = (HttpURLConnection)conn;
http.setRequestMethod(HttpMethod.GET);
}
}
try (InputStream input = conn.getInputStream();
FileOutputStream fOutput = new FileOutputStream(file)) {
byte[] b = new byte[8192];
int r;
while ((r = input.read(b)) != -1) {
fOutput.write(b, 0, r);
}
}
} finally {
if (conn != null && ("http".equals(protocol) || "https".equals(protocol))) {
((HttpURLConnection)conn).disconnect();
}
}
return file;
}
/**
* Copy file or directory to the specified destination. Existed files in destination directory will be overwritten.
*
* @param source
* copy source
* @param target
* copy destination
* @param filter
* copy filter
* @throws java.io.IOException
* if any i/o error occurs
*/
public static void copy(File source, File target, FilenameFilter filter) throws IOException {
copy(source, target, filter, false, true);
}
/**
* Copy file or directory to the specified destination. Existed files in destination directory will be overwritten.
* <p/>
* This method use java.nio for coping files.
*
* @param source
* copy source
* @param target
* copy destination
* @param filter
* copy filter
* @throws java.io.IOException
* if any i/o error occurs
*/
public static void nioCopy(File source, File target, FilenameFilter filter) throws IOException {
copy(source, target, filter, true, true);
}
/**
* Copy file or directory to the specified destination.
*
* @param source
* copy source
* @param target
* copy destination
* @param filter
* copy filter
* @param replaceIfExists
* if <code>true</code> existed files in destination directory will be overwritten
* @throws java.io.IOException
* if any i/o error occurs
*/
public static void copy(File source, File target, FilenameFilter filter, boolean replaceIfExists) throws IOException {
copy(source, target, filter, false, replaceIfExists);
}
/**
* Copy file or directory to the specified destination.
* <p/>
* This method use java.nio for coping files.
*
* @param source
* copy source
* @param target
* copy destination
* @param filter
* copy filter
* @param replaceIfExists
* if <code>true</code> existed files in destination directory will be overwritten
* @throws java.io.IOException
* if any i/o error occurs
*/
public static void nioCopy(File source, File target, FilenameFilter filter, boolean replaceIfExists)
throws IOException {
copy(source, target, filter, true, replaceIfExists);
}
private static void copy(File source, File target, FilenameFilter filter, boolean nio, boolean replaceIfExists)
throws IOException {
if (source.isDirectory()) {
if (!(target.exists() || target.mkdirs())) {
throw new IOException(String.format("Unable create directory '%s'. ", target.getAbsolutePath()));
}
if (filter == null) {
filter = ANY_FILTER;
}
String sourceRoot = source.getAbsolutePath();
LinkedList<File> q = new LinkedList<>();
q.add(source);
while (!q.isEmpty()) {
File current = q.pop();
File[] list = current.listFiles();
if (list != null) {
for (File f : list) {
if (!filter.accept(current, f.getName())) {
continue;
}
File newFile = new File(target, f.getAbsolutePath().substring(sourceRoot.length() + 1));
if (f.isDirectory()) {
if (!(newFile.exists() || newFile.mkdirs())) {
throw new IOException(String.format("Unable create directory '%s'. ", newFile.getAbsolutePath()));
}
if (!f.equals(target)) {
q.push(f);
}
} else {
if (nio) {
nioCopyFile(f, newFile, replaceIfExists);
} else {
copyFile(f, newFile, replaceIfExists);
}
}
}
}
}
} else {
File parent = target.getParentFile();
if (!(parent.exists() || parent.mkdirs())) {
throw new IOException(String.format("Unable create directory '%s'. ", parent.getAbsolutePath()));
}
if (nio) {
nioCopyFile(source, target, replaceIfExists);
} else {
copyFile(source, target, replaceIfExists);
}
}
}
private static void copyFile(File source, File target, boolean replaceIfExists) throws IOException {
if (!target.createNewFile()) {
if (target.exists() && !replaceIfExists) {
throw new IOException(String.format("File '%s' already exists. ", target.getAbsolutePath()));
}
}
byte[] b = new byte[8192];
try (FileInputStream in = new FileInputStream(source); FileOutputStream out = new FileOutputStream(target)) {
int r;
while ((r = in.read(b)) != -1) {
out.write(b, 0, r);
}
}
}
private static void nioCopyFile(File source, File target, boolean replaceIfExists) throws IOException {
if (!target.createNewFile()) // atomic
{
if (target.exists() && !replaceIfExists) {
throw new IOException(String.format("File '%s' already exists. ", target.getAbsolutePath()));
}
}
try (
FileInputStream sourceStream = new FileInputStream(source);
FileOutputStream targetStream = new FileOutputStream(target);
FileChannel sourceChannel = sourceStream.getChannel();
FileChannel targetChannel = targetStream.getChannel()
) {
final long size = sourceChannel.size();
long transferred = 0L;
while (transferred < size) {
transferred += targetChannel.transferFrom(sourceChannel, transferred, (size - transferred));
}
}
}
public static List<File> list(File dir, FilenameFilter filter) {
if (!dir.isDirectory()) {
throw new IllegalArgumentException("Not a directory. ");
}
if (filter == null) {
filter = ANY_FILTER;
}
List<File> files = new ArrayList<>();
LinkedList<File> q = new LinkedList<>();
q.add(dir);
while (!q.isEmpty()) {
File current = q.pop();
File[] list = current.listFiles();
if (list != null) {
for (File f : list) {
if (!filter.accept(current, f.getName())) {
continue;
}
if (f.isDirectory()) {
q.push(f);
} else {
files.add(f);
}
}
}
}
return files;
}
public static String countFileHash(File file, MessageDigest digest) throws IOException {
byte[] b = new byte[8192];
try (DigestInputStream dis = new DigestInputStream(new FileInputStream(file), digest)) {
while (dis.read(b) != -1) ;
return toHex(digest.digest());
}
}
private static final char[] HEX = "0123456789abcdef".toCharArray();
public static String toHex(byte[] hash) {
StringBuilder b = new StringBuilder();
for (int i = 0; i < hash.length; i++) {
b.append(HEX[(hash[i] >> 4) & 0x0f]);
b.append(HEX[hash[i] & 0x0f]);
}
return b.toString();
}
/**
* Detects and returns {@code Path} to file by name pattern.
*
* @param pattern
* file name pattern
* @param folder
* path to folder that contains project sources
* @return pom.xml path
* @throws java.io.IOException
* if an I/O error is thrown while finding pom.xml
* @throws IllegalArgumentException
* if pom.xml not found
*/
public static File findFile(String pattern, File folder) throws IOException {
Finder finder = new Finder(pattern);
Files.walkFileTree(folder.toPath(), EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, finder);
if (finder.getFirstMatchedFile() == null) {
throw new IllegalArgumentException("File not found.");
}
return finder.getFirstMatchedFile().toFile();
}
/** A {@code FileVisitor} that finds first file that match the specified pattern. */
private static class Finder extends SimpleFileVisitor<Path> {
private final PathMatcher matcher;
private Path firstMatchedFile;
Finder(String pattern) {
matcher = FileSystems.getDefault().getPathMatcher("glob:" + pattern);
}
/** {@inheritDoc} */
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Path fileName = file.getFileName();
if (fileName != null && matcher.matches(fileName)) {
firstMatchedFile = file;
return TERMINATE;
}
return CONTINUE;
}
/** Returns the first matched {@link java.nio.file.Path}. */
Path getFirstMatchedFile() {
return firstMatchedFile;
}
}
}