View Javadoc

1   /**
2    * Copyright (2008) Schibsted Søk AS
3    * This file is part of Sesat Commons.
4    *
5    *   Sesat Commons is free software: you can redistribute it and/or modify
6    *   it under the terms of the GNU Lesser General Public License as published by
7    *   the Free Software Foundation, either version 3 of the License, or
8    *   (at your option) any later version.
9    *
10   *   Sesat Commons is distributed in the hope that it will be useful,
11   *   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   *   GNU Lesser General Public License for more details.
14   *
15   *   You should have received a copy of the GNU Lesser General Public License
16   *   along with Sesat Commons.  If not, see <http://www.gnu.org/licenses/>.
17   *
18   */
19  
20  package no.sesat;
21  
22  import java.io.BufferedReader;
23  import java.io.BufferedWriter;
24  import java.io.Console;
25  import java.io.FileReader;
26  import java.io.FileWriter;
27  import java.io.IOException;
28  import java.io.Reader;
29  import java.util.ArrayList;
30  import java.util.Collections;
31  import java.util.Enumeration;
32  import java.util.HashMap;
33  import java.util.Iterator;
34  import java.util.List;
35  import java.util.Map;
36  import java.util.Properties;
37  import java.util.Set;
38  import java.util.TreeSet;
39  import java.util.Vector;
40  
41  import org.apache.log4j.Level;
42  import org.apache.log4j.Logger;
43  import org.apache.log4j.spi.LoggerRepository;
44  
45  /**
46   * Simple interpreter that will start if a console is available. You can add
47   * functions to it by using addFunction(String, Function).
48   *
49   */
50  public class Interpreter {
51      public static final boolean ENABLED = Boolean.parseBoolean(System.getenv("SESAT_INTERPRETER"));
52      private static Map<String, Function> FUNCTIONS = ENABLED ? new HashMap<String, Function>() : null;
53      private static boolean RUN = ENABLED;
54      private static ArrayList<InterpreterRepl> REPL = new ArrayList<InterpreterRepl>();
55  
56      private static interface FunctionInterface {
57          String execute(Context ctx);
58      }
59  
60      private static abstract class InterpreterRepl extends Thread {
61          public InterpreterRepl(String name) {
62              super(name);
63              init();
64          }
65  
66          protected void init() {
67          };
68  
69          protected void output(String s) {
70          };
71      }
72  
73      private static void broadcastToRepl(String s) {
74          for (InterpreterRepl repl : REPL) {
75              repl.output(s);
76          }
77      }
78  
79      /**
80       * Add a function to this Interpreter.
81       *
82       * @param name
83       *            Name of function
84       * @param fun
85       *            The function
86       */
87      public synchronized static void addFunction(final String name, final Function fun) {
88          if (FUNCTIONS != null) {
89              if (FUNCTIONS.containsKey(name)) {
90                  broadcastToRepl("Redefined function: " + name + "\n");
91              } else {
92                  broadcastToRepl("Added function: " + name + "\n");
93              }
94              FUNCTIONS.put(name, fun);
95          }
96      }
97  
98      /**
99       * Remove function from interpreter.
100      *
101      * @param name
102      *            Named function
103      */
104     public synchronized static void removeFunction(final String name) {
105         if (FUNCTIONS != null) {
106             broadcastToRepl("Removed function: " + name + "\n");
107             FUNCTIONS.remove(name);
108         }
109     }
110 
111     /**
112      * Function that you can implement if you want to add a function to this
113      * Interpreter.
114      */
115     public abstract static class Function implements FunctionInterface {
116         /**
117          * One line that describes the function.
118          *
119          * @return Description
120          */
121         protected String describe() {
122             return "";
123         }
124 
125         /**
126          * If there is need for a longer description then what you can write in
127          * description then write it here.
128          *
129          * @return Help text
130          */
131         protected String help() {
132             return describe();
133         }
134     }
135 
136     /**
137      * Context for a function.
138      */
139     public static final class Context {
140         /**
141          * The original argument array.
142          */
143         final private String argString;
144         final private Vector<String> argVector = new Vector<String>();
145         final private HashMap<String, String> argHash = new HashMap<String, String>();
146 
147         private enum State {
148             normal, quote, keywordNormal, keywordQuote
149         };
150 
151         private Context(final String arguments) {
152             argString = arguments.trim();
153             if (!argString.isEmpty()) {
154                 State state = State.normal;
155 
156                 String arg = "";
157                 String value = "";
158                 for (char c : (argString + " ").toCharArray()) {
159                     switch (state) {
160                     case normal:
161                         if (c == ' ') {
162                             argVector.add(arg);
163                             arg = "";
164                         } else if (c == '\'') {
165                             state = State.quote;
166                         } else if (c == '=') {
167                             state = State.keywordNormal;
168                         } else {
169                             arg += c;
170                         }
171                         break;
172 
173                     case quote:
174                         if (c == '\'') {
175                             state = State.normal;
176                         } else {
177                             arg += c;
178                         }
179                         break;
180 
181                     case keywordNormal:
182                         if (c == ' ') {
183                             state = State.normal;
184                             argHash.put(arg.toUpperCase(), value);
185                             arg = "";
186                             value = "";
187                         } else if (c == '\'') {
188                             state = State.keywordQuote;
189                         } else {
190                             value += c;
191                         }
192                         break;
193 
194                     case keywordQuote:
195                         if (c == '\'') {
196                             state = State.keywordNormal;
197                         } else {
198                             value += c;
199                         }
200                         break;
201 
202                     }
203                 }
204             }
205         }
206 
207         /**
208          * @return Number of arguments.
209          */
210         public int length() {
211             return argVector.size();
212         }
213 
214         /**
215          *
216          * @param key
217          * @return The value of the argument if it exists, or null.
218          */
219         public String getKeywordArgument(final String key) {
220             return argHash.get(key.toUpperCase());
221         }
222 
223         /**
224          *
225          * @return available keys
226          */
227         public Set<String> getKeywords() {
228             return argHash.keySet();
229         }
230 
231         /**
232          *
233          * @return Copy of the parsed arguments
234          */
235         public String[] getArgumentArray() {
236             String[] res = new String[argVector.size()];
237             return argVector.toArray(res);
238         }
239 
240         /**
241          *
242          * @param index
243          * @return argument at index
244          */
245         public String getArgument(final int index) {
246             if (index < argVector.size()) {
247                 return argVector.get(index);
248             } else
249                 return null;
250         }
251 
252         /**
253          *
254          * @return the raw argument string
255          */
256         public String getArgumentString() {
257             return argString;
258         }
259 
260         public String toString() {
261             String res = "";
262             res += "Raw argument string:\n    " + argString + "\n";
263             res += "Argument array: " + length() + "\n";
264             for (String arg : getArgumentArray()) {
265                 res += "    " + arg + "\n";
266             }
267             res += "Keywords:\n";
268             for (String key : getKeywords()) {
269                 res += "    " + key + " --> " + getKeywordArgument(key) + "\n";
270             }
271             return res;
272         }
273     }
274 
275     static {
276         if (ENABLED){
277 
278             // Attach to console if running Java6 or greater
279             if(!System.getProperty("java.version").startsWith("1.5.")) { // <1.5 is not supported
280 
281                 final InterpreterRepl consoleReplThread = new InterpreterRepl("CONSOLE_REPL") {
282                     Console console = System.console();
283 
284                     @Override
285                     public void run() {
286                         while (console != null) {
287                             Reader r = console.reader();
288                             char c = ' ';
289                             try {
290                                 c = (char) r.read();
291                             } catch (IOException e) {
292                                 return;
293                             }
294 
295                             String line = "";
296                             while (RUN && c != '\n') {
297                                 line += c;
298                                 try {
299                                     c = (char) r.read();
300                                 } catch (IOException e) {
301                                     return;
302                                 }
303                             }
304                             if (line != null) { // check for null, this will happen
305                                                 // on shutdown
306                                 if (line.length() > 0) {
307                                     console.printf("%s\n", eval(line));
308                                 }
309                             }
310                         }
311                     }
312 
313                     @Override
314                     protected void output(String s) {
315                         if (console != null) {
316                             console.printf("%s\n", s);
317                         }
318                     }
319                 };
320                 consoleReplThread.setDaemon(true);
321                 consoleReplThread.start();
322                 REPL.add(consoleReplThread);
323             }
324 
325             addFunction("echo", new Function() {
326                 public String execute(final Context ctx) {
327                     return ctx.getArgumentString();
328                 }
329 
330                 public String describe() {
331                     return "Echo arguments.";
332                 }
333             });
334 
335             addFunction("test-arguments", new Function() {
336                 public String execute(final Context ctx) {
337                     return ctx.toString();
338                 }
339 
340                 public String describe() {
341                     return "Just for testing.";
342                 }
343             });
344 
345             addFunction("help", new Function() {
346                 public String execute(final Context ctx) {
347                     if (ctx.length() == 1) {
348                         String res = "Help for " + ctx.getArgument(0) + "\n";
349                         final Function fun = FUNCTIONS.get(ctx.getArgument(0).trim());
350                         if (fun != null) {
351                             res += "    " + fun.help();
352                         } else {
353                             res += "    Not found!\n";
354                         }
355                         return res;
356                     } else {
357                         String res = "Functions available: \n";
358 
359                         List<String> tmp = new Vector<String>(FUNCTIONS.keySet());
360                         Collections.sort(tmp);
361 
362                         for (String name : tmp) {
363                             res += "    " + name + "\n";
364                             res += "        " + FUNCTIONS.get(name).describe() + "\n";
365                         }
366                         return res;
367                     }
368                 }
369 
370                 public String describe() {
371                     return "Print this help message";
372                 }
373             });
374 
375             addFunction("gc", new Function() {
376                 public String execute(final Context ctx) {
377                     System.gc();
378                     return "GC requested";
379                 }
380 
381                 public String describe() {
382                     return "Request a gc run.";
383                 }
384             });
385 
386             addFunction("all-stacktraces", new Function() {
387                 public String execute(final Context ctx) {
388                     String res = "";
389                     Map<Thread, StackTraceElement[]> m = Thread.getAllStackTraces();
390                     for (Thread thread : m.keySet()) {
391                         res += "Thread:: " + thread.toString() + "\n";
392                         for (StackTraceElement ste : m.get(thread)) {
393                             res += "   " + ste.toString() + "\n";
394                         }
395                         res += "\n";
396                     }
397                     return res;
398                 }
399 
400                 public String describe() {
401                     return "Look at all stacktraces.";
402                 }
403             });
404 
405             addFunction("loggers", new Function() {
406                 public String execute(final Context ctx) {
407                     String res = "Active loggers:\n";
408                     final LoggerRepository repo = Logger.getRootLogger().getLoggerRepository();
409 
410                     final Enumeration<?> e = repo.getCurrentLoggers();
411                     while (e.hasMoreElements()) {
412                         final Logger logger = (Logger) e.nextElement();
413                         if (ctx.length() == 0 || (ctx.length() > 0 && logger.getName().matches(ctx.getArgument(0)))) {
414                             res += logger.getName() + " " + logger.getLevel();
415                             if (ctx.length() == 2) {
416                                 final Level level = Level.toLevel(ctx.getArgument(1));
417                                 if (level.toString().equalsIgnoreCase(ctx.getArgument(1))) {
418                                     res += " (Setting level to " + level + ")";
419                                     logger.setLevel(level);
420                                 } else {
421                                     res += " (unknown debug level: " + ctx.getArgument(1) + ")";
422                                 }
423                             }
424                             res += "\n";
425                         }
426                     }
427                     return res;
428                 }
429 
430                 public String describe() {
431                     return "Print active loggers, and set level if specified. 'loggers [regexp] [level]'";
432                 }
433             });
434 
435             addFunction("properties", new Function() {
436                 public String execute(final Context ctx) {
437                     String res = "Properties: \n";
438                     Properties p = System.getProperties();
439                     TreeSet<Object> propKeys = new TreeSet<Object>(p.keySet());
440                     for (Iterator<Object> it = propKeys.iterator(); it.hasNext();) {
441                         String key = it.next().toString();
442                         res += key + "=" + p.get(key) + "\n";
443                     }
444                     for (String key : ctx.getKeywords()) {
445                         res += "Setting : " + key + " --> " + ctx.getKeywordArgument(key) + "\n";
446                         System.setProperty(key, ctx.getKeywordArgument(key));
447                     }
448                     return res;
449                 }
450 
451                 public String describe() {
452                     return "List System.getProperties()";
453                 }
454             });
455 
456             addFunction("save", new Function() {
457                 public String execute(final Context ctx) {
458                     if (ctx.length() == 2) {
459                         try {
460                             String file = ctx.getArgument(1);
461                             FileWriter s = new FileWriter(file);
462                             BufferedWriter o = new BufferedWriter(s);
463                             o.write(eval(ctx.getArgument(0)));
464                             o.close();
465                             return "Wrote output to file: " + file;
466                         } catch (Exception e) {
467                             return "Error: " + e.getMessage();
468                         }
469                     } else {
470                         return "Argument count must be 2";
471                     }
472                 }
473 
474                 public String describe() {
475                     return "Save output of function to file. 'save function filename'";
476                 }
477             });
478 
479             addFunction("read", new Function() {
480                 public String execute(final Context ctx) {
481                     if (ctx.length() == 1) {
482                         try {
483                             String file = ctx.getArgument(0);
484                             FileReader s = new FileReader(file);
485                             BufferedReader i = new BufferedReader(s);
486 
487                             String inline = "";
488                             while ((inline = i.readLine()) != null) {
489                                 eval(inline);
490                             }
491                             i.close();
492                             return "Read file: " + file;
493                         } catch (Exception e) {
494                             return "Error: " + e.getMessage();
495                         }
496                     } else {
497                         return "Argument count must be 1";
498                     }
499                 }
500 
501                 public String describe() {
502                     return "Read the file, and evaluate each line. 'read file'";
503                 }
504             });
505 
506             addFunction("macro", new Function() {
507                 public String execute(final Context ctx) {
508                     addFunction(ctx.getArgument(0), new Function() {
509                         public String execute(final Context c) {
510                             return eval(ctx.getArgumentString().substring(ctx.getArgument(0).length()).trim());
511                         }
512                     });
513                     return ctx.getArgumentString();
514                 }
515 
516                 public String describe() {
517                     return "Make a macro";
518                 }
519             });
520 
521             addFunction("quit", new Function() {
522                 public String execute(final Context ctx) {
523                     RUN = false;
524                     return "Bye!";
525                 }
526 
527                 public String describe() {
528                     return "Stop this interpreter.";
529                 }
530             });
531         }
532     }
533 
534     /**
535      * Evaluate the expression
536      *
537      * @param expr
538      *            to be evaluated.
539      * @return result
540      */
541     public synchronized static String eval(String expr) {
542         final int split = expr.trim().indexOf(" ");
543         final String function;
544         final String arguments;
545         if (split > 0) {
546             function = expr.substring(0, split);
547             arguments = expr.substring(split).trim();
548         } else {
549             function = expr;
550             arguments = "";
551         }
552         if (FUNCTIONS.containsKey(function)) {
553             try {
554                 return FUNCTIONS.get(function).execute(new Context(arguments));
555             } catch (Throwable e) {
556                 String error = "Error: " + e + "\n";
557                 for (StackTraceElement ste : e.getStackTrace()) {
558                     error += "    " + ste.toString() + "\n";
559                 }
560                 return error + "\n";
561             }
562         } else {
563             return "Unknown function: " + function;
564         }
565     }
566 }