FileNodes.java
/*
* Copyright (c) 2013, 2014 Oracle and/or its affiliates. All rights reserved. This
* code is released under a tri EPL/GPL/LGPL license. You can use it,
* redistribute it and/or modify it under the terms of the:
*
* Eclipse Public License version 1.0
* GNU General Public License version 2
* GNU Lesser General Public License version 2.1
*/
package org.jruby.truffle.nodes.core;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.source.SourceSection;
import jnr.posix.FileStat;
import org.jruby.truffle.runtime.RubyContext;
import org.jruby.truffle.runtime.UndefinedPlaceholder;
import org.jruby.truffle.runtime.core.*;
import org.jruby.util.ByteList;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
@CoreClass(name = "File")
public abstract class FileNodes {
@CoreMethod(names = "absolute_path", onSingleton = true, required = 1)
public abstract static class AbsolutePathNode extends CoreMethodNode {
public AbsolutePathNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}
public AbsolutePathNode(AbsolutePathNode prev) {
super(prev);
}
@Specialization
public RubyString absolutePath(RubyString path) {
notDesignedForCompilation();
return getContext().makeString(new File(path.toString()).getAbsolutePath());
}
}
@CoreMethod(names = "close")
public abstract static class CloseNode extends CoreMethodNode {
public CloseNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}
public CloseNode(CloseNode prev) {
super(prev);
}
@Specialization
public RubyNilClass close(RubyFile file) {
notDesignedForCompilation();
file.close();
return getContext().getCoreLibrary().getNilObject();
}
}
@CoreMethod(names = { "delete", "unlink" }, onSingleton = true, required = 1)
public abstract static class DeleteNode extends CoreMethodNode {
public DeleteNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}
public DeleteNode(DeleteNode prev) {
super(prev);
}
@Specialization
public int delete(RubyString file) {
notDesignedForCompilation();
new File(file.toString()).delete();
return 1;
}
}
@CoreMethod(names = "directory?", onSingleton = true, required = 1)
public abstract static class DirectoryNode extends CoreMethodNode {
public DirectoryNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}
public DirectoryNode(DirectoryNode prev) {
super(prev);
}
@Specialization
public boolean directory(RubyString path) {
notDesignedForCompilation();
return new File(path.toString()).isDirectory();
}
}
@CoreMethod(names = "dirname", onSingleton = true, required = 1)
public abstract static class DirnameNode extends CoreMethodNode {
public DirnameNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}
public DirnameNode(DirnameNode prev) {
super(prev);
}
@Specialization
public RubyString dirname(RubyString path) {
notDesignedForCompilation();
final String parent = new File(path.toString()).getParent();
if (parent == null) {
return getContext().makeString(".");
} else {
return getContext().makeString(parent);
}
}
}
@CoreMethod(names = "each_line", needsBlock = true)
public abstract static class EachLineNode extends YieldingCoreMethodNode {
public EachLineNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}
public EachLineNode(EachLineNode prev) {
super(prev);
}
@Specialization
public RubyNilClass eachLine(VirtualFrame frame, RubyFile file, RubyProc block) {
notDesignedForCompilation();
final RubyContext context = getContext();
// TODO(cs): this buffered reader may consume too much
final BufferedReader lineReader = new BufferedReader(file.getReader());
while (true) {
String line;
try {
line = lineReader.readLine();
} catch (IOException e) {
throw new RuntimeException(e);
}
if (line == null) {
break;
}
yield(frame, block, context.makeString(line));
}
return getContext().getCoreLibrary().getNilObject();
}
}
@CoreMethod(names = "executable?", onSingleton = true, required = 1)
public abstract static class ExecutableNode extends CoreMethodNode {
public ExecutableNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}
public ExecutableNode(ExecutableNode prev) {
super(prev);
}
@Specialization
public boolean executable(RubyString path) {
notDesignedForCompilation();
return new File(path.toString()).canExecute();
}
}
@CoreMethod(names = {"exist?", "exists?"}, onSingleton = true, required = 1)
public abstract static class ExistsNode extends CoreMethodNode {
public ExistsNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}
public ExistsNode(ExistsNode prev) {
super(prev);
}
@Specialization
public boolean exists(RubyString path) {
notDesignedForCompilation();
return new File(path.toString()).exists();
}
}
@CoreMethod(names = "expand_path", onSingleton = true, required = 1, optional = 1)
public abstract static class ExpandPathNode extends CoreMethodNode {
public ExpandPathNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}
public ExpandPathNode(ExpandPathNode prev) {
super(prev);
}
@Specialization
public RubyString expandPath(RubyString path, @SuppressWarnings("unused") UndefinedPlaceholder dir) {
return getContext().makeString(RubyFile.expandPath(path.toString()));
}
@Specialization
public RubyString expandPath(RubyString path, RubyString dir) {
notDesignedForCompilation();
return getContext().makeString(RubyFile.expandPath(path.toString(), dir.toString()));
}
}
@CoreMethod(names = "file?", onSingleton = true, required = 1)
public abstract static class FileNode extends CoreMethodNode {
public FileNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}
public FileNode(FileNode prev) {
super(prev);
}
@Specialization
public boolean file(RubyString path) {
notDesignedForCompilation();
return new File(path.toString()).isFile();
}
}
@CoreMethod(names = "join", onSingleton = true, argumentsAsArray = true)
public abstract static class JoinNode extends CoreMethodNode {
public JoinNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}
public JoinNode(JoinNode prev) {
super(prev);
}
@Specialization
public RubyString join(Object[] parts) {
notDesignedForCompilation();
final StringBuilder builder = new StringBuilder();
join(builder, parts);
return getContext().makeString(builder.toString());
}
@TruffleBoundary
public static void join(StringBuilder builder, Object[] parts) {
notDesignedForCompilation();
for (int n = 0; n < parts.length; n++) {
if (n > 0) {
builder.append(File.separator);
}
if (parts[n] instanceof RubyArray) {
join(builder, ((RubyArray) parts[n]).slowToArray());
} else {
builder.append(parts[n].toString());
}
}
}
}
@CoreMethod(names = "path", onSingleton = true, required = 1)
public abstract static class PathNode extends CoreMethodNode {
public PathNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}
public PathNode(PathNode prev) {
super(prev);
}
@Specialization
public RubyString path(RubyString path) {
notDesignedForCompilation();
return getContext().makeString(path.toString());
}
}
@CoreMethod(names = "puts", required = 1)
public abstract static class PutsNode extends CoreMethodNode {
public PutsNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}
public PutsNode(PutsNode prev) {
super(prev);
}
@Specialization
public RubyNilClass puts(RubyFile file, RubyString string) {
notDesignedForCompilation();
try {
final Writer writer = file.getWriter();
writer.write(string.toString());
writer.write("\n");
writer.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
return getContext().getCoreLibrary().getNilObject();
}
}
@CoreMethod(names = "read", isModuleFunction = true, needsSelf = false, required = 1)
public abstract static class ReadFunctionNode extends CoreMethodNode {
public ReadFunctionNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}
public ReadFunctionNode(ReadFunctionNode prev) {
super(prev);
}
@Specialization
public RubyString read(RubyString path) {
notDesignedForCompilation();
try {
return new RubyString(getContext().getCoreLibrary().getStringClass(),
new ByteList(Files.readAllBytes(Paths.get(path.toString()))));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
@CoreMethod(names = "read")
public abstract static class ReadNode extends CoreMethodNode {
public ReadNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}
public ReadNode(ReadNode prev) {
super(prev);
}
@Specialization
public RubyString read(RubyFile file) {
notDesignedForCompilation();
try {
final Reader reader = file.getReader();
final StringBuilder builder = new StringBuilder();
while (true) {
final int c = reader.read();
if (c == -1) {
break;
}
builder.append((char) c);
}
return getContext().makeString(builder.toString());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
@CoreMethod(names = "size?", onSingleton = true, required = 1)
public abstract static class SizeNode extends CoreMethodNode {
public SizeNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}
public SizeNode(SizeNode prev) {
super(prev);
}
@Specialization
public Object read(RubyString file) {
notDesignedForCompilation();
final File f = new File(file.toString());
if (!f.exists()) {
return getContext().getCoreLibrary().getNilObject();
}
final long size = f.length();
if (size == 0) {
return getContext().getCoreLibrary().getNilObject();
}
return size;
}
}
@CoreMethod(names = "symlink?", onSingleton = true, required = 1)
public abstract static class SymlinkQueryNode extends CoreMethodNode {
public SymlinkQueryNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}
public SymlinkQueryNode(SymlinkQueryNode prev) {
super(prev);
}
@Specialization
public boolean symlinkQuery(RubyString fileName) {
notDesignedForCompilation();
try {
// Note: We can't use file.exists() to check whether the symlink
// exists or not, because that method returns false for existing
// but broken symlink. So, we try without the existence check,
// but in the try-catch block.
// MRI behavior: symlink? on broken symlink should return true.
FileStat stat = getContext().getRuntime().getPosix().allocateStat();
if (getContext().getRuntime().getPosix().lstat(fileName.toString(), stat) < 0) {
stat = null;
}
return (stat != null && stat.isSymlink());
} catch (SecurityException re) {
return false;
}
}
}
@CoreMethod(names = "write", required = 1)
public abstract static class WriteNode extends CoreMethodNode {
public WriteNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}
public WriteNode(WriteNode prev) {
super(prev);
}
@Specialization
public RubyNilClass write(RubyFile file, RubyString string) {
notDesignedForCompilation();
try {
final Writer writer = file.getWriter();
writer.write(string.toString());
writer.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
return getContext().getCoreLibrary().getNilObject();
}
}
}