|  | /******************************************************************************* | 
|  | * Copyright (c) 2009 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.IOException; | 
|  | import java.util.Calendar; | 
|  | import java.util.Date; | 
|  |  | 
|  | import org.luaj.vm2.Buffer; | 
|  | import org.luaj.vm2.Globals; | 
|  | import org.luaj.vm2.LuaTable; | 
|  | import org.luaj.vm2.LuaValue; | 
|  | import org.luaj.vm2.Varargs; | 
|  |  | 
|  | /** | 
|  | * Subclass of {@link LibFunction} which implements the standard lua {@code os} library. | 
|  | * <p> | 
|  | * It is a usable base with simplified stub functions | 
|  | * for library functions that cannot be implemented uniformly | 
|  | * on Jse and Jme. | 
|  | * <p> | 
|  | * This can be installed as-is on either platform, or extended | 
|  | * and refined to be used in a complete Jse implementation. | 
|  | * <p> | 
|  | * Because the nature of the {@code os} library is to encapsulate | 
|  | * os-specific features, the behavior of these functions varies considerably | 
|  | * from their counterparts in the C platform. | 
|  | * <p> | 
|  | * The following functions have limited implementations of features | 
|  | * that are not supported well on Jme: | 
|  | * <ul> | 
|  | * <li>{@code execute()}</li> | 
|  | * <li>{@code remove()}</li> | 
|  | * <li>{@code rename()}</li> | 
|  | * <li>{@code tmpname()}</li> | 
|  | * </ul> | 
|  | * <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(); | 
|  | * System.out.println( globals.get("os").get("time").call() ); | 
|  | * } </pre> | 
|  | * In this example the platform-specific {@link JseOsLib} library will be loaded, which will include | 
|  | * the base functionality provided by this class. | 
|  | * <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()); | 
|  | * System.out.println( globals.get("os").get("time").call() ); | 
|  | * } </pre> | 
|  | * <p> | 
|  | * @see LibFunction | 
|  | * @see JseOsLib | 
|  | * @see JsePlatform | 
|  | * @see JmePlatform | 
|  | * @see <a href="http://www.lua.org/manual/5.1/manual.html#5.8">http://www.lua.org/manual/5.1/manual.html#5.8</a> | 
|  | */ | 
|  | public class OsLib extends TwoArgFunction { | 
|  | public static String TMP_PREFIX    = ".luaj"; | 
|  | public static String TMP_SUFFIX    = "tmp"; | 
|  |  | 
|  | private static final int CLOCK     = 0; | 
|  | private static final int DATE      = 1; | 
|  | private static final int DIFFTIME  = 2; | 
|  | private static final int EXECUTE   = 3; | 
|  | private static final int EXIT      = 4; | 
|  | private static final int GETENV    = 5; | 
|  | private static final int REMOVE    = 6; | 
|  | private static final int RENAME    = 7; | 
|  | private static final int SETLOCALE = 8; | 
|  | private static final int TIME      = 9; | 
|  | private static final int TMPNAME   = 10; | 
|  |  | 
|  | private static final String[] NAMES = { | 
|  | "clock", | 
|  | "date", | 
|  | "difftime", | 
|  | "execute", | 
|  | "exit", | 
|  | "getenv", | 
|  | "remove", | 
|  | "rename", | 
|  | "setlocale", | 
|  | "time", | 
|  | "tmpname", | 
|  | }; | 
|  |  | 
|  | private static final long t0 = System.currentTimeMillis(); | 
|  | private static long tmpnames = t0; | 
|  |  | 
|  | protected Globals globals; | 
|  |  | 
|  | /** | 
|  | * Create and OsLib instance. | 
|  | */ | 
|  | public OsLib() { | 
|  | } | 
|  |  | 
|  | public LuaValue call(LuaValue modname, LuaValue env) { | 
|  | globals = env.checkglobals(); | 
|  | LuaTable os = new LuaTable(); | 
|  | for (int i = 0; i < NAMES.length; ++i) | 
|  | os.set(NAMES[i], new OsLibFunc(i, NAMES[i])); | 
|  | env.set("os", os); | 
|  | env.get("package").get("loaded").set("os", os); | 
|  | return os; | 
|  | } | 
|  |  | 
|  | class OsLibFunc extends VarArgFunction { | 
|  | public OsLibFunc(int opcode, String name) { | 
|  | this.opcode = opcode; | 
|  | this.name = name; | 
|  | } | 
|  | public Varargs invoke(Varargs args) { | 
|  | try { | 
|  | switch ( opcode ) { | 
|  | case CLOCK: | 
|  | return valueOf(clock()); | 
|  | case DATE: { | 
|  | String s = args.optjstring(1, "%c"); | 
|  | double t = args.isnumber(2)? args.todouble(2): time(null); | 
|  | if (s.equals("*t")) { | 
|  | Calendar d = Calendar.getInstance(); | 
|  | d.setTime(new Date((long)(t*1000))); | 
|  | LuaTable tbl = LuaValue.tableOf(); | 
|  | tbl.set("year", LuaValue.valueOf(d.get(Calendar.YEAR))); | 
|  | tbl.set("month", LuaValue.valueOf(d.get(Calendar.MONTH)+1)); | 
|  | tbl.set("day", LuaValue.valueOf(d.get(Calendar.DAY_OF_MONTH))); | 
|  | tbl.set("hour", LuaValue.valueOf(d.get(Calendar.HOUR))); | 
|  | tbl.set("min", LuaValue.valueOf(d.get(Calendar.MINUTE))); | 
|  | tbl.set("sec", LuaValue.valueOf(d.get(Calendar.SECOND))); | 
|  | tbl.set("wday", LuaValue.valueOf(d.get(Calendar.DAY_OF_WEEK))); | 
|  | tbl.set("yday", LuaValue.valueOf(d.get(0x6))); // Day of year | 
|  | tbl.set("isdst", LuaValue.valueOf(isDaylightSavingsTime(d))); | 
|  | return tbl; | 
|  | } | 
|  | return valueOf( date(s, t==-1? time(null): t) ); | 
|  | } | 
|  | case DIFFTIME: | 
|  | return valueOf(difftime(args.checkdouble(1),args.checkdouble(2))); | 
|  | case EXECUTE: | 
|  | return execute(args.optjstring(1, null)); | 
|  | case EXIT: | 
|  | exit(args.optint(1, 0)); | 
|  | return NONE; | 
|  | case GETENV: { | 
|  | final String val = getenv(args.checkjstring(1)); | 
|  | return val!=null? valueOf(val): NIL; | 
|  | } | 
|  | case REMOVE: | 
|  | remove(args.checkjstring(1)); | 
|  | return LuaValue.TRUE; | 
|  | case RENAME: | 
|  | rename(args.checkjstring(1), args.checkjstring(2)); | 
|  | return LuaValue.TRUE; | 
|  | case SETLOCALE: { | 
|  | String s = setlocale(args.optjstring(1,null), args.optjstring(2, "all")); | 
|  | return s!=null? valueOf(s): NIL; | 
|  | } | 
|  | case TIME: | 
|  | return valueOf(time(args.opttable(1, null))); | 
|  | case TMPNAME: | 
|  | return valueOf(tmpname()); | 
|  | } | 
|  | return NONE; | 
|  | } catch ( IOException e ) { | 
|  | return varargsOf(NIL, valueOf(e.getMessage())); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @return an approximation of the amount in seconds of CPU time used by | 
|  | * the program.  For luaj this simple returns the elapsed time since the | 
|  | * OsLib class was loaded. | 
|  | */ | 
|  | protected double clock() { | 
|  | return (System.currentTimeMillis()-t0) / 1000.; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns the number of seconds from time t1 to time t2. | 
|  | * In POSIX, Windows, and some other systems, this value is exactly t2-t1. | 
|  | * @param t2 | 
|  | * @param t1 | 
|  | * @return diffeence in time values, in seconds | 
|  | */ | 
|  | protected double difftime(double t2, double t1) { | 
|  | return t2 - t1; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * If the time argument is present, this is the time to be formatted | 
|  | * (see the os.time function for a description of this value). | 
|  | * Otherwise, date formats the current time. | 
|  | * | 
|  | * Date returns the date as a string, | 
|  | * formatted according to the same rules as ANSII strftime, but without | 
|  | * support for %g, %G, or %V. | 
|  | * | 
|  | * When called without arguments, date returns a reasonable date and | 
|  | * time representation that depends on the host system and on the | 
|  | * current locale (that is, os.date() is equivalent to os.date("%c")). | 
|  | * | 
|  | * @param format | 
|  | * @param time time since epoch, or -1 if not supplied | 
|  | * @return a LString or a LTable containing date and time, | 
|  | * formatted according to the given string format. | 
|  | */ | 
|  | public String date(String format, double time) { | 
|  | Calendar d = Calendar.getInstance(); | 
|  | d.setTime(new Date((long)(time*1000))); | 
|  | if (format.startsWith("!")) { | 
|  | time -= timeZoneOffset(d); | 
|  | d.setTime(new Date((long)(time*1000))); | 
|  | format = format.substring(1); | 
|  | } | 
|  | byte[] fmt = format.getBytes(); | 
|  | final int n = fmt.length; | 
|  | Buffer result = new Buffer(n); | 
|  | byte c; | 
|  | for ( int i = 0; i < n; ) { | 
|  | switch ( c = fmt[i++ ] ) { | 
|  | case '\n': | 
|  | result.append( "\n" ); | 
|  | break; | 
|  | default: | 
|  | result.append( c ); | 
|  | break; | 
|  | case '%': | 
|  | if (i >= n) break; | 
|  | switch ( c = fmt[i++ ] ) { | 
|  | default: | 
|  | LuaValue.argerror(1, "invalid conversion specifier '%"+c+"'"); | 
|  | break; | 
|  | case '%': | 
|  | result.append( (byte)'%' ); | 
|  | break; | 
|  | case 'a': | 
|  | result.append(WeekdayNameAbbrev[d.get(Calendar.DAY_OF_WEEK)-1]); | 
|  | break; | 
|  | case 'A': | 
|  | result.append(WeekdayName[d.get(Calendar.DAY_OF_WEEK)-1]); | 
|  | break; | 
|  | case 'b': | 
|  | result.append(MonthNameAbbrev[d.get(Calendar.MONTH)]); | 
|  | break; | 
|  | case 'B': | 
|  | result.append(MonthName[d.get(Calendar.MONTH)]); | 
|  | break; | 
|  | case 'c': | 
|  | result.append(date("%a %b %d %H:%M:%S %Y", time)); | 
|  | break; | 
|  | case 'd': | 
|  | result.append(String.valueOf(100+d.get(Calendar.DAY_OF_MONTH)).substring(1)); | 
|  | break; | 
|  | case 'H': | 
|  | result.append(String.valueOf(100+d.get(Calendar.HOUR_OF_DAY)).substring(1)); | 
|  | break; | 
|  | case 'I': | 
|  | result.append(String.valueOf(100+(d.get(Calendar.HOUR_OF_DAY)%12)).substring(1)); | 
|  | break; | 
|  | case 'j': { // day of year. | 
|  | Calendar y0 = beginningOfYear(d); | 
|  | int dayOfYear = (int) ((d.getTime().getTime() - y0.getTime().getTime()) / (24 * 3600L * 1000L)); | 
|  | result.append(String.valueOf(1001+dayOfYear).substring(1)); | 
|  | break; | 
|  | } | 
|  | case 'm': | 
|  | result.append(String.valueOf(101+d.get(Calendar.MONTH)).substring(1)); | 
|  | break; | 
|  | case 'M': | 
|  | result.append(String.valueOf(100+d.get(Calendar.MINUTE)).substring(1)); | 
|  | break; | 
|  | case 'p': | 
|  | result.append(d.get(Calendar.HOUR_OF_DAY) < 12? "AM": "PM"); | 
|  | break; | 
|  | case 'S': | 
|  | result.append(String.valueOf(100+d.get(Calendar.SECOND)).substring(1)); | 
|  | break; | 
|  | case 'U': | 
|  | result.append(String.valueOf(weekNumber(d, 0))); | 
|  | break; | 
|  | case 'w': | 
|  | result.append(String.valueOf((d.get(Calendar.DAY_OF_WEEK)+6)%7)); | 
|  | break; | 
|  | case 'W': | 
|  | result.append(String.valueOf(weekNumber(d, 1))); | 
|  | break; | 
|  | case 'x': | 
|  | result.append(date("%m/%d/%y", time)); | 
|  | break; | 
|  | case 'X': | 
|  | result.append(date("%H:%M:%S", time)); | 
|  | break; | 
|  | case 'y': | 
|  | result.append(String.valueOf(d.get(Calendar.YEAR)).substring(2)); | 
|  | break; | 
|  | case 'Y': | 
|  | result.append(String.valueOf(d.get(Calendar.YEAR))); | 
|  | break; | 
|  | case 'z': { | 
|  | final int tzo = timeZoneOffset(d) / 60; | 
|  | final int a = Math.abs(tzo); | 
|  | final String h = String.valueOf(100 + a / 60).substring(1); | 
|  | final String m = String.valueOf(100 + a % 60).substring(1); | 
|  | result.append((tzo>=0? "+": "-") + h + m); | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | return result.tojstring(); | 
|  | } | 
|  |  | 
|  | private static final String[] WeekdayNameAbbrev = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; | 
|  | private static final String[] WeekdayName = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; | 
|  | private static final String[] MonthNameAbbrev = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; | 
|  | private static final String[] MonthName = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; | 
|  |  | 
|  | private Calendar beginningOfYear(Calendar d) { | 
|  | Calendar y0 = Calendar.getInstance(); | 
|  | y0.setTime(d.getTime()); | 
|  | y0.set(Calendar.MONTH, 0); | 
|  | y0.set(Calendar.DAY_OF_MONTH, 1); | 
|  | y0.set(Calendar.HOUR_OF_DAY, 0); | 
|  | y0.set(Calendar.MINUTE, 0); | 
|  | y0.set(Calendar.SECOND, 0); | 
|  | y0.set(Calendar.MILLISECOND, 0); | 
|  | return y0; | 
|  | } | 
|  |  | 
|  | private int weekNumber(Calendar d, int startDay) { | 
|  | Calendar y0 =  beginningOfYear(d); | 
|  | y0.set(Calendar.DAY_OF_MONTH, 1 + (startDay + 8 - y0.get(Calendar.DAY_OF_WEEK)) % 7); | 
|  | if (y0.after(d)) { | 
|  | y0.set(Calendar.YEAR, y0.get(Calendar.YEAR) - 1); | 
|  | y0.set(Calendar.DAY_OF_MONTH, 1 + (startDay + 8 - y0.get(Calendar.DAY_OF_WEEK)) % 7); | 
|  | } | 
|  | long dt = d.getTime().getTime() - y0.getTime().getTime(); | 
|  | return 1 + (int) (dt / (7L * 24L * 3600L * 1000L)); | 
|  | } | 
|  |  | 
|  | private int timeZoneOffset(Calendar d) { | 
|  | int localStandarTimeMillis = ( | 
|  | d.get(Calendar.HOUR_OF_DAY) * 3600 + | 
|  | d.get(Calendar.MINUTE) * 60 + | 
|  | d.get(Calendar.SECOND)) * 1000; | 
|  | return d.getTimeZone().getOffset( | 
|  | 1, | 
|  | d.get(Calendar.YEAR), | 
|  | d.get(Calendar.MONTH), | 
|  | d.get(Calendar.DAY_OF_MONTH), | 
|  | d.get(Calendar.DAY_OF_WEEK), | 
|  | localStandarTimeMillis) / 1000; | 
|  | } | 
|  |  | 
|  | private boolean isDaylightSavingsTime(Calendar d) { | 
|  | return timeZoneOffset(d) != d.getTimeZone().getRawOffset() / 1000; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * This function is equivalent to the C function system. | 
|  | * It passes command to be executed by an operating system shell. | 
|  | * It returns a status code, which is system-dependent. | 
|  | * If command is absent, then it returns nonzero if a shell | 
|  | * is available and zero otherwise. | 
|  | * @param command command to pass to the system | 
|  | */ | 
|  | protected Varargs execute(String command) { | 
|  | return varargsOf(NIL, valueOf("exit"), ONE); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Calls the C function exit, with an optional code, to terminate the host program. | 
|  | * @param code | 
|  | */ | 
|  | protected void exit(int code) { | 
|  | System.exit(code); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns the value of the process environment variable varname, | 
|  | * or null if the variable is not defined. | 
|  | * @param varname | 
|  | * @return String value, or null if not defined | 
|  | */ | 
|  | protected String getenv(String varname) { | 
|  | return System.getProperty(varname); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Deletes the file or directory with the given name. | 
|  | * Directories must be empty to be removed. | 
|  | * If this function fails, it throws and IOException | 
|  | * | 
|  | * @param filename | 
|  | * @throws IOException if it fails | 
|  | */ | 
|  | protected void remove(String filename) throws IOException { | 
|  | throw new IOException( "not implemented" ); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Renames file or directory named oldname to newname. | 
|  | * If this function fails,it throws and IOException | 
|  | * | 
|  | * @param oldname old file name | 
|  | * @param newname new file name | 
|  | * @throws IOException if it fails | 
|  | */ | 
|  | protected void rename(String oldname, String newname) throws IOException { | 
|  | throw new IOException( "not implemented" ); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Sets the current locale of the program. locale is a string specifying | 
|  | * a locale; category is an optional string describing which category to change: | 
|  | * "all", "collate", "ctype", "monetary", "numeric", or "time"; the default category | 
|  | * is "all". | 
|  | * | 
|  | * If locale is the empty string, the current locale is set to an implementation- | 
|  | * defined native locale. If locale is the string "C", the current locale is set | 
|  | * to the standard C locale. | 
|  | * | 
|  | * When called with null as the first argument, this function only returns the | 
|  | * name of the current locale for the given category. | 
|  | * | 
|  | * @param locale | 
|  | * @param category | 
|  | * @return the name of the new locale, or null if the request | 
|  | * cannot be honored. | 
|  | */ | 
|  | protected String setlocale(String locale, String category) { | 
|  | return "C"; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns the current time when called without arguments, | 
|  | * or a time representing the date and time specified by the given table. | 
|  | * This table must have fields year, month, and day, | 
|  | * and may have fields hour, min, sec, and isdst | 
|  | * (for a description of these fields, see the os.date function). | 
|  | * @param table | 
|  | * @return long value for the time | 
|  | */ | 
|  | protected double time(LuaTable table) { | 
|  | java.util.Date d; | 
|  | if (table == null) { | 
|  | d = new java.util.Date(); | 
|  | } else { | 
|  | Calendar c = Calendar.getInstance(); | 
|  | c.set(Calendar.YEAR, table.get("year").checkint()); | 
|  | c.set(Calendar.MONTH, table.get("month").checkint()-1); | 
|  | c.set(Calendar.DAY_OF_MONTH, table.get("day").checkint()); | 
|  | c.set(Calendar.HOUR, table.get("hour").optint(12)); | 
|  | c.set(Calendar.MINUTE, table.get("min").optint(0)); | 
|  | c.set(Calendar.SECOND, table.get("sec").optint(0)); | 
|  | c.set(Calendar.MILLISECOND, 0); | 
|  | d = c.getTime(); | 
|  | } | 
|  | return d.getTime() / 1000.; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns a string with a file name that can be used for a temporary file. | 
|  | * The file must be explicitly opened before its use and explicitly removed | 
|  | * when no longer needed. | 
|  | * | 
|  | * On some systems (POSIX), this function also creates a file with that name, | 
|  | * to avoid security risks. (Someone else might create the file with wrong | 
|  | * permissions in the time between getting the name and creating the file.) | 
|  | * You still have to open the file to use it and to remove it (even if you | 
|  | * do not use it). | 
|  | * | 
|  | * @return String filename to use | 
|  | */ | 
|  | protected String tmpname() { | 
|  | synchronized ( OsLib.class ) { | 
|  | return TMP_PREFIX+(tmpnames++)+TMP_SUFFIX; | 
|  | } | 
|  | } | 
|  | } |