/******************************************************************************* * Copyright (c) 2009-2011 Luaj.org. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. ******************************************************************************/ package org.luaj.vm2.lib; import java.io.ByteArrayOutputStream; import java.io.EOFException; import java.io.IOException; import org.luaj.vm2.Globals; import org.luaj.vm2.LuaString; import org.luaj.vm2.LuaTable; import org.luaj.vm2.LuaValue; import org.luaj.vm2.Varargs; /** * Abstract base class extending {@link LibFunction} which implements the * core of the lua standard {@code io} library. * <p> * It contains the implementation of the io library support that is common to * the JSE and JME platforms. * In practice on of the concrete IOLib subclasses is chosen: * {@link org.luaj.vm2.lib.jse.JseIoLib} for the JSE platform, and * {@link org.luaj.vm2.lib.jme.JmeIoLib} for the JME platform. * <p> * The JSE implementation conforms almost completely to the C-based lua library, * while the JME implementation follows closely except in the area of random-access files, * which are difficult to support properly on JME. * <p> * Typically, this library is included as part of a call to either * {@link JsePlatform#standardGlobals()} or {@link JmePlatform#standardGlobals()} * <pre> {@code * Globals globals = JsePlatform.standardGlobals(); * globals.get("io").get("write").call(LuaValue.valueOf("hello, world\n")); * } </pre> * In this example the platform-specific {@link JseIoLib} library will be loaded, which will include * the base functionality provided by this class, whereas the {@link JsePlatform} would load the * {@link JseIoLib}. * <p> * To instantiate and use it directly, * link it into your globals table via {@link LuaValue#load(LuaValue)} using code such as: * <pre> {@code * Globals globals = new Globals(); * globals.load(new JseBaseLib()); * globals.load(new PackageLib()); * globals.load(new OsLib()); * globals.get("io").get("write").call(LuaValue.valueOf("hello, world\n")); * } </pre> * <p> * This has been implemented to match as closely as possible the behavior in the corresponding library in C. * @see LibFunction * @see JsePlatform * @see JmePlatform * @see JseIoLib * @see JmeIoLib * @see <a href="http://www.lua.org/manual/5.1/manual.html#5.7">http://www.lua.org/manual/5.1/manual.html#5.7</a> */ abstract public class IoLib extends TwoArgFunction { abstract protected class File extends LuaValue{ abstract public void write( LuaString string ) throws IOException; abstract public void flush() throws IOException; abstract public boolean isstdfile(); abstract public void close() throws IOException; abstract public boolean isclosed(); // returns new position abstract public int seek(String option, int bytecount) throws IOException; abstract public void setvbuf(String mode, int size); // get length remaining to read abstract public int remaining() throws IOException; // peek ahead one character abstract public int peek() throws IOException, EOFException; // return char if read, -1 if eof, throw IOException on other exception abstract public int read() throws IOException, EOFException; // return number of bytes read if positive, false if eof, throw IOException on other exception abstract public int read(byte[] bytes, int offset, int length) throws IOException; // delegate method access to file methods table public LuaValue get( LuaValue key ) { return filemethods.get(key); } // essentially a userdata instance public int type() { return LuaValue.TUSERDATA; } public String typename() { return "userdata"; } // displays as "file" type public String tojstring() { return "file: " + Integer.toHexString(hashCode()); } } /** Enumerated value representing stdin */ protected static final int FTYPE_STDIN = 0; /** Enumerated value representing stdout */ protected static final int FTYPE_STDOUT = 1; /** Enumerated value representing stderr */ protected static final int FTYPE_STDERR = 2; /** Enumerated value representing a file type for a named file */ protected static final int FTYPE_NAMED = 3; /** * Wrap the standard input. * @return File * @throws IOException */ abstract protected File wrapStdin() throws IOException; /** * Wrap the standard output. * @return File * @throws IOException */ abstract protected File wrapStdout() throws IOException; /** * Wrap the standard error output. * @return File * @throws IOException */ abstract protected File wrapStderr() throws IOException; /** * Open a file in a particular mode. * @param filename * @param readMode true if opening in read mode * @param appendMode true if opening in append mode * @param updateMode true if opening in update mode * @param binaryMode true if opening in binary mode * @return File object if successful * @throws IOException if could not be opened */ abstract protected File openFile( String filename, boolean readMode, boolean appendMode, boolean updateMode, boolean binaryMode ) throws IOException; /** * Open a temporary file. * @return File object if successful * @throws IOException if could not be opened */ abstract protected File tmpFile() throws IOException; /** * Start a new process and return a file for input or output * @param prog the program to execute * @param mode "r" to read, "w" to write * @return File to read to or write from * @throws IOException if an i/o exception occurs */ abstract protected File openProgram(String prog, String mode) throws IOException; private File infile = null; private File outfile = null; private File errfile = null; private static final LuaValue STDIN = valueOf("stdin"); private static final LuaValue STDOUT = valueOf("stdout"); private static final LuaValue STDERR = valueOf("stderr"); private static final LuaValue FILE = valueOf("file"); private static final LuaValue CLOSED_FILE = valueOf("closed file"); private static final int IO_CLOSE = 0; private static final int IO_FLUSH = 1; private static final int IO_INPUT = 2; private static final int IO_LINES = 3; private static final int IO_OPEN = 4; private static final int IO_OUTPUT = 5; private static final int IO_POPEN = 6; private static final int IO_READ = 7; private static final int IO_TMPFILE = 8; private static final int IO_TYPE = 9; private static final int IO_WRITE = 10; private static final int FILE_CLOSE = 11; private static final int FILE_FLUSH = 12; private static final int FILE_LINES = 13; private static final int FILE_READ = 14; private static final int FILE_SEEK = 15; private static final int FILE_SETVBUF = 16; private static final int FILE_WRITE = 17; private static final int IO_INDEX = 18; private static final int LINES_ITER = 19; public static final String[] IO_NAMES = { "close", "flush", "input", "lines", "open", "output", "popen", "read", "tmpfile", "type", "write", }; public static final String[] FILE_NAMES = { "close", "flush", "lines", "read", "seek", "setvbuf", "write", }; LuaTable filemethods; protected Globals globals; public LuaValue call(LuaValue modname, LuaValue env) { globals = env.checkglobals(); // io lib functions LuaTable t = new LuaTable(); bind(t, IoLibV.class, IO_NAMES ); // create file methods table filemethods = new LuaTable(); bind(filemethods, IoLibV.class, FILE_NAMES, FILE_CLOSE ); // set up file metatable LuaTable mt = new LuaTable(); bind(mt, IoLibV.class, new String[] { "__index" }, IO_INDEX ); t.setmetatable( mt ); // all functions link to library instance setLibInstance( t ); setLibInstance( filemethods ); setLibInstance( mt ); // return the table env.set("io", t); env.get("package").get("loaded").set("io", t); return t; } private void setLibInstance(LuaTable t) { LuaValue[] k = t.keys(); for ( int i=0, n=k.length; i<n; i++ ) ((IoLibV) t.get(k[i])).iolib = this; } static final class IoLibV extends VarArgFunction { private File f; public IoLib iolib; public IoLibV() { } public IoLibV(File f, String name, int opcode, IoLib iolib) { super(); this.f = f; this.name = name; this.opcode = opcode; this.iolib = iolib; } public Varargs invoke(Varargs args) { try { switch ( opcode ) { case IO_FLUSH: return iolib._io_flush(); case IO_TMPFILE: return iolib._io_tmpfile(); case IO_CLOSE: return iolib._io_close(args.arg1()); case IO_INPUT: return iolib._io_input(args.arg1()); case IO_OUTPUT: return iolib._io_output(args.arg1()); case IO_TYPE: return iolib._io_type(args.arg1()); case IO_POPEN: return iolib._io_popen(args.checkjstring(1),args.optjstring(2,"r")); case IO_OPEN: return iolib._io_open(args.checkjstring(1), args.optjstring(2,"r")); case IO_LINES: return iolib._io_lines(args.isvalue(1)? args.checkjstring(1): null); case IO_READ: return iolib._io_read(args); case IO_WRITE: return iolib._io_write(args); case FILE_CLOSE: return iolib._file_close(args.arg1()); case FILE_FLUSH: return iolib._file_flush(args.arg1()); case FILE_SETVBUF: return iolib._file_setvbuf(args.arg1(),args.checkjstring(2),args.optint(3,1024)); case FILE_LINES: return iolib._file_lines(args.arg1()); case FILE_READ: return iolib._file_read(args.arg1(),args.subargs(2)); case FILE_SEEK: return iolib._file_seek(args.arg1(),args.optjstring(2,"cur"),args.optint(3,0)); case FILE_WRITE: return iolib._file_write(args.arg1(),args.subargs(2)); case IO_INDEX: return iolib._io_index(args.arg(2)); case LINES_ITER: return iolib._lines_iter(f); } } catch ( IOException ioe ) { return errorresult(ioe); } return NONE; } } private File input() { return infile!=null? infile: (infile=ioopenfile(FTYPE_STDIN, "-","r")); } // io.flush() -> bool public Varargs _io_flush() throws IOException { checkopen(output()); outfile.flush(); return LuaValue.TRUE; } // io.tmpfile() -> file public Varargs _io_tmpfile() throws IOException { return tmpFile(); } // io.close([file]) -> void public Varargs _io_close(LuaValue file) throws IOException { File f = file.isnil()? output(): checkfile(file); checkopen(f); return ioclose(f); } // io.input([file]) -> file public Varargs _io_input(LuaValue file) { infile = file.isnil()? input(): file.isstring()? ioopenfile(FTYPE_NAMED, file.checkjstring(),"r"): checkfile(file); return infile; } // io.output(filename) -> file public Varargs _io_output(LuaValue filename) { outfile = filename.isnil()? output(): filename.isstring()? ioopenfile(FTYPE_NAMED, filename.checkjstring(),"w"): checkfile(filename); return outfile; } // io.type(obj) -> "file" | "closed file" | nil public Varargs _io_type(LuaValue obj) { File f = optfile(obj); return f!=null? f.isclosed()? CLOSED_FILE: FILE: NIL; } // io.popen(prog, [mode]) -> file public Varargs _io_popen(String prog, String mode) throws IOException { return openProgram(prog, mode); } // io.open(filename, [mode]) -> file | nil,err public Varargs _io_open(String filename, String mode) throws IOException { return rawopenfile(FTYPE_NAMED, filename, mode); } // io.lines(filename) -> iterator public Varargs _io_lines(String filename) { infile = filename==null? input(): ioopenfile(FTYPE_NAMED, filename,"r"); checkopen(infile); return lines(infile); } // io.read(...) -> (...) public Varargs _io_read(Varargs args) throws IOException { checkopen(input()); return ioread(infile,args); } // io.write(...) -> void public Varargs _io_write(Varargs args) throws IOException { checkopen(output()); return iowrite(outfile,args); } // file:close() -> void public Varargs _file_close(LuaValue file) throws IOException { return ioclose(checkfile(file)); } // file:flush() -> void public Varargs _file_flush(LuaValue file) throws IOException { checkfile(file).flush(); return LuaValue.TRUE; } // file:setvbuf(mode,[size]) -> void public Varargs _file_setvbuf(LuaValue file, String mode, int size) { checkfile(file).setvbuf(mode,size); return LuaValue.TRUE; } // file:lines() -> iterator public Varargs _file_lines(LuaValue file) { return lines(checkfile(file)); } // file:read(...) -> (...) public Varargs _file_read(LuaValue file, Varargs subargs) throws IOException { return ioread(checkfile(file),subargs); } // file:seek([whence][,offset]) -> pos | nil,error public Varargs _file_seek(LuaValue file, String whence, int offset) throws IOException { return valueOf( checkfile(file).seek(whence,offset) ); } // file:write(...) -> void public Varargs _file_write(LuaValue file, Varargs subargs) throws IOException { return iowrite(checkfile(file),subargs); } // __index, returns a field public Varargs _io_index(LuaValue v) { return v.equals(STDOUT)?output(): v.equals(STDIN)? input(): v.equals(STDERR)? errput(): NIL; } // lines iterator(s,var) -> var' public Varargs _lines_iter(LuaValue file) throws IOException { return freadline(checkfile(file)); } private File output() { return outfile!=null? outfile: (outfile=ioopenfile(FTYPE_STDOUT,"-","w")); } private File errput() { return errfile!=null? errfile: (errfile=ioopenfile(FTYPE_STDERR,"-","w")); } private File ioopenfile(int filetype, String filename, String mode) { try { return rawopenfile(filetype, filename, mode); } catch ( Exception e ) { error("io error: "+e.getMessage()); return null; } } private static Varargs ioclose(File f) throws IOException { if ( f.isstdfile() ) return errorresult("cannot close standard file"); else { f.close(); return successresult(); } } private static Varargs successresult() { return LuaValue.TRUE; } private static Varargs errorresult(Exception ioe) { String s = ioe.getMessage(); return errorresult("io error: "+(s!=null? s: ioe.toString())); } private static Varargs errorresult(String errortext) { return varargsOf(NIL, valueOf(errortext)); } private Varargs lines(final File f) { try { return new IoLibV(f,"lnext",LINES_ITER,this); } catch ( Exception e ) { return error("lines: "+e); } } private static Varargs iowrite(File f, Varargs args) throws IOException { for ( int i=1, n=args.narg(); i<=n; i++ ) f.write( args.checkstring(i) ); return f; } private Varargs ioread(File f, Varargs args) throws IOException { int i,n=args.narg(); LuaValue[] v = new LuaValue[n]; LuaValue ai,vi; LuaString fmt; for ( i=0; i<n; ) { item: switch ( (ai = args.arg(i+1)).type() ) { case LuaValue.TNUMBER: vi = freadbytes(f,ai.toint()); break item; case LuaValue.TSTRING: fmt = ai.checkstring(); if ( fmt.m_length == 2 && fmt.m_bytes[fmt.m_offset] == '*' ) { switch ( fmt.m_bytes[fmt.m_offset+1] ) { case 'n': vi = freadnumber(f); break item; case 'l': vi = freadline(f); break item; case 'a': vi = freadall(f); break item; } } default: return argerror( i+1, "(invalid format)" ); } if ( (v[i++] = vi).isnil() ) break; } return i==0? NIL: varargsOf(v, 0, i); } private static File checkfile(LuaValue val) { File f = optfile(val); if ( f == null ) argerror(1,"file"); checkopen( f ); return f; } private static File optfile(LuaValue val) { return (val instanceof File)? (File) val: null; } private static File checkopen(File file) { if ( file.isclosed() ) error("attempt to use a closed file"); return file; } private File rawopenfile(int filetype, String filename, String mode) throws IOException { switch (filetype) { case FTYPE_STDIN: return wrapStdin(); case FTYPE_STDOUT: return wrapStdout(); case FTYPE_STDERR: return wrapStderr(); } boolean isreadmode = mode.startsWith("r"); boolean isappend = mode.startsWith("a"); boolean isupdate = mode.indexOf("+") > 0; boolean isbinary = mode.endsWith("b"); return openFile( filename, isreadmode, isappend, isupdate, isbinary ); } // ------------- file reading utilitied ------------------ public static LuaValue freadbytes(File f, int count) throws IOException { byte[] b = new byte[count]; int r; if ( ( r = f.read(b,0,b.length) ) < 0 ) return NIL; return LuaString.valueOf(b, 0, r); } public static LuaValue freaduntil(File f,boolean lineonly) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); int c; try { if ( lineonly ) { loop: while ( (c = f.read()) > 0 ) { switch ( c ) { case '\r': break; case '\n': break loop; default: baos.write(c); break; } } } else { while ( (c = f.read()) > 0 ) baos.write(c); } } catch ( EOFException e ) { c = -1; } return ( c < 0 && baos.size() == 0 )? (LuaValue) NIL: (LuaValue) LuaString.valueOf(baos.toByteArray()); } public static LuaValue freadline(File f) throws IOException { return freaduntil(f,true); } public static LuaValue freadall(File f) throws IOException { int n = f.remaining(); if ( n >= 0 ) { return freadbytes(f, n); } else { return freaduntil(f,false); } } public static LuaValue freadnumber(File f) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); freadchars(f," \t\r\n",null); freadchars(f,"-+",baos); //freadchars(f,"0",baos); //freadchars(f,"xX",baos); freadchars(f,"0123456789",baos); freadchars(f,".",baos); freadchars(f,"0123456789",baos); //freadchars(f,"eEfFgG",baos); // freadchars(f,"+-",baos); //freadchars(f,"0123456789",baos); String s = baos.toString(); return s.length()>0? valueOf( Double.parseDouble(s) ): NIL; } private static void freadchars(File f, String chars, ByteArrayOutputStream baos) throws IOException { int c; while ( true ) { c = f.peek(); if ( chars.indexOf(c) < 0 ) { return; } f.read(); if ( baos != null ) baos.write( c ); } } }