/******************************************************************************* * 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; //TODO 与3.0不一样 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_OF_DAY))); 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_OF_DAY, table.get("hour").optint(0)); 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; } } }