Reorganize compiler for more flexibility.
[jagi.git] / src / jagi / fs / JavaHandler.java
1 package jagi.fs;
2
3 import jagi.*;
4 import java.util.*;
5 import java.util.function.*;
6 import java.util.logging.*;
7 import java.lang.reflect.*;
8 import java.nio.file.*;
9
10 public class JavaHandler implements Function<Map<Object, Object>, Map<Object, Object>> {
11     private static final Logger log = Logger.getLogger("jagi-fs");
12     private final Map<Compiler.Module, Function<Map<Object, Object>, Map<Object, Object>>> handlers = new WeakHashMap<>();
13
14     public static class HandlerException extends RuntimeException {
15         public final Path file;
16
17         public HandlerException(Path file, String msg, Throwable cause) {
18             super(msg, cause);
19             this.file = file;
20         }
21         public HandlerException(Path file, String msg) {
22             this(file, msg, null);
23         }
24
25         public String getMessage() {
26             return(file + ": " + super.getMessage());
27         }
28     }
29
30     @SuppressWarnings("unchecked")
31     private static Function<Map<Object, Object>, Map<Object, Object>> makehandler(Compiler.Module mod) {
32         Class<?> main;
33         try {
34             main = mod.code.loadClass("Main");
35         } catch(ClassNotFoundException e) {
36             throw(new HandlerException(mod.file, "no Main class"));
37         }
38         try {
39             Method wmain = main.getDeclaredMethod("wmain", String[].class);
40             int attr = wmain.getModifiers();
41             if(((attr & Modifier.STATIC) == 0) || ((attr & Modifier.PUBLIC) == 0))
42                 throw(new NoSuchMethodException());
43             Object handler = wmain.invoke(null, new Object[] {new String[] {}});
44             if(!(handler instanceof Function))
45                 throw(new HandlerException(mod.file, "wmain in " + main.getName() + " returned " + ((handler == null) ? "null" : ("a " + handler.getClass()))));
46             return((Function<Map<Object, Object>, Map<Object, Object>>)handler);
47         } catch(IllegalAccessException e) {
48             throw(new HandlerException(mod.file, "could not call wmain", e));
49         } catch(InvocationTargetException e) {
50             throw(new HandlerException(mod.file, "wmain failed", e.getCause()));
51         } catch(NoSuchMethodException e) {
52         }
53         if(Function.class.isAssignableFrom(main)) {
54             try {
55                 Constructor<? extends Function> cons = main.asSubclass(Function.class).getConstructor();
56                 Function handler = cons.newInstance();
57                 return((Function<Map<Object, Object>, Map<Object, Object>>)handler);
58             } catch(NoSuchMethodException e) {
59             } catch(InvocationTargetException e) {
60                 throw(new HandlerException(mod.file, "constructor failed", e.getCause()));
61             } catch(ReflectiveOperationException e) {
62                 throw(new HandlerException(mod.file, "could not construct Main", e));
63             }
64         }
65         throw(new HandlerException(mod.file, "no wmain and not directly applicable"));
66     }
67
68     private Function<Map<Object, Object>, Map<Object, Object>> gethandler(Compiler.File file) {
69         Compiler.Module mod = file.mod();
70         synchronized(handlers) {
71             Function<Map<Object, Object>, Map<Object, Object>> ret = handlers.get(mod);
72             if(ret == null)
73                 handlers.put(mod, ret = makehandler(mod));
74             return(ret);
75         }
76     }
77
78     public Map<Object, Object> apply(Map<Object, Object> req) {
79         Compiler.File file = Compiler.get().file(Paths.get((String)req.get("SCRIPT_FILENAME")));
80         try {
81             file.update();
82         } catch(Compiler.CompilationException e) {
83             log.warning(String.format("Could not compile %s:\n%s", file.name, e.messages()));
84             return(Utils.simpleerror(500, "Internal Error", "Could not load JAGI handler"));
85         } catch(Exception e) {
86             log.log(Level.WARNING, String.format("Error occurred when loading %s", file.name), e);
87             return(Utils.simpleerror(500, "Internal Error", "Could not load JAGI handler"));
88         }
89         Function<Map<Object, Object>, Map<Object, Object>> handler;
90         try {
91             handler = gethandler(file);
92         } catch(HandlerException e) {
93             Throwable cause = e.getCause();
94             if(cause != null)
95                 log.log(Level.WARNING, cause, e::getMessage);
96             else
97                 log.log(Level.WARNING, e::getMessage);
98             return(Utils.simpleerror(500, "Internal Error", "Invalid JAGI handler"));
99         }
100         return(handler.apply(req));
101     }
102 }