package org.netbeans.gradle.project.util;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Collection;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jtrim.cancel.CancellationToken;
import org.jtrim.event.ListenerRef;
import org.jtrim.utils.ExceptionHelper;
import org.netbeans.api.project.Project;
import org.netbeans.gradle.project.api.event.NbListenerRefs;
import org.netbeans.gradle.project.others.ChangeLFPlugin;
import org.openide.filesystems.FileChangeAdapter;
import org.openide.filesystems.FileChangeListener;
import org.openide.filesystems.FileEvent;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
public final class NbFileUtils {
private static final Logger LOGGER = Logger.getLogger(NbFileUtils.class.getName());
private static final int FILE_BUFFER_SIZE = 8 * 1024;
private static boolean isLineEndingByte(byte ch) {
return ch == 13 || ch == 10;
}
public static String tryGetLineSeparatorForTextFile(Path file) {
if (!Files.isRegularFile(file)) {
return null;
}
// This method won't work with every encoding but we save properties
// file with UTF-8, with which this should be fine.
byte prevChar = 0;
byte[] buffer = new byte[FILE_BUFFER_SIZE];
try (InputStream input = Files.newInputStream(file)) {
int readCount = input.read(buffer);
while (readCount > 0) {
for (int i = 0; i < readCount; i++) {
byte ch = buffer[i];
if (isLineEndingByte(prevChar)) {
switch (ch) {
case 10:
return prevChar == 13 ? "\r\n" : "\n";
case 13:
// \n\r is not valid, returning null is safer.
return prevChar == 10 ? null : "\r";
default:
return Character.toString((char)prevChar);
}
}
prevChar = ch;
}
input.read(buffer);
}
return isLineEndingByte(prevChar) ? Character.toString((char)prevChar) : null;
} catch (IOException ex) {
LOGGER.log(Level.INFO, "Failed to read config file for determining its line separator", ex);
return null;
}
}
public static String tryMakeRelative(File parent, File file) {
File normParent = FileUtil.normalizeFile(parent);
File normFile = FileUtil.normalizeFile(file);
if (normParent == null || normFile == null) {
return null;
}
FileObject parentObj = FileUtil.toFileObject(normParent);
FileObject fileObj = FileUtil.toFileObject(normFile);
if (fileObj == null || parentObj == null) {
return null;
}
String relPath = FileUtil.getRelativePath(parentObj, fileObj);
return relPath != null ? relPath.replace("/", File.separator) : null;
}
private static boolean isSafeChar(char ch) {
if (ch >= 'A' && ch <= 'Z') return true;
if (ch >= 'a' && ch <= 'z') return true;
if (ch >= '0' && ch <= '9') return true;
return "_-$. ".indexOf(ch) >= 0;
}
public static String toSafeFileName(String name) {
ExceptionHelper.checkNotNullArgument(name, "name");
StringBuilder result = new StringBuilder(name.length());
for (int i = 0; i < name.length(); i++) {
char ch = name.charAt(i);
result.append(isSafeChar(ch) ? ch : "_");
}
return result.toString();
}
public static ListenerRef addDirectoryContentListener(
final FileObject dir,
final Runnable listener) {
return addDirectoryContentListener(dir, false, listener);
}
public static ListenerRef addDirectoryContentListener(
final FileObject dir,
final boolean listenForDirs,
final Runnable listener) {
ExceptionHelper.checkNotNullArgument(dir, "dir");
ExceptionHelper.checkNotNullArgument(listener, "listener");
final FileChangeListener fileChangeListener = new FileChangeAdapter() {
@Override
public void fileFolderCreated(FileEvent fe) {
if (listenForDirs) {
listener.run();
}
}
@Override
public void fileDeleted(FileEvent fe) {
listener.run();
}
@Override
public void fileDataCreated(FileEvent fe) {
listener.run();
}
};
dir.addFileChangeListener(fileChangeListener);
return NbListenerRefs.fromRunnable(new Runnable() {
@Override
public void run() {
dir.removeFileChangeListener(fileChangeListener);
}
});
}
public static URL getSafeURL(String url) {
try {
return new URL(url);
} catch (MalformedURLException ex) {
throw new RuntimeException(ex);
}
}
public static String getFileNameStr(Path path) {
Path result = path.getFileName();
return result != null ? result.toString() : "";
}
public static FileObject asArchiveOrDir(File file) {
FileObject result = FileUtil.toFileObject(file);
return asArchiveOrDir(result);
}
public static FileObject asArchiveOrDir(FileObject file) {
if (file == null) {
return null;
}
if (FileUtil.isArchiveFile(file)) {
return FileUtil.getArchiveRoot(file);
}
else {
return file;
}
}
public static FileObject getFileFromASubDir(FileObject root, String fileName) {
File rootDir = FileUtil.toFile(root);
if (rootDir == null) {
return null;
}
File[] subDirs = rootDir.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.isDirectory();
}
});
if (subDirs == null) {
return null;
}
for (File dir: subDirs) {
File srcFileName = new File(dir, fileName);
if (srcFileName.isFile()) {
return asArchiveOrDir(FileUtil.toFileObject(srcFileName));
}
}
return null;
}
public static Path asPath(FileObject fileObj) {
return fileObj != null ? asPath(FileUtil.toFile(fileObj)) : null;
}
public static Path asPath(File file) {
try {
return file != null ? file.toPath() : null;
} catch (InvalidPathException ex) {
return null;
}
}
public static File asFile(FileObject fileObj) {
return fileObj != null ? FileUtil.toFile(fileObj) : null;
}
public static FileObject asFileObject(File file) {
return file != null ? FileUtil.toFileObject(file) : null;
}
public static boolean isParentOrSame(File parent, File child) {
for (File current = child; current != null; current = current.getParentFile()) {
if (current.equals(parent)) {
return true;
}
}
return false;
}
public static void deleteDirectory(final CancellationToken cancelToken, Path directory) throws IOException {
ExceptionHelper.checkNotNullArgument(cancelToken, "cancelToken");
ExceptionHelper.checkNotNullArgument(directory, "directory");
Files.walkFileTree(directory, new FileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
cancelToken.checkCanceled();
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
cancelToken.checkCanceled();
Files.deleteIfExists(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
cancelToken.checkCanceled();
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
cancelToken.checkCanceled();
Files.deleteIfExists(dir);
return FileVisitResult.CONTINUE;
}
});
}
public static void deleteDirectory(CancellationToken cancelToken, FileObject directory) throws IOException {
ExceptionHelper.checkNotNullArgument(cancelToken, "cancelToken");
ExceptionHelper.checkNotNullArgument(directory, "directory");
File asFile = FileUtil.toFile(directory);
if (asFile == null) {
throw new FileNotFoundException("Cannot find " + directory);
}
deleteDirectory(cancelToken, asFile.toPath());
}
public static void writeLinesToFile(
Path file,
Collection<String> lines,
Charset charset) throws IOException {
writeLinesToFile(file, lines, charset, (Project)null);
}
public static void writeLinesToFile(
Path file,
Collection<String> lines,
Charset charset,
Project ownerProject) throws IOException {
String lineSeparator = ChangeLFPlugin.getPreferredLineSeparator(ownerProject);
writeLinesToFile(file, lines, charset, lineSeparator);
}
public static void writeLinesToFile(
Path file,
Collection<String> lines,
Charset charset,
String lineSeparator) throws IOException {
ExceptionHelper.checkNotNullArgument(file, "file");
ExceptionHelper.checkNotNullElements(lines, "lines");
ExceptionHelper.checkNotNullArgument(charset, "charset");
if (lineSeparator == null) {
Files.write(file, lines, charset);
}
else {
byte[] lineSeparatorBytes = lineSeparator.getBytes(charset);
try (OutputStream fileOutput = Files.newOutputStream(file);
OutputStream output = new BufferedOutputStream(fileOutput, 16 * 1024)) {
for (String line: lines) {
output.write(line.getBytes(charset));
output.write(lineSeparatorBytes);
}
}
}
}
public static Path toSafeRealPath(Path src) {
try {
return src.toRealPath();
} catch (IOException ex) {
return src.normalize();
}
}
private NbFileUtils() {
throw new AssertionError();
}
}