/******************************************************************************* | |
* Copyright (c) 2008 The University of York. | |
* This program and the accompanying materials | |
* are made available under the terms of the Eclipse Public License 2.0 | |
* which is available at https://www.eclipse.org/legal/epl-2.0/ | |
* | |
* Contributors: | |
* Dimitrios Kolovos - initial API and implementation | |
******************************************************************************/ | |
package org.eclipse.epsilon.common.util; | |
import static java.nio.file.attribute.PosixFilePermission.OWNER_EXECUTE; | |
import static java.nio.file.attribute.PosixFilePermission.OWNER_READ; | |
import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE; | |
import static java.security.AccessController.doPrivileged; | |
import java.io.BufferedReader; | |
import java.io.File; | |
import java.io.FileInputStream; | |
import java.io.FileNotFoundException; | |
import java.io.FileOutputStream; | |
import java.io.FileReader; | |
import java.io.FileWriter; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.io.OutputStream; | |
import java.io.UnsupportedEncodingException; | |
import java.net.URISyntaxException; | |
import java.net.URLDecoder; | |
import java.nio.channels.FileChannel; | |
import java.nio.file.DirectoryStream; | |
import java.nio.file.FileAlreadyExistsException; | |
import java.nio.file.FileSystems; | |
import java.nio.file.Files; | |
import java.nio.file.InvalidPathException; | |
import java.nio.file.Path; | |
import java.nio.file.Paths; | |
import java.nio.file.attribute.FileAttribute; | |
import java.nio.file.attribute.PosixFilePermission; | |
import java.nio.file.attribute.PosixFilePermissions; | |
import java.util.*; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
public class FileUtil { | |
private static Logger logger = LoggerFactory.getLogger(FileUtil.class); | |
// temporary directory location | |
private static final Path tmpdir = Paths.get(System.getProperty("java.io.tmpdir")); | |
private static final boolean isPosix = FileSystems.getDefault().supportedFileAttributeViews().contains("posix"); | |
// default file and directory permissions (lazily initialized) | |
private static class PosixPermissions { | |
static final FileAttribute<Set<PosixFilePermission>> filePermissions = PosixFilePermissions | |
.asFileAttribute(EnumSet.of(OWNER_READ, OWNER_WRITE)); | |
static final FileAttribute<Set<PosixFilePermission>> dirPermissions = PosixFilePermissions | |
.asFileAttribute(EnumSet.of(OWNER_READ, OWNER_WRITE, OWNER_EXECUTE)); | |
} | |
private FileUtil() { | |
} | |
public static Path getCurrentDirectory() { | |
return Paths.get(".").toAbsolutePath().normalize(); | |
} | |
public static void setFileContents(String str, File file) throws Exception { | |
try (FileWriter writer = new FileWriter(file)) { | |
writer.append(str); | |
writer.flush(); | |
} | |
} | |
public static String replaceExtension(String filename, String newExtension) { | |
int dotIndex = filename.lastIndexOf('.'); | |
if (dotIndex > -1) { | |
filename = filename.substring(0, dotIndex + 1) + newExtension; | |
} | |
return filename; | |
} | |
public static String removeExtension(String filename) { | |
int dotIndex = filename.lastIndexOf('.'); | |
if (dotIndex > -1) { | |
filename = filename.substring(0, dotIndex); | |
} | |
return filename; | |
} | |
public static String getFileName(String path) { | |
return getFileName(path, true); | |
} | |
public static String getFileName(String path, boolean includeExtension) { | |
String filename = path.substring(path.replace("\\", "/").lastIndexOf('/') + 1); | |
if (!includeExtension) { | |
filename = removeExtension(filename); | |
} | |
return filename; | |
} | |
/** | |
* Copied from @linkplain{https://stackoverflow.com/a/3571239/5870336} | |
* | |
* @param filename | |
* @return | |
* @since 1.6 | |
*/ | |
public static String getExtension(String filename) { | |
String extension = ""; | |
int dotIndex = filename.lastIndexOf('.'); | |
if (dotIndex > Math.max(filename.lastIndexOf('/'), filename.lastIndexOf('\\'))) { | |
extension = filename.substring(dotIndex + 1); | |
} | |
return extension; | |
} | |
public static String getFileContents(File file) throws Exception { | |
final StringBuffer buffer = new StringBuffer(); | |
final String lineSeparator = System.getProperty("line.separator"); | |
for (String line : getFileLineContents(file)) { | |
buffer.append(line); | |
buffer.append(lineSeparator); | |
} | |
return buffer.toString(); | |
} | |
public static Collection<String> getFileLineContents(File file) throws Exception { | |
try (BufferedReader bufferedReader = new BufferedReader(new FileReader(file))) { | |
final List<String> lines = new LinkedList<>(); | |
String line = bufferedReader.readLine(); | |
while (line != null) { | |
lines.add(line); | |
line = bufferedReader.readLine(); | |
} | |
return lines; | |
} | |
} | |
public static String getAbsolutePath(String basePath, String relativePath) { | |
File file = new File(relativePath); | |
if (!file.isAbsolute()) { | |
file = new File(basePath, relativePath); | |
} | |
return file.getAbsolutePath(); | |
} | |
/** | |
* Gets a file stored as a resource in a jar. Since not all users of the file | |
* can read from inside jars, we get the file as a stream and create a temp file | |
* with its contents. | |
* | |
* @param name | |
* @param relativeTo | |
* @return | |
*/ | |
public static File getFile(String name, Class<?> relativeTo) { | |
logger.info("getFile {}@{}", name, relativeTo.getSimpleName()); | |
InputStream is = relativeTo.getResourceAsStream(name); | |
return inputStreamToFile(is, name); | |
} | |
@Deprecated | |
public static File getDirectoryOf(Class<?> clazz) { | |
return getFile(clazz.getSimpleName() + ".class", clazz).getParentFile(); | |
} | |
@Deprecated | |
public static String getPath(String name, Class<?> relativeTo) { | |
return getFile(name, relativeTo).getAbsolutePath(); | |
} | |
public static void checkFileExists(final File file) throws FileNotFoundException { | |
if (!file.exists()) { | |
throw new FileNotFoundException("File " + file.getPath() + " does not exist"); | |
} | |
} | |
public static File createTempFile(String name) { | |
return createTempFile(name, "tmp"); | |
} | |
public static File createTempFile(String name, String extension) { | |
Path tmpFile; | |
try { | |
// tmpFile = Files.createTempFile(name, extension); | |
tmpFile = createTempFile(null, name, extension); | |
} catch (IOException e) { | |
throw new IllegalArgumentException("Could not create temp file ", e); | |
} | |
return tmpFile.toFile(); | |
} | |
public static File createTempDir(String name) { | |
return createTempDir(name, false); | |
} | |
public static File createTempDir(String name, boolean reuse) { | |
Path tmpFile = null; | |
if (reuse) { | |
DirectoryStream.Filter<Path> filter = new DirectoryStream.Filter<Path>() { | |
@Override | |
public boolean accept(Path file) throws IOException { | |
return (Files.isDirectory(file) && file.getFileName().startsWith(Paths.get(name))); | |
} | |
}; | |
Iterator<Path> it; | |
try { | |
it = Files.newDirectoryStream(tmpdir, filter).iterator(); | |
tmpFile = it.hasNext() ? it.next() : null; | |
} catch (IOException e) { | |
// TODO Auto-generated catch block | |
e.printStackTrace(); | |
} | |
} | |
if (tmpFile == null) { | |
try { | |
tmpFile = createTempDirectory(null, name); | |
} catch (IOException e) { | |
throw new IllegalArgumentException("Could not create temp directory ", e); | |
} | |
} | |
return tmpFile.toFile(); | |
} | |
public static File copyToTemp(File srcFile) throws IOException { | |
File tmpFile = File.createTempFile("filecompare", "tmp"); | |
if (srcFile.isDirectory()) { | |
tmpFile.delete(); | |
tmpFile.mkdir(); | |
} | |
copy(srcFile, tmpFile); | |
return tmpFile; | |
} | |
public static void copy(File srcFile, File dstFile) throws IOException { | |
if (srcFile.isDirectory()) { | |
dstFile.mkdir(); | |
for (File entry : srcFile.listFiles()) { | |
copy(entry, new File(dstFile, entry.getName())); | |
} | |
} else { | |
// Based on the second answer in http://stackoverflow.com/questions/106770. | |
FileInputStream isSrc = null; | |
FileOutputStream osDst = null; | |
FileChannel chSrc = null; | |
FileChannel chDst = null; | |
try { | |
isSrc = new FileInputStream(srcFile); | |
osDst = new FileOutputStream(dstFile); | |
chSrc = isSrc.getChannel(); | |
chDst = osDst.getChannel(); | |
final long srcBytes = srcFile.length(); | |
long transferred = 0; | |
while (transferred < srcBytes) { | |
transferred += chDst.transferFrom(chSrc, transferred, srcBytes); | |
chDst.position(transferred); | |
} | |
} finally { | |
if (chDst != null) { | |
chDst.close(); | |
} else if (osDst != null) { | |
osDst.close(); | |
} | |
if (chSrc != null) { | |
chSrc.close(); | |
} else if (isSrc != null) { | |
isSrc.close(); | |
} | |
} | |
} | |
} | |
public static Set<String> listFilesAsSet(File fileExpected) { | |
return new HashSet<>(Arrays.asList(fileExpected.list())); | |
} | |
/** | |
* We implement our own comparison algorithm here, so we don't need Eclipse | |
* Compare to compute differences, but rather only to show them in the UI. | |
*/ | |
public static boolean sameContents(File fileExpected, File fileActual, Set<String> ignoreFilenames) | |
throws IOException { | |
if (fileExpected.isDirectory() != fileActual.isDirectory()) { | |
// One is a file, the other is a directory: not the same | |
return false; | |
} | |
if (fileExpected.isDirectory()) { | |
// Both are directories: they should contain the same filenames, | |
// and each pair should have the same contents | |
final Set<String> expectedFilenames = listFilesAsSet(fileExpected); | |
final Set<String> actualFilenames = listFilesAsSet(fileActual); | |
expectedFilenames.removeAll(ignoreFilenames); | |
actualFilenames.removeAll(ignoreFilenames); | |
if (!expectedFilenames.equals(actualFilenames)) { | |
return false; | |
} | |
for (String filename : expectedFilenames) { | |
final File expectedEntry = new File(fileExpected, filename); | |
final File actualEntry = new File(fileActual, filename); | |
if (!sameContents(expectedEntry, actualEntry, ignoreFilenames)) { | |
return false; | |
} | |
} | |
return true; | |
} else { | |
if (fileExpected.length() != fileActual.length()) { | |
// Different length: no need to read the files | |
return false; | |
} | |
try (FileInputStream isExpected = new FileInputStream(fileExpected)) { | |
try (FileInputStream isActual = new FileInputStream(fileActual)) { | |
return sameContents(isExpected, isActual); | |
} | |
} | |
} | |
} | |
public static boolean sameContents(InputStream isExpected, InputStream isActual) throws IOException { | |
int chExpected, chActual; | |
do { | |
chExpected = isExpected.read(); | |
chActual = isActual.read(); | |
} while (chExpected == chActual && chExpected > 0 && chActual > 0); | |
return chExpected == chActual; | |
} | |
/** | |
* WARNIING: Use with caution! Deletes all contents and sub-directories of the | |
* specified path. | |
* | |
* @param dir The absolute path to the directory. | |
* @throws IOException | |
* @since 1.6 | |
*/ | |
public static void deleteDirectory(String dir) throws IOException { | |
Path path = Paths.get(dir); | |
if (Files.exists(path)) { | |
Files.walk(path).map(Path::toFile).sorted((o1, o2) -> -o1.compareTo(o2)).forEach(File::delete); | |
} | |
} | |
/** | |
* Reads entire directory recursively, mapping the contents of each file as a | |
* string to its path. | |
* | |
* @param dir The root directory. | |
* @return The contents of each file in the directory and its subdirectories. | |
* @throws IOException | |
* @since 1.6 | |
*/ | |
public static Map<Path, String> readDirectory(String dir) throws IOException { | |
Map<Path, String> contents = new HashMap<>(); | |
for (Path path : ((Iterable<Path>) Files.walk(Paths.get(dir))::iterator)) { | |
if (Files.isRegularFile(path)) { | |
contents.put(path, new String(Files.readAllBytes(path))); | |
} | |
} | |
return contents; | |
} | |
private static File inputStreamToFile(InputStream inputStream, String name) { | |
logger.debug("inputStreamToFile {}", name); | |
OutputStream outputStream = null; | |
String prefix = name; | |
String suffix = ""; | |
Path dirStructure = null; | |
// Name might be a path, get the file name, remove it | |
if (name.contains("/")) { | |
dirStructure = Paths.get(tmpdir.toString(), name.substring(0, name.lastIndexOf("/")), "/"); | |
dirStructure.toFile().mkdirs(); | |
name = name.substring(name.lastIndexOf("/") + 1); | |
logger.debug("inputStreamToFile {}", name); | |
} | |
String[] parts = name.split("\\."); | |
if (parts.length == 2) { | |
prefix = parts[0]; | |
suffix = "." + parts[1]; | |
logger.debug("splitting name and extension: {} - {}", prefix, suffix); | |
} | |
File file = null; | |
try { | |
Path tempFile; | |
try { | |
// tempFile = Files.createTempFile(prefix, sufix); | |
tempFile = createTempFile(dirStructure, prefix, suffix); | |
} catch (IllegalArgumentException e) { | |
logger.error("Error creating temp file: {}-{}", prefix, suffix, e); | |
throw e; | |
} | |
outputStream = new FileOutputStream(tempFile.toFile()); | |
int read = 0; | |
byte[] bytes = new byte[1024]; | |
logger.debug("{} bytes available", inputStream.available()); | |
while ((read = inputStream.read(bytes)) != -1) { | |
logger.debug("{} bytes read from input", read); | |
outputStream.write(bytes, 0, read); | |
} | |
logger.debug("Copying data into temp file Done!"); | |
// Runtime.getRuntime().addShutdownHook(new Thread() { | |
// @Override | |
// public void run() { | |
// try { | |
// Files.delete(tempFile); | |
// } catch (IOException e) { | |
// e.printStackTrace(); | |
// } | |
// } | |
// }); | |
file = tempFile.toFile(); | |
} catch (IOException e) { | |
logger.error("Unable to create File for resoruce", e); | |
e.printStackTrace(); | |
} finally { | |
if (inputStream != null) { | |
try { | |
inputStream.close(); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
} | |
if (outputStream != null) { | |
try { | |
outputStream.close(); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
} | |
} | |
return file; | |
} | |
static Path createTempDirectory(Path dir, String prefix, FileAttribute<?>... attrs) throws IOException { | |
return create(dir, prefix, null, true, attrs); | |
} | |
static Path createTempFile(Path dir, String prefix, String suffix, FileAttribute<?>... attrs) throws IOException { | |
return create(dir, prefix, suffix, false, attrs); | |
} | |
/** | |
* Creates a file or directory in in the given given directory (or in the | |
* temporary directory if dir is {@code null}). | |
*/ | |
private static Path create(Path dir, String prefix, String suffix, boolean createDirectory, | |
FileAttribute<?>[] attrs) throws IOException { | |
if (prefix == null) | |
prefix = ""; | |
if (suffix == null) | |
suffix = (createDirectory) ? "" : ".tmp"; | |
if (dir == null) | |
dir = tmpdir; | |
// in POSIX environments use default file and directory permissions | |
// if initial permissions not given by caller. | |
if (isPosix && (dir.getFileSystem() == FileSystems.getDefault())) { | |
if (attrs.length == 0) { | |
// no attributes so use default permissions | |
attrs = new FileAttribute<?>[1]; | |
attrs[0] = (createDirectory) ? PosixPermissions.dirPermissions : PosixPermissions.filePermissions; | |
} else { | |
// check if posix permissions given; if not use default | |
boolean hasPermissions = false; | |
for (int i = 0; i < attrs.length; i++) { | |
if (attrs[i].name().equals("posix:permissions")) { | |
hasPermissions = true; | |
break; | |
} | |
} | |
if (!hasPermissions) { | |
FileAttribute<?>[] copy = new FileAttribute<?>[attrs.length + 1]; | |
System.arraycopy(attrs, 0, copy, 0, attrs.length); | |
attrs = copy; | |
attrs[attrs.length - 1] = (createDirectory) ? PosixPermissions.dirPermissions | |
: PosixPermissions.filePermissions; | |
} | |
} | |
} | |
// loop generating random names until file or directory can be created | |
SecurityManager sm = System.getSecurityManager(); | |
//for (;;) { | |
Path f; | |
try { | |
f = generatePath(prefix, suffix, dir); | |
} catch (InvalidPathException e) { | |
// don't reveal temporary directory location | |
if (sm != null) | |
throw new IllegalArgumentException("Invalid prefix or suffix"); | |
throw e; | |
} | |
try { | |
if (createDirectory) { | |
return Files.createDirectory(f, attrs); | |
} else { | |
return Files.createFile(f, attrs); | |
} | |
} catch (SecurityException e) { | |
// don't reveal temporary directory location | |
if (dir == tmpdir && sm != null) | |
throw new SecurityException("Unable to create temporary file or directory"); | |
throw e; | |
} catch (FileAlreadyExistsException e) { | |
// FIXME Add the file deletion so we dont need this. | |
// FIXME or make each test use a different folder... | |
logger.debug("File found, reusing"); | |
return f; | |
} | |
//} | |
} | |
private static Path generatePath(String prefix, String suffix, Path dir) { | |
Path name = dir.getFileSystem().getPath(prefix + suffix); | |
// the generated name should be a simple file name | |
if (name.getParent() != null) | |
throw new IllegalArgumentException("Invalid prefix or suffix"); | |
return dir.resolve(name); | |
} | |
} |