/*
* Copyright 2003-2011 JetBrains s.r.o.
*
* 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 jetbrains.mps.util;
import jetbrains.mps.vfs.IFile;
import jetbrains.mps.vfs.path.Path;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.BufferedReader;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Reader;
import java.nio.charset.Charset;
import java.text.MessageFormat;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* @author Kostik
*/
public class FileUtil {
private static final Logger LOG = LogManager.getLogger(FileUtil.class);
private static final String[] IGNORED_DIRS = new String[]{".svn", ".git", "_svn"};
public static final String DEFAULT_CHARSET_NAME = "UTF-8";
public static final Charset DEFAULT_CHARSET = Charset.forName(DEFAULT_CHARSET_NAME);
private static final String MPSTEMP = "mpstemp";
private static final char DOT = '.';
public static File createTmpDir() {
File tmp = getTempDir();
for (int i = 0;; ++i) {
if (!new File(tmp, MPSTEMP + i).exists()) {
File tmpDir = new File(tmp, MPSTEMP + i);
boolean result = tmpDir.mkdir();
if (!result) {
throw new IllegalStateException("Could not create a directory " + tmpDir);
}
return tmpDir;
}
}
}
public static File createTmpFile() {
File tmp = getTempDir();
int i = 0;
while (true) {
if (!new File(tmp, MPSTEMP + i).exists()) {
break;
}
i++;
}
File result = new File(tmp, MPSTEMP + i);
try {
result.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
public static File getTempDir() {
return new File(System.getProperty("java.io.tmpdir"));
}
public static void jar(File dir, final Manifest mf, File to) {
new Packer() {
@Override
protected ZipOutputStream createDeflaterStream(FileOutputStream fos) throws Exception {
return new JarOutputStream(fos, mf);
}
}.pack(dir, to);
}
@SuppressWarnings({"UnusedDeclaration"})
public static void zip(File dir, File to) {
new Packer() {
@Override
protected ZipOutputStream createDeflaterStream(FileOutputStream fos) throws Exception {
return new ZipOutputStream(fos);
}
}.pack(dir, to);
}
public static void zip(Map<String, File> entries, File to) {
new Packer() {
@Override
protected ZipOutputStream createDeflaterStream(FileOutputStream fos) throws Exception {
return new ZipOutputStream(fos);
}
}.pack(entries, to);
}
public static void copyDir(File what, File to) {
assert what.isDirectory();
if (!to.exists()) {
to.mkdirs();
}
for (File f : what.listFiles()) {
if (f.isDirectory()) {
if (isIgnoredDir(f.getName())) continue;
File fCopy = new File(to, f.getName());
if (!fCopy.exists()) {
fCopy.mkdir();
}
copyDir(f, fCopy);
}
if (f.isFile()) {
copyFile(f, to);
}
}
}
public static void copyFile(File f, File to) {
try {
copyFileChecked(f, to);
} catch (IOException e) {
e.printStackTrace();
}
}
public static void copyFileChecked(File f, File to) throws IOException {
FileInputStream is = new FileInputStream(f);
File target;
if (to.isDirectory()) {
target = new File(to, f.getName());
} else {
target = to;
}
if (!to.getParentFile().exists()) {
to.getParentFile().mkdirs();
}
OutputStream os = new FileOutputStream(target);
os.write(ReadUtil.read(is));
is.close();
os.close();
}
public static String getCanonicalPath(File file) {
if (file == null) {
return null;
}
try {
return file.getCanonicalPath();
} catch (IOException e) {
return file.getAbsolutePath();
}
}
public static String getCanonicalPath(String path) {
if (path == null) {
path = "";
}
path = normalize0(path, Path.SYSTEM_SEPARATOR);
File file = new File(path);
return getCanonicalPath(file);
}
@NotNull
public static String normalize(@NotNull String path) {
return stripLastSlashes(normalize0(getUnixPath(path), Path.UNIX_SEPARATOR));
}
// poor version of normalization
// does not consider '..'; will be provided in the future release within new vfs API
private static String normalize0(@NotNull String path, @NotNull String separator) {
path = path.replaceAll("/+", "/").replaceAll("\\\\+", "\\\\");
if (path.endsWith(separator + DOT)) {
path = path.substring(0, path.length() - 1);
}
if (path.equals("" + DOT)) {
return "";
}
// four backslashes are for windows file separator (escaping it twice), and two are escaping the dot
path = path.replaceAll("\\\\\\.\\\\", "\\\\").replaceAll("/\\./", "/");
return path;
}
public static boolean delete(File root) {
boolean result = true;
if (root.isDirectory()) {
for (File child : root.listFiles()) {
result = delete(child) && result;
}
}
// !result means one of children was not deleted, hence you may not delete this directory
return result && root.delete();
}
/**
* deletes the file and all its parents above which happen to be empty after this file's removal
* @return true iff the file has been removed
*/
public static boolean deleteWithAllEmptyDirs(@NotNull IFile file) {
if (file.exists()) { // exists is vital, see VirtualFile assert for file existence [AP]
do {
file.delete();
file = file.getParent();
} while (file != null && file.getChildren().isEmpty());
return true;
}
return false;
}
public static boolean clear(File dir) {
File[] files = dir.listFiles();
if (files == null) return true;
boolean result = true;
for (File f : files) {
boolean r = delete(f);
result = result && r;
}
return result;
}
public static long getNewestFileTime(File dir) {
return getNewestFileTime(dir, true);
}
public static long getNewestFileTime(File dir, boolean recursive) {
File[] files = dir.listFiles();
if (files == null) {
return dir.lastModified();
}
long result = dir.lastModified();
for (File file : files) {
if (isIgnoredDir(file.getName())) {
continue;
}
if (recursive) {
result = Math.max(result, getNewestFileTime(file));
} else {
result = Math.max(result, file.lastModified());
}
}
return result;
}
public static void writeFile(final File file, final String content) throws IOException {
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
if (file.exists()) {
try {
String existingContents = FileUtil.read(file);
if (existingContents.equals(content)) {
return;
}
} catch (RuntimeException ex) {
/* ignore */
}
file.delete();
}
boolean fileCreated = false;
IOException lastExc = null;
for (int i = 1; i <= 20; i++) {
try {
file.createNewFile();
fileCreated = true;
break;
} catch (IOException ex) {
lastExc = ex;
//sometimes:
//java.io.IOException: Access is denied
//at java.io.WinNTFileSystem.createFileExclusively(Native Method)
//at java.io.File.createNewFile(File.java:850)
// so we'll try 5(20) times
}
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
//ok
}
}
if (fileCreated) {
FileUtil.write(file, content);
} else {
throw lastExc == null ? new IOException("Can't create " + file.getPath()) : lastExc;
}
}
/*
* use writeFile
*/
@Deprecated
public static void write(File file, String content) {
PrintWriter writer = null;
try {
writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(file), FileUtil.DEFAULT_CHARSET));
writer.print(content);
writer.flush();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (writer != null) {
writer.close();
}
}
}
public static void write(File file, byte[] content) {
OutputStream writer = null;
try {
writer = new FileOutputStream(file);
writer.write(content);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
FileUtil.closeFileSafe(writer);
}
}
public static String read(File file) {
try {
return read(new FileReader(file));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static String read(File file, String charset) {
try {
return read(new InputStreamReader(new FileInputStream(file), charset));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static String read(Reader reader) {
BufferedReader r = null;
try {
r = new BufferedReader(reader);
StringBuilder result = new StringBuilder();
String line;
while ((line = r.readLine()) != null) {
result.append(line).append("\n");
}
return result.toString();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
FileUtil.closeFileSafe(r);
}
}
public static String readLine(Reader reader, int lineNo) {
BufferedReader r = null;
try {
r = new BufferedReader(reader);
String line = null;
int currentLine = 0;
while ((line = r.readLine()) != null) {
if (currentLine == lineNo) {
return line;
}
currentLine++;
}
return null;
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
if (r != null) {
r.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static boolean isParent(File parent, File child) {
if (!parent.isDirectory()) {
return false;
}
if (parent.equals(child)) return true;
for (File f : parent.listFiles()) {
if (isParent(f, child)) return true;
}
return false;
}
public static File getMaxContainingFile(List<File> files) {
if (files.size() == 0) return null;
Iterator<File> fileIterator = files.iterator();
File max = fileIterator.next();
while (fileIterator.hasNext()) {
if (max == null) return null;
max = getMaxContainingFile(max, fileIterator.next());
}
return max;
}
public static File getMaxContainingFile(File file1, File file2) {
if (isParentUp(file1, file2)) return file1;
if (isParentUp(file2, file1)) return file2;
File parent1 = file1.getParentFile();
File parent2 = file2.getParentFile();
if ((parent1 == null) && (parent2 == null)) {
return null;
} else if (parent1 == null) {
return getMaxContainingFile(file1, parent2);
} else if (parent2 == null) {
return getMaxContainingFile(parent1, file2);
}
return getMaxContainingFile(parent1, parent2);
}
public static boolean isParentUp(File parent, File child) {
if (!parent.isDirectory()) {
return false;
}
if (parent.getPath().equals(child.getPath())) return true;
File parentOfChild = child.getParentFile();
if (parentOfChild == null) return false;
return isParentUp(parent, parentOfChild);
}
public static boolean isIgnoredDir(String name) {
for (String ignoredDir : IGNORED_DIRS) {
if (ignoredDir.equals(name)) {
return true;
}
}
return false;
}
@NotNull
public static String getUnixPath(@NotNull String path) {
return path.replace(Path.WIN_SEPARATOR, Path.UNIX_SEPARATOR);
}
public static boolean isAncestor(@NotNull String ancestorPath, @NotNull String path) {
ancestorPath = getUnixPath(ancestorPath);
path = getUnixPath(path);
return path.startsWith(ancestorPath);
}
/**
* @throws PathResolutionException if the paths do not intersect
*/
public static String getRelativePath(@NotNull String targetPath, @NotNull String basePath, @NotNull String pathSeparator) {
String[] base = basePath.split(Pattern.quote(pathSeparator));
String[] target = targetPath.split(Pattern.quote(pathSeparator));
StringBuilder common = new StringBuilder();
int commonIndex = 0;
while (commonIndex < target.length && commonIndex < base.length
&& target[commonIndex].equals(base[commonIndex])) {
common.append(target[commonIndex]).append(pathSeparator);
commonIndex++;
}
if (commonIndex == 0) {
throw new PathResolutionException("No common path element found for '" + targetPath + "' and '" + basePath + "'");
}
if (target.length == commonIndex && base.length == target.length) {
return "";
}
boolean baseIsFile = true;
File baseResource = new File(basePath);
if (baseResource.exists()) {
baseIsFile = baseResource.isFile();
} else if (basePath.endsWith(pathSeparator)) {
baseIsFile = false;
}
StringBuilder relative = new StringBuilder();
if (base.length != commonIndex) {
int numDirsUp = baseIsFile ? base.length - commonIndex - 1 : base.length - commonIndex;
for (int i = 0; i < numDirsUp; i++) {
relative.append("..").append(pathSeparator);
}
}
if (targetPath.length() > common.length()) {
relative.append(targetPath.substring(common.length()));
}
return relative.toString();
}
public static boolean isAbsolute(@NotNull String path) {
return new File(path).isAbsolute();
}
public static String getAbsolutePath(String path) {
return new File(path).getAbsolutePath();
}
public static void closeFileSafe(Closeable c) {
if (c != null) {
try {
c.close();
} catch (IOException ignored) {
}
}
}
public static boolean canWrite(File f) {
while (!f.exists()) {
f = f.getParentFile();
if (f == null) {
return false;
}
}
return f.canWrite();
}
public final static class PathResolutionException extends RuntimeException {
PathResolutionException(String msg) {
super(msg);
}
}
@NotNull
public static String unquote(@NotNull String urlString) {
urlString = urlString.replace('/', File.separatorChar);
return URLUtil.unescapePercentSequences(urlString);
}
@NotNull
public static String getNameWithoutExtension(@NotNull String name) {
int i = name.lastIndexOf(DOT);
if (i != -1) {
name = name.substring(0, i);
}
return name;
}
@Nullable
public static String getExtension(@NotNull String name) {
int i = name.lastIndexOf(DOT);
if (i != -1) {
return name.substring(i + 1);
}
return null;
}
@Contract(value = "null -> null;!null->!null")
@Nullable
public static String stripLastSlashes(@Nullable String path) {
if (path == null) return null;
while (path.endsWith("/") || path.endsWith("\\")) {
path = path.substring(0, path.length() - 1);
}
return path;
}
// not taking non-canonical paths into account
public static boolean isSubPath(@NotNull String base, @NotNull String sub) {
boolean startsWith = sub.startsWith(base);
if (!startsWith) return false;
int baseLen = base.length();
if (sub.length() == baseLen) return true; // non-strict comparison: equal strings -> true
char lastBaseChar = base.charAt(baseLen - 1);
char nextChar = sub.charAt(baseLen);
if (lastBaseChar == '/' || lastBaseChar == '\\' || nextChar == '/' || nextChar == '\\') {
return true;
}
return false;
}
private abstract static class Packer {
public void pack(File dir, File to) {
FileOutputStream fos = null;
ZipOutputStream out = null;
try {
fos = new FileOutputStream(to);
out = createDeflaterStream(fos);
_zip(dir, "", out);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (out != null) {
out.close();
}
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void pack(Map<String, File> entries, File to) {
FileOutputStream fos = null;
ZipOutputStream out = null;
try {
fos = new FileOutputStream(to);
out = createDeflaterStream(fos);
for (String key : entries.keySet()) {
addZipEntry(out, key, entries.get(key));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (out != null) {
out.close();
}
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
protected abstract ZipOutputStream createDeflaterStream(FileOutputStream fos) throws Exception;
private static void addZipEntry(ZipOutputStream out, String path, File file) {
ZipEntry entry = new ZipEntry(path);
entry.setTime(file.lastModified());
FileInputStream is = null;
try {
out.putNextEntry(entry);
if (file.isFile()) {
is = new FileInputStream(file);
byte[] bytes = ReadUtil.read(is);
out.write(bytes);
}
out.closeEntry();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private static void _zip(File base, String prefix, ZipOutputStream out) {
File current = new File(base.getPath() + File.separator + prefix).getAbsoluteFile();
if (prefix.length() > 0) {
addZipEntry(out, prefix, current);
}
if (current.isDirectory()) {
for (File file : current.listFiles()) {
if (file.isFile()) {
_zip(base, prefix + file.getName(), out);
}
if (file.isDirectory()) {
_zip(base, prefix + file.getName() + "/", out);
}
}
}
}
}
}