OSDN Git Service

Some icons have changed
[stew/Stew4.git] / src / net / argius / stew / CommandProcessor.java
1 package net.argius.stew;
2
3 import java.io.*;
4 import java.sql.*;
5 import java.util.*;
6
7 import javax.script.*;
8
9 import net.argius.stew.io.*;
10 import net.argius.stew.ui.*;
11 import net.argius.stew.ui.window.*;
12
13 /**
14  * Command Processor.
15  */
16 final class CommandProcessor {
17
18     private static Logger log = Logger.getLogger(CommandProcessor.class);
19     private static ResourceManager res = ResourceManager.getInstance(WindowLauncher.class);
20     private static final String HYPHEN_E = "-e";
21
22     private final Environment env;
23     private final OutputProcessor op;
24
25     CommandProcessor(Environment env) {
26         this.env = env;
27         this.op = env.getOutputProcessor();
28     }
29
30     /**
31      * Invokes this command.
32      * @param parameterString
33      * @return whether this application continues or not
34      * @throws CommandException
35      */
36     boolean invoke(String parameterString) throws CommandException {
37         Parameter p = new Parameter(parameterString);
38         if (parameterString.replaceFirst("^\\s+", "").startsWith(HYPHEN_E)) {
39             final int offset = parameterString.indexOf(HYPHEN_E) + 2;
40             for (String s : parameterString.substring(offset).split(HYPHEN_E)) {
41                 op.output(" >> " + s);
42                 if (!invoke(s)) {
43                     outputMessage("w.exit-not-available-in-sequencial-command");
44                 }
45             }
46             return true;
47         }
48         final String commandName = p.at(0);
49         try {
50             return invoke(commandName, new Parameter(parameterString));
51         } catch (UsageException ex) {
52             outputMessage("e.usage", commandName, ex.getMessage());
53         } catch (DynamicLoadingException ex) {
54             log.error(ex);
55             outputMessage("e.not-found", commandName);
56         } catch (CommandException ex) {
57             log.error(ex);
58             Throwable cause = ex.getCause();
59             String message = (cause == null) ? ex.getMessage() : cause.getMessage();
60             outputMessage("e.command", message);
61         } catch (IOException ex) {
62             log.error(ex);
63             outputMessage("e.command", ex.getMessage());
64         } catch (SQLException ex) {
65             log.error(ex);
66             SQLException parent = ex;
67             while (true) {
68                 SQLException sqle = parent.getNextException();
69                 if (sqle == null || sqle == parent) {
70                     break;
71                 }
72                 log.error(sqle, "------ SQLException.getNextException ------");
73                 parent = sqle;
74             }
75             outputMessage("e.database", ex.getMessage());
76         } catch (UnsupportedOperationException ex) {
77             log.warn(ex);
78             outputMessage("e.unsupported", ex.getMessage());
79         } catch (RuntimeException ex) {
80             log.error(ex);
81             outputMessage("e.runtime", ex.getMessage());
82         } catch (Throwable th) {
83             log.fatal(th);
84             outputMessage("e.fatal", th.getMessage());
85         }
86         try {
87             Connection conn = env.getCurrentConnection();
88             if (conn != null) {
89                 boolean isClosed = conn.isClosed();
90                 if (isClosed) {
91                     log.info("connection is already closed");
92                     disconnect();
93                 }
94             }
95         } catch (SQLException ex) {
96             log.warn(ex);
97         }
98         return true;
99     }
100
101     private boolean invoke(String commandName, Parameter p) throws IOException, SQLException {
102         assert commandName != null;
103         // do nothing if blank
104         if (commandName.length() == 0) {
105             return true;
106         }
107         // exit
108         if (commandName.equalsIgnoreCase("exit")) {
109             disconnect();
110             outputMessage("i.exit");
111             return false;
112         }
113         // connect 
114         if (commandName.equalsIgnoreCase("connect") || commandName.equalsIgnoreCase("-c")) {
115             connect(p);
116             return true;
117         }
118         // from file
119         if (commandName.equals("-f")) {
120             final File file = Path.resolve(env.getCurrentDirectory(), p.at(1));
121             if (!file.isFile()) {
122                 throw new UsageException(res.get("usage.-f"));
123             }
124             log.debug("-f %s", file.getAbsolutePath());
125             invoke(String.format("%s%s", Command.readFileAsString(file), p.after(2)));
126             return true;
127         }
128         // script
129         if (commandName.equals("-s")) {
130             final File file = Path.resolve(env.getCurrentDirectory(), p.at(1));
131             if (!file.isFile()) {
132                 throw new UsageException(res.get("usage.-s"));
133             }
134             log.debug("-s %s", file.getAbsolutePath());
135             ScriptEngineManager factory = new ScriptEngineManager();
136             ScriptEngine engine = factory.getEngineByName("JavaScript");
137             engine.put("connection", env.getCurrentConnection());
138             engine.put("conn", env.getCurrentConnection());
139             engine.put("patameter", p);
140             engine.put("p", p);
141             engine.put("outputProcessor", op);
142             engine.put("op", op);
143             try {
144                 Reader r = new FileReader(file);
145                 try {
146                     engine.eval("function using(o, f) { f(o); o.close() }");
147                     engine.eval(r);
148                 } finally {
149                     r.close();
150                 }
151             } catch (Exception ex) {
152                 throw new CommandException(ex);
153             }
154             return true;
155         }
156         // alias
157         AliasMap aliasMap = env.getAliasMap();
158         if (commandName.equalsIgnoreCase("alias") || commandName.equalsIgnoreCase("unalias")) {
159             aliasMap.reload();
160             if (commandName.equalsIgnoreCase("alias")) {
161                 if (p.has(2)) {
162                     final String keyword = p.at(1);
163                     if (isUsableKeywordForAlias(keyword)) {
164                         outputMessage("w.unusable-keyword-for-alias", keyword);
165                         return true;
166                     }
167                     aliasMap.setValue(keyword, p.after(2));
168                     aliasMap.save();
169                 } else if (p.has(1)) {
170                     final String keyword = p.at(1);
171                     if (isUsableKeywordForAlias(keyword)) {
172                         outputMessage("w.unusable-keyword-for-alias", keyword);
173                         return true;
174                     }
175                     if (aliasMap.containsKey(keyword)) {
176                         outputMessage("i.dump-alias", keyword, aliasMap.getValue(keyword));
177                     }
178                 } else {
179                     if (aliasMap.isEmpty()) {
180                         outputMessage("i.noalias");
181                     } else {
182                         for (final String key : new TreeSet<String>(aliasMap.keys())) {
183                             outputMessage("i.dump-alias", key, aliasMap.getValue(key));
184                         }
185                     }
186                 }
187             } else if (commandName.equalsIgnoreCase("unalias")) {
188                 if (p.has(1)) {
189                     aliasMap.remove(p.at(1));
190                     aliasMap.save();
191                 } else {
192                     throw new UsageException(res.get("usage.unalias"));
193                 }
194             }
195             return true;
196         } else if (aliasMap.containsKey(commandName)) {
197             final String command = aliasMap.expand(commandName, p);
198             op.output(" >> " + command);
199             invoke(command);
200             return true;
201         }
202         // cd
203         if (commandName.equalsIgnoreCase("cd")) {
204             if (!p.has(1)) {
205                 throw new UsageException(res.get("usage.cd"));
206             }
207             File olddir = env.getCurrentDirectory();
208             final String path = p.at(1);
209             final File dir = new File(path);
210             final File newdir = ((dir.isAbsolute()) ? dir : new File(olddir, path)).getCanonicalFile();
211             if (!newdir.isDirectory()) {
212                 outputMessage("e.dir-not-exists", newdir);
213                 return true;
214             }
215             env.setCurrentDirectory(newdir);
216             outputMessage("i.directory-changed", olddir.getAbsolutePath(), newdir.getAbsolutePath());
217             return true;
218         }
219         // at
220         if (commandName.equals("@")) {
221             final String currentDirectory = env.getCurrentDirectory().getAbsolutePath();
222             final String systemDirectory = Bootstrap.getSystemDirectory().getAbsolutePath();
223             op.output(String.format("current dir : %s", currentDirectory));
224             op.output(String.format("system  dir : %s", systemDirectory));
225             return true;
226         }
227         // report -
228         if (commandName.equals("-")) {
229             return invoke("report -");
230         }
231         // runtime informations
232         if (commandName.equals("?")) {
233             if (p.has(1)) {
234                 for (final String k : p.asArray()) {
235                     if (k.equals("?")) {
236                         continue;
237                     }
238                     final String s = System.getProperties().containsKey(k)
239                             ? String.format("[%s]", System.getProperty(k))
240                             : "undefined";
241                     op.output(String.format("%s=%s", k, s));
242                 }
243             } else {
244                 op.output(String.format("JRE : %s %s",
245                                         System.getProperty("java.runtime.name"),
246                                         System.getProperty("java.runtime.version")));
247                 op.output(String.format("OS : %s (osver=%s)",
248                                         System.getProperty("os.name"),
249                                         System.getProperty("os.version")));
250                 op.output(String.format("Locale : %s", Locale.getDefault()));
251             }
252             return true;
253         }
254         // connection
255         Connection conn = env.getCurrentConnection();
256         if (conn == null) {
257             outputMessage("e.not-connect");
258         } else if (commandName.equalsIgnoreCase("disconnect") || commandName.equalsIgnoreCase("-d")) {
259             disconnect();
260             outputMessage("i.disconnected");
261         } else if (commandName.equalsIgnoreCase("commit")) {
262             conn.commit();
263             outputMessage("i.committed");
264         } else if (commandName.equalsIgnoreCase("rollback")) {
265             conn.rollback();
266             outputMessage("i.rollbacked");
267         } else {
268             executeDynamicCommand(commandName, conn, p);
269         }
270         return true;
271     }
272
273     private static boolean isUsableKeywordForAlias(String keyword) {
274         return keyword != null && keyword.matches("(?i)-.*|exit|alias|unalias");
275     }
276
277     private void connect(Parameter p) throws SQLException {
278         log.info("connect start");
279         disconnect();
280         final String id = p.at(1);
281         Connector connector;
282         if (!p.has(1)) {
283             connector = AnonymousConnector.getConnector(id, p.at(2), p.at(3));
284         } else if (id.indexOf('@') >= 0) {
285             connector = AnonymousConnector.getConnector(id);
286         } else {
287             connector = env.getConnectorMap().getConnector(id);
288         }
289         if (connector != null) {
290             env.establishConnection(connector);
291         } else {
292             outputMessage("e.no-connector", id);
293         }
294         log.info("connect end");
295     }
296
297     private void disconnect() {
298         log.debug("disconnect start");
299         try {
300             env.releaseConnection();
301         } catch (SQLException ex) {
302             outputMessage("w.connection-closed-abnormally");
303         }
304         log.debug("disconnect end");
305     }
306
307     private void executeDynamicCommand(String commandName, Connection conn, Parameter p) {
308         assert commandName != null && !commandName.contains(" ");
309         final String fqcn;
310         if (commandName.indexOf('.') > 0) {
311             fqcn = commandName;
312         } else {
313             fqcn = "net.argius.stew.command."
314                    + commandName.substring(0, 1).toUpperCase()
315                    + commandName.substring(1).toLowerCase();
316         }
317         Class<? extends Command> c;
318         try {
319             c = DynamicLoader.loadClass(fqcn);
320         } catch (DynamicLoadingException ex) {
321             c = Command.isSelect(p.asString()) ? Select.class : UpdateAndOthers.class;
322         }
323         Command command = DynamicLoader.newInstance(c);
324         try {
325             Connector connector = env.getCurrentConnector();
326             if (connector.isReadOnly() && !command.isReadOnly()) {
327                 outputMessage("e.readonly");
328                 return;
329             }
330             command.setEnvironment(env);
331             log.info("command: %s start", command);
332             log.debug(p);
333             command.initialize();
334             command.execute(conn, p);
335         } finally {
336             command.close();
337         }
338         log.info("command: %s end", command);
339     }
340
341     /**
342      * Outputs message.
343      * @param id message-id (resource)
344      * @param args
345      * @throws CommandException
346      */
347     void outputMessage(String id, Object... args) throws CommandException {
348         op.output(res.get(id, args));
349     }
350
351     /**
352      * SQL statement command.
353      */
354     abstract static class RawSQL extends Command {
355
356         @Override
357         public final void execute(Connection conn, Parameter parameter) throws CommandException {
358             final String rawString = parameter.asString();
359             try {
360                 Statement stmt = prepareStatement(conn, rawString);
361                 try {
362                     execute(stmt, rawString);
363                 } finally {
364                     stmt.close();
365                 }
366             } catch (SQLException ex) {
367                 throw new CommandException(ex);
368             }
369         }
370
371         protected abstract void execute(Statement stmt, String sql) throws SQLException;
372
373     }
374
375     /**
376      * Select statement command.
377      */
378     static final class Select extends RawSQL {
379
380         public Select() {
381             // empty
382         }
383
384         @Override
385         public boolean isReadOnly() {
386             return true;
387         }
388
389         @Override
390         public void execute(Statement stmt, String rawString) throws SQLException {
391             final long startTime = System.currentTimeMillis();
392             ResultSet rs = executeQuery(stmt, rawString);
393             try {
394                 outputMessage("i.response-time", (System.currentTimeMillis() - startTime) / 1000f);
395                 ResultSetReference ref = new ResultSetReference(rs, rawString);
396                 output(ref);
397                 outputMessage("i.selected", ref.getRecordCount());
398             } finally {
399                 rs.close();
400             }
401         }
402
403     }
404
405     /**
406      * Update statement (contains all SQL excepted a Select SQL) command.
407      */
408     static final class UpdateAndOthers extends RawSQL {
409
410         public UpdateAndOthers() {
411             // empty
412         }
413
414         @Override
415         public boolean isReadOnly() {
416             return false;
417         }
418
419         @Override
420         protected void execute(Statement stmt, String sql) throws SQLException {
421             final int updatedCount = executeUpdate(stmt, sql);
422             final String msgId;
423             if (sql.matches("(?i)\\s*UPDATE.*")) {
424                 msgId = "i.updated";
425             } else if (sql.matches("(?i)\\\\s*INSERT.*")) {
426                 msgId = "i.inserted";
427             } else if (sql.matches("(?i)\\\\s*DELETE.*")) {
428                 msgId = "i.deleted";
429             } else {
430                 msgId = "i.proceeded";
431             }
432             outputMessage(msgId, updatedCount);
433         }
434
435     }
436
437 }