package org.jruby.ext.pathname;
import static org.jruby.anno.FrameField.BACKREF;
import org.jruby.*;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.exceptions.RaiseException;
import org.jruby.internal.runtime.methods.JavaMethod;
import org.jruby.runtime.*;
import org.jruby.runtime.builtin.IRubyObject;
@JRubyClass(name = "Pathname")
public class RubyPathname extends RubyObject {
private RubyString getPath() {
return this.getInstanceVariable("@path").convertToString();
}
private void setPath(RubyString path) {
this.setInstanceVariable("@path", path);
}
static void createPathnameClass(Ruby runtime) {
RubyClass cPathname = runtime.defineClass("Pathname", runtime.getObject(),
PATHNAME_ALLOCATOR);
cPathname.defineAnnotatedMethods(RubyPathname.class);
runtime.getKernel().defineAnnotatedMethods(PathnameKernelMethods.class);
defineDelegateMethods(cPathname, runtime.getFile(), "atime", "ctime", "birthtime", "mtime", "ftype",
"rename", "stat", "lstat", "truncate", "extname", "open");
defineDelegateMethodsAppendPath(cPathname, runtime.getFile(), "chmod", "lchmod", "chown",
"lchown", "utime");
defineDelegateMethodsSinglePath(cPathname, runtime.getFile(), "realpath", "realdirpath",
"basename", "dirname", "expand_path", "readlink");
defineDelegateMethodsArrayOfPaths(cPathname, runtime.getFile(), "split");
defineDelegateMethods(cPathname, runtime.getIO(), "read", "binread", "write", "binwrite",
"readlines", "sysopen");
defineDelegateMethods(cPathname, runtime.getFileTest(), "blockdev?", "chardev?",
"executable?", "executable_real?", "exist?", "grpowned?", "directory?", "file?",
"pipe?", "socket?", "owned?", "readable?", "world_readable?", "readable_real?",
"setuid?", "setgid?", "size", "size?", "sticky?", "symlink?", "writable?",
"world_writable?", "writable_real?", "zero?");
defineDelegateMethods(cPathname, runtime.getDir(), "mkdir", "rmdir");
defineDelegateMethodsArrayOfPaths(cPathname, runtime.getDir(), "entries");
cPathname.undefineMethod("=~");
}
static interface ReturnValueMapper {
IRubyObject map(ThreadContext context, RubyClass klazz, IRubyObject value);
}
static interface AddArg {
IRubyObject[] addArg(IRubyObject[] args, RubyString path);
}
private static final ReturnValueMapper IDENTITY_MAPPER = new ReturnValueMapper() {
@Override
public IRubyObject map(ThreadContext context, RubyClass klazz, IRubyObject value) {
return value;
}
};
private static final ReturnValueMapper SINGLE_PATH_MAPPER = new ReturnValueMapper() {
@Override
public IRubyObject map(ThreadContext context, RubyClass klazz, IRubyObject value) {
return newInstance(context, klazz, value);
}
};
private static final ReturnValueMapper ARRAY_OF_PATHS_MAPPER = new ReturnValueMapper() {
@Override
public IRubyObject map(ThreadContext context, RubyClass klazz, IRubyObject value) {
return mapToPathnames(context, klazz, value);
}
};
private static final AddArg UNSHIFT_PATH = new AddArg() {
@Override
public IRubyObject[] addArg(IRubyObject[] args, RubyString path) {
return insert(args, 0, path);
}
};
private static final AddArg APPEND_PATH = new AddArg() {
@Override
public IRubyObject[] addArg(IRubyObject[] args, RubyString path) {
return insert(args, args.length, path);
}
};
private static void defineDelegateMethodsGeneric(RubyClass cPathname, final RubyModule klass,
final ReturnValueMapper mapper, final AddArg addArg, String... methods) {
for (String method : methods) {
cPathname.addMethod(method, new JavaMethod.JavaMethodNBlock(cPathname, Visibility.PUBLIC, method) {
@Override
public IRubyObject call(ThreadContext context, IRubyObject _self, RubyModule clazz,
String name, IRubyObject[] args, Block block) {
RubyPathname self = (RubyPathname) _self;
args = addArg.addArg(args, self.getPath());
return mapper.map(context, (RubyClass) clazz, klass.callMethod(context, name, args, block));
}
});
}
}
private static void defineDelegateMethods(RubyClass cPathname, final RubyModule klass,
String... methods) {
defineDelegateMethodsGeneric(cPathname, klass, IDENTITY_MAPPER, UNSHIFT_PATH, methods);
}
private static void defineDelegateMethodsAppendPath(RubyClass cPathname,
final RubyModule klass, String... methods) {
defineDelegateMethodsGeneric(cPathname, klass, IDENTITY_MAPPER, APPEND_PATH, methods);
}
private static void defineDelegateMethodsSinglePath(RubyClass cPathname,
final RubyModule klass, String... methods) {
defineDelegateMethodsGeneric(cPathname, klass, SINGLE_PATH_MAPPER, UNSHIFT_PATH, methods);
}
private static void defineDelegateMethodsArrayOfPaths(RubyClass cPathname,
final RubyModule klass, String... methods) {
defineDelegateMethodsGeneric(cPathname, klass, ARRAY_OF_PATHS_MAPPER, UNSHIFT_PATH, methods);
}
public static class PathnameKernelMethods {
@JRubyMethod(name = "Pathname", module = true, visibility = Visibility.PRIVATE)
public static IRubyObject newPathname(IRubyObject recv, IRubyObject path) {
return RubyPathname.newInstance(recv.getRuntime().getCurrentContext(), path);
}
}
private static ObjectAllocator PATHNAME_ALLOCATOR = new ObjectAllocator() {
@Override
public IRubyObject allocate(Ruby runtime, RubyClass klass) {
return new RubyPathname(runtime, klass);
}
};
public RubyPathname(Ruby runtime, RubyClass metaClass) {
super(runtime, metaClass);
}
public static RubyPathname newInstance(ThreadContext context, RubyClass klass, IRubyObject path) {
RubyPathname pathname = new RubyPathname(context.runtime, klass);
return (RubyPathname) pathname.initialize(context, path);
}
public static RubyPathname newInstance(ThreadContext context, IRubyObject path) {
return newInstance(context, context.runtime.getClass("Pathname"), path);
}
@JRubyMethod(visibility = Visibility.PRIVATE)
public IRubyObject initialize(ThreadContext context, IRubyObject path) {
if (path.respondsTo("to_path")) {
path = path.callMethod(context, "to_path");
}
RubyString str = path.convertToString();
if (str.getByteList().indexOf('\0') != -1) {
throw context.runtime.newArgumentError("pathname contains null byte");
}
infectBy(str);
this.setPath((RubyString) str.dup());
return this;
}
@JRubyMethod(visibility = Visibility.PRIVATE)
public IRubyObject initialize_copy(ThreadContext context, IRubyObject pathname) {
super.initialize_copy(pathname);
initialize(context, pathname);
return this;
}
@JRubyMethod
public IRubyObject to_path(ThreadContext context) {
return getPath();
}
@Override
@JRubyMethod
public IRubyObject freeze(ThreadContext context) {
getPath().freeze(context);
return super.freeze(context);
}
@Override
@JRubyMethod
public IRubyObject taint(ThreadContext context) {
getPath().taint(context);
return super.taint(context);
}
@Override
@JRubyMethod
public IRubyObject untaint(ThreadContext context) {
getPath().untaint(context);
return super.untaint(context);
}
@Override
@JRubyMethod(name = { "==", "eql?" })
public IRubyObject op_equal(ThreadContext context, IRubyObject other) {
if (other instanceof RubyPathname) {
return Helpers.rbEqual(context, getPath(), ((RubyPathname) other).getPath());
} else {
return context.fals;
}
}
private int cmp(RubyPathname other) {
byte[] a = getPath().getByteList().bytes();
byte[] b = other.getPath().getByteList().bytes();
int i;
for (i = 0; i < a.length && i < b.length; i++) {
byte ca = a[i];
byte cb = b[i];
if (ca == '/') {
ca = '\0';
}
if (cb == '/') {
cb = '\0';
}
if (ca != cb) {
return ca < cb ? -1 : 1;
}
}
if (i < a.length) {
return 1;
}
if (i < b.length) {
return -1;
}
return 0;
}
@Override
@JRubyMethod(name = "<=>")
public IRubyObject op_cmp(ThreadContext context, IRubyObject other) {
if (other instanceof RubyPathname) {
return context.runtime.newFixnum(cmp((RubyPathname) other));
} else {
return context.nil;
}
}
@JRubyMethod(name = "hash")
public RubyFixnum hash(ThreadContext context) {
return getPath().hash();
}
@Override
public int hashCode() {
return getPath().hashCode();
}
@JRubyMethod
public IRubyObject to_s(ThreadContext context) {
return getPath().dup();
}
@JRubyMethod
public IRubyObject inspect(ThreadContext context) {
return context.runtime.newString("<Pathname:" + getPath() + ">");
}
@JRubyMethod(required = 1, optional = 1, reads = BACKREF, writes = BACKREF)
public IRubyObject sub(ThreadContext context, IRubyObject[] args, Block block) {
IRubyObject result = sites(context).sub.call(context, this, getPath(), args, block);
return newInstance(context, result);
}
@JRubyMethod
public IRubyObject sub_ext(ThreadContext context, IRubyObject newExt) {
IRubyObject ext = context.runtime.getFile().callMethod(context, "extname", getPath());
IRubyObject newPath = getPath().chomp(context, ext).callMethod(context, "+", newExt);
return newInstance(context, newPath);
}
@JRubyMethod(alias = "fnmatch?", required = 1, optional = 1)
public IRubyObject fnmatch(ThreadContext context, IRubyObject[] args) {
args = insertPath(args, 1);
return context.runtime.getFile().callMethod(context, "fnmatch?", args);
}
@JRubyMethod
public IRubyObject make_link(ThreadContext context, IRubyObject old) {
IRubyObject[] args = new IRubyObject[] { old, getPath()};
return context.runtime.getFile().callMethod(context, "link", args);
}
@JRubyMethod
public IRubyObject make_symlink(ThreadContext context, IRubyObject old) {
IRubyObject[] args = new IRubyObject[] { old, getPath()};
return context.runtime.getFile().callMethod(context, "symlink", args);
}
@JRubyMethod(optional = 3)
public IRubyObject each_line(ThreadContext context, IRubyObject[] args, Block block) {
return context.runtime.getIO().callMethod(context, "foreach", unshiftPath(args), block);
}
@JRubyMethod(alias = "pwd", meta = true)
public static IRubyObject getwd(ThreadContext context, IRubyObject recv) {
return newInstance(context, context.runtime.getDir().callMethod("getwd"));
}
@JRubyMethod(required = 1, optional = 1, meta = true)
public static IRubyObject glob(ThreadContext context, IRubyObject recv, IRubyObject[] args,
Block block) {
RubyArray files = mapToPathnames(context, (RubyClass) recv,
context.runtime.getDir().callMethod(context, "glob", args));
if (block.isGiven()) {
files.each(context, block);
return context.nil;
} else {
return files;
}
}
@JRubyMethod(required = 1, optional = 1)
public IRubyObject glob(ThreadContext context, IRubyObject[] _args, Block block) {
Ruby runtime = context.runtime;
IRubyObject[] args = new IRubyObject[3];
boolean blockGiven = block.isGiven();
args[0] = _args[0];
if (_args.length == 1) {
args[1] = RubyFixnum.zero(runtime);
} else {
args[1] = _args[1];
}
args[2] = RubyHash.newSmallHash(runtime);
((RubyHash) args[2]).fastASetSmall(runtime.newSymbol("base"), context.runtime.getFile().callMethod(context, "realpath", getPath()));
JavaSites.PathnameSites sites = sites(context);
CallSite glob = sites.glob;
RubyArray ary;
long i;
ary = glob.call(context, this, runtime.getDir(), args).convertToArray();
CallSite op_plus = sites.op_plus;
for (i = 0; i < ary.size(); i++) {
IRubyObject elt = ary.eltOk(i);
elt = op_plus.call(context, this, this, elt);
ary.eltSetOk(i, elt);
if (blockGiven) block.yield(context, elt);
}
return blockGiven ? context.nil : ary;
}
@JRubyMethod
public IRubyObject opendir(ThreadContext context, Block block) {
return context.runtime.getDir().callMethod(context, "open", new IRubyObject[] { getPath()},
block);
}
@JRubyMethod
public IRubyObject each_entry(ThreadContext context, Block block) {
if (block.isGiven()) {
RubyArray entries = callMethod(context, "entries").convertToArray();
entries.each(context, block);
return context.nil;
} else {
return context.runtime.getDir().callMethod(context, "foreach");
}
}
@JRubyMethod(name = {"unlink", "delete"})
public IRubyObject unlink(ThreadContext context) {
IRubyObject oldExc = context.runtime.getGlobalVariables().get("$!");
try {
return context.runtime.getDir().callMethod(context, "unlink", getPath());
} catch (RaiseException ex) {
if (!context.runtime.getErrno().getClass("ENOTDIR").isInstance(ex.getException())) {
throw ex;
}
context.runtime.getGlobalVariables().set("$!", oldExc);
return context.runtime.getFile().callMethod(context, "unlink", getPath());
}
}
@JRubyMethod(name = "empty?")
public IRubyObject empty_p(ThreadContext context) {
RubyModule fileTest = context.runtime.getFileTest();
if (fileTest.callMethod(context, "directory?", getPath()).isTrue()) {
return context.runtime.getDir().callMethod(context, "empty?", getPath());
} else {
return fileTest.callMethod(context, "empty?", getPath());
}
}
private IRubyObject[] insertPath(IRubyObject[] args, int i) {
return insert(args, i, getPath());
}
private IRubyObject[] unshiftPath(IRubyObject[] args) {
return insert(args, 0, getPath());
}
private static IRubyObject[] insert(IRubyObject[] old, int i, IRubyObject obj) {
IRubyObject[] ary = new IRubyObject[old.length + 1];
if (i > 0) {
System.arraycopy(old, 0, ary, 0, i);
}
ary[i] = obj;
if (old.length > i) {
System.arraycopy(old, i, ary, i + 1, old.length - i);
}
return ary;
}
private static RubyArray mapToPathnames(ThreadContext context, RubyClass clazz, IRubyObject ary) {
RubyArray paths = ary.convertToArray();
for (int i = 0; i < paths.size(); i++) {
RubyString path = paths.eltOk(i).convertToString();
paths.store(i, newInstance(context, clazz, path));
}
return paths;
}
private static JavaSites.PathnameSites sites(ThreadContext context) {
return context.sites.Pathname;
}
}