package org.jruby;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.Watchable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
import jnr.posix.FileStat;
import jnr.posix.util.Platform;
import org.jcodings.Encoding;
import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyClass;
import org.jruby.exceptions.RaiseException;
import org.jruby.javasupport.JavaUtil;
import org.jruby.runtime.Block;
import org.jruby.runtime.ClassIndex;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.*;
import org.jruby.ast.util.ArgsUtil;
import static org.jruby.RubyEnumerator.enumeratorize;
import static org.jruby.RubyString.UTF8;
@JRubyClass(name = "Dir", include = "Enumerable")
public class RubyDir extends RubyObject implements Closeable {
private RubyString path;
protected FileResource dir;
private long lastModified = Long.MIN_VALUE;
private String[] snapshot;
private int pos;
private boolean isOpen = true;
private Encoding encoding;
private static final Pattern PROTOCOL_PATTERN = Pattern.compile("^(uri|jar|file|classpath):([^:]*:)?//?.*");
public RubyDir(Ruby runtime, RubyClass type) {
super(runtime, type);
}
private static final ObjectAllocator DIR_ALLOCATOR = new ObjectAllocator() {
@Override
public IRubyObject allocate(Ruby runtime, RubyClass klass) {
return new RubyDir(runtime, klass);
}
};
public static RubyClass createDirClass(Ruby runtime) {
RubyClass dirClass = runtime.defineClass("Dir", runtime.getObject(), DIR_ALLOCATOR);
runtime.setDir(dirClass);
dirClass.setClassIndex(ClassIndex.DIR);
dirClass.setReifiedClass(RubyDir.class);
dirClass.includeModule(runtime.getEnumerable());
dirClass.defineAnnotatedMethods(RubyDir.class);
return dirClass;
}
private final void checkDir() {
checkDirIgnoreClosed();
if (!isOpen) throw getRuntime().newIOError("closed directory");
}
private final void checkDirIgnoreClosed() {
testFrozen("Dir");
if (snapshot == null || dir.exists() && dir.lastModified() > lastModified) {
lastModified = dir.lastModified();
snapshot = list(dir);
}
}
@JRubyMethod(name = "initialize")
public IRubyObject initialize(ThreadContext context, IRubyObject path) {
Ruby runtime = context.runtime;
return initializeCommon(context, path, runtime.getDefaultFilesystemEncoding(), runtime);
}
@JRubyMethod(name = "initialize")
public IRubyObject initialize(ThreadContext context, IRubyObject path, IRubyObject encOpts) {
Ruby runtime = context.runtime;
Encoding encoding = null;
if (!encOpts.isNil()) {
RubyHash opts = encOpts.convertToHash();
IRubyObject encodingArg = ArgsUtil.extractKeywordArg(context, opts, "encoding");
if (encodingArg != null && !encodingArg.isNil()) {
encoding = runtime.getEncodingService().getEncodingFromObject(encodingArg);
}
}
if (encoding == null) encoding = runtime.getDefaultFilesystemEncoding();
return initializeCommon(context, path, encoding, runtime);
}
private RubyDir initializeCommon(ThreadContext context, IRubyObject pathArg, Encoding encoding, Ruby runtime) {
RubyString newPath = StringSupport.checkEmbeddedNulls(runtime, RubyFile.get_path(context, pathArg));
this.path = newPath;
this.pos = 0;
this.encoding = encoding;
String adjustedPath = RubyFile.adjustRootPathOnWindows(runtime, newPath.toString(), null);
checkDirIsTwoSlashesOnWindows(getRuntime(), adjustedPath);
this.dir = JRubyFile.createResource(context, adjustedPath);
this.snapshot = getEntries(context, dir, adjustedPath);
return this;
}
@Deprecated
public IRubyObject initialize19(ThreadContext context, IRubyObject arg) {
return initialize(context, arg);
}
private static ArrayList<ByteList> dirGlobs(ThreadContext context, String cwd, IRubyObject[] args, int flags) {
ArrayList<ByteList> dirs = new ArrayList<>();
for ( int i = 0; i < args.length; i++ ) {
dirs.addAll(Dir.push_glob(context.runtime, cwd, globArgumentAsByteList(context, args[i]), flags));
}
return dirs;
}
private static RubyArray asRubyStringList(Ruby runtime, List<ByteList> dirs) {
final int size = dirs.size();
if ( size == 0 ) return RubyArray.newEmptyArray(runtime);
IRubyObject[] dirStrings = new IRubyObject[ size ];
for ( int i = 0; i < size; i++ ) {
dirStrings[i] = RubyString.newStringNoCopy(runtime, dirs.get(i));
}
return RubyArray.newArrayMayCopy(runtime, dirStrings);
}
private static String getCWD(Ruby runtime) {
final String cwd = runtime.getCurrentDirectory();
if (cwd.startsWith("uri:") || cwd.startsWith("jar:") || cwd.startsWith("file:")) {
return cwd;
}
try {
return new JRubyFile(cwd).getCanonicalPath();
}
catch (IOException e) {
return cwd;
}
}
private static final String[] BASE = new String[] { "base" };
private static final String[] BASE_FLAGS = new String[] { "base", "flags" };
private static String globOptions(ThreadContext context, IRubyObject[] args, int[] flags) {
Ruby runtime = context.runtime;
if (args.length > 1) {
IRubyObject tmp = TypeConverter.checkHashType(runtime, args[args.length - 1]);
if (tmp == context.nil) {
if (flags != null) {
flags[0] = RubyNumeric.num2int(args[1]);
}
} else {
String[] keys = flags != null ? BASE_FLAGS : BASE;
IRubyObject[] rets = ArgsUtil.extractKeywordArgs(context, (RubyHash) tmp, keys);
String base = rets[0] == null || rets[0] == context.nil ? "" : RubyFile.get_path(context, rets[0]).asJavaString();
if (!base.isEmpty()) {
JRubyFile file = new JRubyFile(base);
if (!file.isAbsolute()) {
base = new JRubyFile(runtime.getCurrentDirectory(), base).getAbsolutePath();
}
}
if (flags != null) flags[0] = rets[1] == null ? 0 : RubyNumeric.num2int(rets[1]);
return base;
}
}
return null;
}
@JRubyMethod(name = "[]", rest = true, meta = true)
public static IRubyObject aref(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
Ruby runtime = context.runtime;
String base = globOptions(context, args, null);
List<ByteList> dirs;
if (args.length == 1) {
String dir = base == null || base.isEmpty() ? runtime.getCurrentDirectory() : base;
dirs = Dir.push_glob(runtime, dir, globArgumentAsByteList(context, args[0]), 0);
} else {
IRubyObject[] arefArgs;
if (base != null) {
arefArgs = ArraySupport.newCopy(args, args.length - 1);
} else {
arefArgs = args;
base = "";
}
String dir = base.isEmpty() ? runtime.getCurrentDirectory() : base;
dirs = dirGlobs(context, dir, arefArgs, 0);
}
return asRubyStringList(runtime, dirs);
}
private static ByteList globArgumentAsByteList(ThreadContext context, IRubyObject arg) {
return RubyFile.get_path(context, arg).getByteList();
}
@JRubyMethod(required = 1, optional = 2, meta = true)
public static IRubyObject glob(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
Ruby runtime = context.runtime;
int[] flags = new int[] { 0 };
String base = globOptions(context, args, flags);
List<ByteList> dirs;
if (!(base == null || base.isEmpty()) && !(new File(base).exists())){
dirs = new ArrayList<ByteList>();
} else {
IRubyObject tmp = args[0].checkArrayType();
String dir = base == null || base.isEmpty() ? runtime.getCurrentDirectory() : base;
if (tmp.isNil()) {
dirs = Dir.push_glob(runtime, dir, globArgumentAsByteList(context, args[0]), flags[0]);
} else {
dirs = dirGlobs(context, dir, ((RubyArray) tmp).toJavaArray(), flags[0]);
}
}
if (block.isGiven()) {
for (int i = 0; i < dirs.size(); i++) {
block.yield(context, RubyString.newString(runtime, dirs.get(i)));
}
return runtime.getNil();
}
return asRubyStringList(runtime, dirs);
}
@JRubyMethod(name = "entries")
public RubyArray entries() {
JavaUtil.StringConverter converter = new JavaUtil.StringConverter(encoding);
return RubyArray.newArrayMayCopy(getRuntime(), JavaUtil.convertStringArrayToRuby(getRuntime(), snapshot, converter));
}
@Deprecated
public static RubyArray entries(IRubyObject recv, IRubyObject path) {
return entries(recv.getRuntime().getCurrentContext(), recv, path);
}
@Deprecated
public static RubyArray entries(IRubyObject recv, IRubyObject path, IRubyObject arg, IRubyObject opts) {
return entries(recv.getRuntime().getCurrentContext(), recv, path, opts);
}
@JRubyMethod(name = "entries", meta = true)
public static RubyArray entries(ThreadContext context, IRubyObject recv, IRubyObject arg) {
Ruby runtime = context.runtime;
RubyString path = StringSupport.checkEmbeddedNulls(runtime, RubyFile.get_path(context, arg));
return entriesCommon(context, path.asJavaString(), runtime.getDefaultEncoding(), false);
}
@JRubyMethod(name = "entries", meta = true)
public static RubyArray entries(ThreadContext context, IRubyObject recv, IRubyObject arg, IRubyObject opts) {
Ruby runtime = context.runtime;
Encoding encoding = null;
RubyString path = StringSupport.checkEmbeddedNulls(runtime, RubyFile.get_path(context, arg));
if (opts instanceof RubyHash) {
IRubyObject encodingArg = ArgsUtil.extractKeywordArg(context, (RubyHash) opts, "encoding");
if (encodingArg != null && !encodingArg.isNil()) {
encoding = runtime.getEncodingService().getEncodingFromObject(encodingArg);
}
}
if (encoding == null) encoding = runtime.getDefaultEncoding();
return entriesCommon(context, path.asJavaString(), encoding, false);
}
private static RubyArray entriesCommon(ThreadContext context, String path, Encoding encoding, final boolean childrenOnly) {
Ruby runtime = context.runtime;
String adjustedPath = RubyFile.adjustRootPathOnWindows(runtime, path, null);
checkDirIsTwoSlashesOnWindows(runtime, adjustedPath);
FileResource directory = JRubyFile.createResource(context, path);
String[] files = getEntries(context, directory, adjustedPath);
RubyArray result = RubyArray.newArray(runtime, files.length);
for (String file : files) {
if (childrenOnly) {
final int len = file.length();
if (len == 1 && file.charAt(0) == '.') continue;
if (len == 2 && file.charAt(0) == '.' && file.charAt(1) == '.') continue;
}
result.append( RubyString.newString(runtime, file, encoding) );
}
return result;
}
private static final String[] NO_FILES = StringSupport.EMPTY_STRING_ARRAY;
private static String[] getEntries(ThreadContext context, FileResource dir, String path) {
if (!dir.isDirectory()) {
if (dir.exists()) {
throw context.runtime.newErrnoENOTDIRError(path);
}
throw context.runtime.newErrnoENOENTError(path);
}
if (!dir.canRead()) throw context.runtime.newErrnoEACCESError(path);
String[] list = dir.list();
return list == null ? NO_FILES : list;
}
private static void checkDirIsTwoSlashesOnWindows(Ruby runtime, String path) {
if (Platform.IS_WINDOWS && ("//".equals(path) || "\\\\".equals(path))) {
throw runtime.newErrnoEINVALError("Invalid argument - " + path);
}
}
@JRubyMethod(optional = 1, meta = true)
public static IRubyObject chdir(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
Ruby runtime = context.runtime;
RubyString path = args.length == 1 ?
StringSupport.checkEmbeddedNulls(runtime, RubyFile.get_path(context, args[0])) :
getHomeDirectoryPath(context);
String adjustedPath = RubyFile.adjustRootPathOnWindows(runtime, path.asJavaString(), null);
checkDirIsTwoSlashesOnWindows(runtime, adjustedPath);
adjustedPath = getExistingDir(runtime, adjustedPath).canonicalPath();
IRubyObject result;
if (block.isGiven()) {
final String oldCwd = runtime.getCurrentDirectory();
runtime.setCurrentDirectory(adjustedPath);
try {
result = block.yield(context, path);
} finally {
getExistingDir(runtime, oldCwd);
runtime.setCurrentDirectory(oldCwd);
}
} else {
runtime.setCurrentDirectory(adjustedPath);
result = runtime.newFixnum(0);
}
return result;
}
@JRubyMethod(name = "chroot", required = 1, meta = true)
public static IRubyObject chroot(IRubyObject recv, IRubyObject path) {
throw recv.getRuntime().newNotImplementedError("chroot not implemented: chroot is non-portable and is not supported.");
}
@JRubyMethod(name = "children", meta = true)
public static RubyArray children(ThreadContext context, IRubyObject recv, IRubyObject arg) {
return children(context, recv, arg, context.nil);
}
@JRubyMethod(name = "children", meta = true)
public static RubyArray children(ThreadContext context, IRubyObject recv, IRubyObject arg, IRubyObject opts) {
Encoding encoding = null;
if (opts instanceof RubyHash) {
IRubyObject encodingArg = ArgsUtil.extractKeywordArg(context, (RubyHash) opts, "encoding");
if (encodingArg != null && !encodingArg.isNil()) {
encoding = context.runtime.getEncodingService().getEncodingFromObject(encodingArg);
}
}
if (encoding == null) encoding = context.runtime.getDefaultEncoding();
return entriesCommon(context, RubyFile.get_path(context, arg).asJavaString(), encoding, true);
}
public static IRubyObject rmdir(IRubyObject recv, IRubyObject path) {
return rmdir19(recv.getRuntime().getCurrentContext(), recv, path);
}
@JRubyMethod(name = {"rmdir", "unlink", "delete"}, required = 1, meta = true)
public static IRubyObject rmdir19(ThreadContext context, IRubyObject recv, IRubyObject path) {
Ruby runtime = context.runtime;
RubyString cleanPath = StringSupport.checkEmbeddedNulls(runtime, RubyFile.get_path(context, path));
return rmdirCommon(runtime, cleanPath.asJavaString());
}
private static RubyFixnum rmdirCommon(Ruby runtime, String path) {
JRubyFile directory = getDirForRmdir(runtime, path);
if (runtime.getPosix().rmdir(directory.toString()) < 0) {
throw runtime.newErrnoENOTEMPTYError(path);
}
return runtime.newFixnum(0);
}
@JRubyMethod(name = "each_child", meta = true)
public static IRubyObject each_child(ThreadContext context, IRubyObject recv, IRubyObject arg, Block block) {
return eachChildCommon(context, recv, RubyFile.get_path(context, arg), null, block);
}
@JRubyMethod(name = "each_child", meta = true)
public static IRubyObject each_child(ThreadContext context, IRubyObject recv, IRubyObject arg, IRubyObject enc, Block block) {
RubyEncoding encoding;
if (enc instanceof RubyEncoding) {
encoding = (RubyEncoding) enc;
} else {
throw context.runtime.newTypeError(enc, context.runtime.getEncoding());
}
return eachChildCommon(context, recv, RubyFile.get_path(context, arg), encoding, block);
}
@JRubyMethod(name = "foreach", meta = true)
public static IRubyObject foreach(ThreadContext context, IRubyObject recv, IRubyObject path, Block block) {
return foreachCommon(context, recv, RubyFile.get_path(context, path), null, block);
}
@JRubyMethod(name = "foreach", meta = true)
public static IRubyObject foreach(ThreadContext context, IRubyObject recv, IRubyObject path, IRubyObject encOpts, Block block) {
Encoding encoding = null;
if(!encOpts.isNil()) {
IRubyObject opts = TypeConverter.checkHashType(context.runtime, encOpts);
IRubyObject encodingArg = ArgsUtil.extractKeywordArg(context, (RubyHash) opts, "encoding");
if (encodingArg != null && !encodingArg.isNil()) {
encoding = context.runtime.getEncodingService().getEncodingFromObject(encodingArg);
}
}
if (encoding == null) encoding = context.runtime.getDefaultFilesystemEncoding();
return foreachCommon(context, recv, RubyFile.get_path(context, path), encoding, block);
}
@Deprecated
public static IRubyObject foreach19(ThreadContext context, IRubyObject recv, IRubyObject path, Block block) {
return foreachCommon(context, recv, RubyFile.get_path(context, path), null, block);
}
@Deprecated
public static IRubyObject foreach19(ThreadContext context, IRubyObject recv, IRubyObject path, IRubyObject enc, Block block) {
return foreachCommon(context, recv, RubyFile.get_path(context, path), context.runtime.getEncodingService().getEncodingFromObject(enc), block);
}
private static IRubyObject eachChildCommon(ThreadContext context, IRubyObject recv, RubyString path, RubyEncoding encoding, Block block) {
final Ruby runtime = context.runtime;
if (block.isGiven()) {
RubyDir dir = (RubyDir) runtime.getDir().newInstance(context, path, Block.NULL_BLOCK);
dir.each_child(context, encoding == null ? runtime.getDefaultEncoding() : encoding.getEncoding(), block);
return context.nil;
}
if (encoding == null) {
return enumeratorize(runtime, recv, "each_child", path);
}
return enumeratorize(runtime, recv, "each_child", path, encoding);
}
private static IRubyObject foreachCommon(ThreadContext context, IRubyObject recv, RubyString path, Encoding encoding, Block block) {
final Ruby runtime = context.runtime;
if (block.isGiven()) {
RubyDir dir = (RubyDir) runtime.getDir().newInstance(context, path, Block.NULL_BLOCK);
dir.each(context, encoding == null ? runtime.getDefaultEncoding() : encoding, block);
return context.nil;
}
if (encoding == null) {
return enumeratorize(runtime, recv, "foreach", path);
}
IRubyObject rubyEncoding = runtime.getEncodingService().getEncoding(encoding);
return enumeratorize(runtime, recv, "foreach", path, rubyEncoding);
}
@JRubyMethod(name = {"getwd", "pwd"}, meta = true)
public static RubyString getwd(IRubyObject recv) {
Ruby runtime = recv.getRuntime();
RubyString pwd = RubyString.newUnicodeString(runtime, getCWD(runtime));
pwd.setTaint(true);
return pwd;
}
@JRubyMethod(name = "home", optional = 1, meta = true)
public static IRubyObject home(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
if (args.length > 0 && args[0] != context.nil) return getHomeDirectoryPath(context, args[0].toString());
return getHomeDirectoryPath(context);
}
@JRubyMethod(name = "mkdir", required = 1, optional = 1, meta = true)
public static IRubyObject mkdir(ThreadContext context, IRubyObject recv, IRubyObject... args) {
Ruby runtime = context.runtime;
RubyString path = StringSupport.checkEmbeddedNulls(runtime, RubyFile.get_path(context, args[0]));
return mkdirCommon(runtime, path.asJavaString(), args);
}
@Deprecated
public static IRubyObject mkdir(IRubyObject recv, IRubyObject[] args) {
return mkdir(recv.getRuntime().getCurrentContext(), recv, args);
}
@Deprecated
public static IRubyObject mkdir19(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
return mkdir(context, recv, args);
}
private static IRubyObject mkdirCommon(Ruby runtime, String path, IRubyObject[] args) {
if (path.startsWith("uri:")) throw runtime.newErrnoEACCESError(path);
path = dirFromPath(path, runtime);
FileResource res = JRubyFile.createResource(runtime, path);
if (res.isDirectory()) throw runtime.newErrnoEEXISTError(path);
String name = path.replace('\\', '/');
boolean startsWithDriveLetterOnWindows = RubyFile.startsWithDriveLetterOnWindows(name);
if (startsWithDriveLetterOnWindows) {
if (path.length() == 2) return RubyFixnum.zero(runtime);
if (path.length() == 3 && (path.charAt(0) == '/' || path.charAt(2) == '/')) return RubyFixnum.zero(runtime);
if (path.length() == 4 && (path.charAt(0) == '/' && path.charAt(3) == '/')) return RubyFixnum.zero(runtime);
}
File newDir = res.unwrap(File.class);
if (File.separatorChar == '\\') newDir = new File(newDir.getPath());
int mode = args.length == 2 ? ((int) args[1].convertToInteger().getLongValue()) : 0777;
if (runtime.getPosix().mkdir(newDir.getAbsolutePath(), mode) < 0) {
throw runtime.newSystemCallError("mkdir failed");
}
return RubyFixnum.zero(runtime);
}
@JRubyMethod(name = "open", meta = true)
public static IRubyObject open(ThreadContext context, IRubyObject recv, IRubyObject path, Block block) {
RubyDir directory = (RubyDir) context.runtime.getDir().newInstance(context, path, Block.NULL_BLOCK);
if (!block.isGiven()) return directory;
try {
return block.yield(context, directory);
} finally {
directory.close();
}
}
@JRubyMethod(name = "open", meta = true)
public static IRubyObject open(ThreadContext context, IRubyObject recv, IRubyObject path, IRubyObject encOpts, Block block) {
RubyDir directory = (RubyDir) context.runtime.getDir().newInstance(context, path, encOpts, Block.NULL_BLOCK);
if (!block.isGiven()) return directory;
try {
return block.yield(context, directory);
} finally {
directory.close();
}
}
@Deprecated
public static IRubyObject open19(ThreadContext context, IRubyObject recv, IRubyObject path, Block block) {
return open(context, recv, path, block);
}
@JRubyMethod(name = "close")
public IRubyObject close(ThreadContext context) {
close();
return context.nil;
}
public final void close() {
checkDirIgnoreClosed();
isOpen = false;
}
public IRubyObject each(ThreadContext context, Encoding enc, Block block) {
checkDir();
String[] contents = snapshot;
for (pos = 0; pos < contents.length; pos++) {
block.yield(context, RubyString.newString(context.runtime, contents[pos], enc));
}
return this;
}
@JRubyMethod(name = "each")
public IRubyObject each(ThreadContext context, Block block) {
return block.isGiven() ? each(context, encoding, block) : enumeratorize(context.runtime, this, "each");
}
@JRubyMethod(name = "each")
public IRubyObject each(ThreadContext context, IRubyObject encoding, Block block) {
if (!(encoding instanceof RubyEncoding)) throw context.runtime.newTypeError(encoding, context.runtime.getEncoding());
return block.isGiven() ? each(context, ((RubyEncoding) encoding).getEncoding(), block) : enumeratorize(context.runtime, this, "each", encoding);
}
@Deprecated
public IRubyObject each19(ThreadContext context, Block block) {
return each(context, block);
}
@Deprecated
public IRubyObject each19(ThreadContext context, IRubyObject encoding, Block block) {
return each(context, encoding, block);
}
public IRubyObject each_child(ThreadContext context, Encoding enc, Block block) {
checkDir();
String[] contents = snapshot;
for (pos = 0; pos < contents.length; pos++) {
if (StringSupport.contentEquals(contents[pos], '.')) continue;
if (StringSupport.contentEquals(contents[pos], '.', '.')) continue;
block.yield(context, RubyString.newString(context.runtime, contents[pos], enc));
}
return this;
}
public IRubyObject each_child(ThreadContext context, Block block) {
return each_child(context, encoding, block);
}
@Override
@JRubyMethod
public IRubyObject inspect() {
Ruby runtime = getRuntime();
StringBuilder part = new StringBuilder();
String cname = getMetaClass().getRealClass().getName();
part.append("#<").append(cname).append(':');
if (path != null) { part.append(path.asJavaString()); }
part.append('>');
return runtime.newString(part.toString());
}
@JRubyMethod(name = {"tell", "pos"})
public RubyInteger tell() {
checkDir();
return getRuntime().newFixnum(pos);
}
@JRubyMethod(name = "seek", required = 1)
public IRubyObject seek(IRubyObject newPos) {
checkDir();
set_pos(newPos);
return this;
}
@JRubyMethod(name = "pos=", required = 1)
public IRubyObject set_pos(IRubyObject newPos) {
int pos2 = RubyNumeric.fix2int(newPos);
if (pos2 >= 0) this.pos = pos2;
return newPos;
}
@JRubyMethod(name = {"path", "to_path"})
public IRubyObject path(ThreadContext context) {
return path == null ? context.nil : path.strDup(context.runtime);
}
@JRubyMethod
public IRubyObject to_path(ThreadContext context) {
return path(context);
}
public String getPath() {
if (path == null) return null;
return path.asJavaString();
}
@JRubyMethod(name = "read")
public IRubyObject read() {
checkDir();
final String[] snapshot = this.snapshot;
if (pos >= snapshot.length) return getRuntime().getNil();
RubyString result = RubyString.newString(getRuntime(), snapshot[pos], encoding);
pos++;
return result;
}
@JRubyMethod(name = "rewind")
public IRubyObject rewind() {
checkDir();
pos = 0;
return this;
}
@JRubyMethod(name = "empty?", meta = true)
public static IRubyObject empty_p(ThreadContext context, IRubyObject recv, IRubyObject arg) {
Ruby runtime = context.runtime;
RubyString path = StringSupport.checkEmbeddedNulls(runtime, RubyFile.get_path(context, arg));
RubyFileStat fileStat = runtime.newFileStat(path.asJavaString(), false);
boolean isDirectory = fileStat.directory_p().isTrue();
return runtime.newBoolean(isDirectory && entries(context, recv, arg).getLength() <= 2);
}
@JRubyMethod(name = "exist?", meta = true)
public static IRubyObject exist(ThreadContext context, IRubyObject recv, IRubyObject arg) {
Ruby runtime = context.runtime;
IRubyObject exception = runtime.getGlobalVariables().get("$!");
RubyString path = StringSupport.checkEmbeddedNulls(runtime, RubyFile.get_path(context, arg));
try {
return runtime.newFileStat(path.asJavaString(), false).directory_p();
} catch (Exception e) {
runtime.getGlobalVariables().set("$!", exception);
return runtime.newBoolean(false);
}
}
@JRubyMethod(name = "exists?", meta = true)
public static IRubyObject exists_p(ThreadContext context, IRubyObject recv, IRubyObject arg) {
if (context.runtime.warningsEnabled()) {
context.runtime.getWarnings().warn("Dir.exists? is a deprecated name, use Dir.exist? instead");
}
return exist(context, recv, arg);
}
@JRubyMethod(name = "fileno", notImplemented = true)
public IRubyObject fileno(ThreadContext context) {
throw context.runtime.newNotImplementedError("Dir#fileno");
}
protected static FileResource getDir(final Ruby runtime, final String path, final boolean mustExist) {
String dir = dirFromPath(path, runtime);
FileResource result = JRubyFile.createResource(runtime, dir);
if (mustExist && (result == null || !result.exists())) {
throw runtime.newErrnoENOENTError(dir);
}
boolean isDirectory = result.isDirectory();
if (mustExist && !isDirectory) {
throw runtime.newErrnoENOTDIRError(path);
}
if (!mustExist && isDirectory) {
throw runtime.newErrnoEEXISTError(dir);
}
return result;
}
private static FileResource getExistingDir(final Ruby runtime, final String path) {
FileResource result = JRubyFile.createResource(runtime, path);
if (result == null || !result.exists()) {
throw runtime.newErrnoENOENTError(path);
}
if (!result.isDirectory()) {
throw runtime.newErrnoENOTDIRError(path);
}
return result;
}
protected static JRubyFile getDirForRmdir(final Ruby runtime, final String path) {
String dir = dirFromPath(path, runtime);
JRubyFile directory = JRubyFile.create(runtime.getCurrentDirectory(), dir);
File parentFile = directory.getParentFile();
if (parentFile.exists() && ! parentFile.canWrite()) {
throw runtime.newErrnoEACCESError(path);
}
FileStat stat = runtime.getPosix().stat(directory.toString());
if (!stat.isDirectory()) throw runtime.newErrnoENOTDIRError(path);
return directory;
}
private static String dirFromPath(final String path, final Ruby runtime) throws RaiseException {
String dir = path;
String[] pathParts = RubyFile.splitURI(path);
if (pathParts != null) {
if (pathParts[0].startsWith("file:") && pathParts[1].length() > 0 && pathParts[1].indexOf(".jar!/") == -1) {
dir = pathParts[1];
} else {
throw runtime.newErrnoENOTDIRError(dir);
}
}
return dir;
}
private static String[] list(FileResource directory) {
final String[] contents = directory.list();
return contents == null ? NO_FILES : contents;
}
protected static List<String> getContents(FileResource directory) {
final String[] contents = directory.list();
final List<String> result;
if (contents != null) {
result = Arrays.asList(contents);
}
else {
result = Collections.emptyList();
}
return result;
}
protected static List<RubyString> getContents(FileResource directory, Ruby runtime) {
final String[] contents = directory.list();
final List<RubyString> result;
if (contents != null) {
result = new ArrayList<>(contents.length);
for (int i = 0; i < contents.length; i++) {
result.add( runtime.newString(contents[i]) );
}
}
else {
result = Collections.emptyList();
}
return result;
}
public static IRubyObject getHomeDirectoryPath(ThreadContext context, String user) {
Ruby runtime = context.runtime;
try {
return runtime.newString(runtime.getPosix().getpwnam(user).getHome());
} catch (Exception e) {
String passwd;
try {
FileInputStream stream = new FileInputStream("/etc/passwd");
int readBytes = stream.available();
byte[] bytes = new byte[readBytes];
readBytes = stream.read(bytes);
stream.close();
passwd = new String(bytes, 0, readBytes);
} catch (IOException ioe) {
return runtime.getNil();
}
List<String> rows = StringSupport.split(passwd, '\n');
for (int i = 0; i < rows.size(); i++) {
List<String> fields = StringSupport.split(rows.get(i), ':');
if (fields.get(0).equals(user)) {
return runtime.newString(fields.get(5));
}
}
}
throw runtime.newArgumentError("user " + user + " doesn't exist");
}
static final ByteList HOME = new ByteList(new byte[] {'H','O','M','E'}, false);
public static RubyString getHomeDirectoryPath(ThreadContext context) {
final RubyString homeKey = RubyString.newStringShared(context.runtime, HOME);
return getHomeDirectoryPath(context, context.runtime.getENV().op_aref(context, homeKey));
}
private static final ByteList user_home = new ByteList(new byte[] {'u','s','e','r','.','h','o','m','e'}, false);
static RubyString getHomeDirectoryPath(ThreadContext context, IRubyObject home) {
final Ruby runtime = context.runtime;
if (home == null || home == context.nil) {
IRubyObject ENV_JAVA = runtime.getObject().getConstant("ENV_JAVA");
home = ENV_JAVA.callMethod(context, "[]", RubyString.newString(runtime, user_home, UTF8));
}
if (home == null || home == context.nil) {
home = context.runtime.getENV().op_aref(context, runtime.newString("LOGDIR"));
}
if (home == null || home == context.nil) {
throw runtime.newArgumentError("user.home/LOGDIR not set");
}
return (RubyString) home.dup();
}
@Override
public <T> T toJava(Class<T> target) {
if (target == File.class) {
final String path = getPath();
return path == null ? null : (T) new File(path);
}
if (target == Path.class || target == Watchable.class) {
final String path = getPath();
return path == null ? null : (T) FileSystems.getDefault().getPath(path);
}
return super.toJava(target);
}
@Deprecated
public static RubyArray entries19(ThreadContext context, IRubyObject recv, IRubyObject arg) {
return entries(context, recv, arg);
}
@Deprecated
public static RubyArray entries19(ThreadContext context, IRubyObject recv, IRubyObject arg, IRubyObject opts) {
return entries(context, recv, arg, opts);
}
}