001: /**
002: * Sequoia: Database clustering technology.
003: * Copyright (C) 2002-2004 French National Institute For Research In Computer
004: * Science And Control (INRIA).
005: * Contact: sequoia@continuent.org
006: *
007: * Licensed under the Apache License, Version 2.0 (the "License");
008: * you may not use this file except in compliance with the License.
009: * You may obtain a copy of the License at
010: *
011: * http://www.apache.org/licenses/LICENSE-2.0
012: *
013: * Unless required by applicable law or agreed to in writing, software
014: * distributed under the License is distributed on an "AS IS" BASIS,
015: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016: * See the License for the specific language governing permissions and
017: * limitations under the License.
018: *
019: * Initial developer(s): Nicolas Modrzyk.
020: * Contributor(s): Mathieu Peltier.
021: */package org.continuent.sequoia.console.text.module;
022:
023: import java.io.IOException;
024: import java.lang.reflect.Constructor;
025: import java.lang.reflect.UndeclaredThrowableException;
026: import java.util.Hashtable;
027: import java.util.Iterator;
028: import java.util.LinkedList;
029: import java.util.List;
030: import java.util.Map;
031: import java.util.Properties;
032: import java.util.Set;
033: import java.util.StringTokenizer;
034: import java.util.TreeSet;
035:
036: import jline.ArgumentCompletor;
037: import jline.Completor;
038: import jline.FileNameCompletor;
039: import jline.SimpleCompletor;
040:
041: import org.continuent.sequoia.common.i18n.ConsoleTranslate;
042: import org.continuent.sequoia.console.text.Console;
043: import org.continuent.sequoia.console.text.commands.ConsoleCommand;
044: import org.continuent.sequoia.console.text.commands.Help;
045: import org.continuent.sequoia.console.text.commands.History;
046: import org.continuent.sequoia.console.text.commands.Quit;
047:
048: /**
049: * This class defines a AbstractConsoleModule
050: *
051: * @author <a href="mailto:Nicolas.Modrzyk@inria.fr">Nicolas Modrzyk </a>
052: * @author <a href="mailto:Mathieu.Peltier@inrialpes.fr">Mathieu Peltier </a>
053: * @version 1.0
054: */
055: public abstract class AbstractConsoleModule {
056: Console console;
057:
058: TreeSet commands;
059:
060: boolean quit = false;
061:
062: protected Completor consoleCompletor;
063:
064: /**
065: * location of the default command lists for the console modules
066: */
067: public static final String DEFAULT_COMMAND_PROPERTIES_FILE = "org/continuent/sequoia/console/text/console.ini";
068:
069: /**
070: * Creates a new <code>AbstractConsoleModule.java</code> object
071: *
072: * @param console to refer from
073: */
074: public AbstractConsoleModule(Console console) {
075: this .console = console;
076: this .commands = new TreeSet();
077: commands.add(new Help(this ));
078: commands.add(new History(this ));
079: commands.add(new Quit(this ));
080: if (console.isInteractive())
081: console.printInfo(ConsoleTranslate.get("module.loading",
082: getDescriptionString()));
083: this .loadCommands();
084: this .loadCompletor();
085: }
086:
087: /**
088: * Loads the commands for this module
089: */
090: protected final void loadCommands() {
091: commands.clear();
092: String commandClassesAsString = loadCommandsFromProperties(getModuleID());
093: String[] commandClasses = parseCommands(commandClassesAsString);
094: addCommands(commandClasses, commands);
095: }
096:
097: /**
098: * Parses a String representing a list of command classes (separated by
099: * commas) and returns an String[] representing the command classes
100: *
101: * @param commandClassesAsString a String representing a list of command
102: * classes (separated by commas)
103: * @return a (eventually empty) String[] where each String represents a
104: * command class
105: */
106: protected String[] parseCommands(String commandClassesAsString) {
107: if (commandClassesAsString == null) {
108: return new String[0];
109: }
110: String[] cmds = commandClassesAsString.split("\\s*,\\s*"); //$NON-NLS-1$
111: return cmds;
112: }
113:
114: /**
115: * Add commands to this module. Commands instances are created by reflection
116: * based on the command class names passed in parameter
117: *
118: * @param commandClasses a String[] containing the class names of the command
119: * to instantiate
120: * @param commands Set where the commands are added
121: */
122: protected void addCommands(String[] commandClasses, Set commands) {
123: for (int i = 0; i < commandClasses.length; i++) {
124: String commandClass = commandClasses[i].trim();
125: Class clazz;
126: try {
127: clazz = Class.forName(commandClass);
128: Constructor constructor;
129: try {
130: constructor = clazz
131: .getConstructor(new Class[] { this
132: .getClass() });
133: } catch (NoSuchMethodException e) {
134: constructor = clazz
135: .getConstructor(new Class[] { AbstractConsoleModule.class });
136: }
137: ConsoleCommand command = (ConsoleCommand) constructor
138: .newInstance(new Object[] { this });
139: commands.add(command);
140: } catch (Exception e) {
141: // fail silently: the command won't be added to the commands list
142: }
143: }
144: }
145:
146: /**
147: * Extracts the commands from the command properties file as a single
148: * <code>String</code> containing a list of comma-separated command classes.
149: *
150: * @param moduleID module ID used as the key in the properties file
151: * @return a single <code>String</code> containing a list of comma-separated
152: * command classes corresponding to the module identified by
153: */
154: protected String loadCommandsFromProperties(String moduleID) {
155: Properties props = new Properties();
156: try {
157: String propertiesFile = System
158: .getProperty("console.commands",
159: DEFAULT_COMMAND_PROPERTIES_FILE);
160: props.load(ClassLoader
161: .getSystemResourceAsStream(propertiesFile));
162: } catch (IOException e) {
163: // fail silently: no commands will be loaded
164: }
165: String commandClassesAsString = props.getProperty(moduleID, "");
166: return commandClassesAsString;
167: }
168:
169: /**
170: * Returns an unique ID which identifies the module. This value is used to
171: * identify the commands to load for this given module.
172: *
173: * @return an unique <code>String</code> which identifies the module
174: */
175: protected abstract String getModuleID();
176:
177: /**
178: * Loads the commands for this module
179: */
180: protected void loadCompletor() {
181: List completors = new LinkedList();
182: int size = commands.size();
183: if (size > 0) {
184: TreeSet set = new TreeSet();
185: Iterator it = commands.iterator();
186: while (it.hasNext()) {
187: set.add(((ConsoleCommand) it.next()).getCommandName());
188: }
189: completors.add(new SimpleCompletor((String[]) set
190: .toArray(new String[size])));
191: }
192: completors.add(new FileNameCompletor());
193:
194: Completor[] completorsArray = (Completor[]) completors
195: .toArray(new Completor[completors.size()]);
196: consoleCompletor = new ArgumentCompletor(completorsArray,
197: new CommandDelimiter());
198: }
199:
200: /**
201: * Reload the completor associated with this module. This method must be
202: * called if the list of commands has been dynamically modified.
203: */
204: protected synchronized void reloadCompletor() {
205: console.getConsoleReader().removeCompletor(consoleCompletor);
206: loadCompletor();
207: console.getConsoleReader().addCompletor(consoleCompletor);
208: }
209:
210: /**
211: * Text description of this module
212: *
213: * @return <code>String</code> description to display
214: */
215: public abstract String getDescriptionString();
216:
217: /**
218: * Display help for this module
219: */
220: public void help() {
221: console.println(ConsoleTranslate.get(
222: "module.commands.available", getDescriptionString()));
223: ConsoleCommand command;
224: Iterator it = commands.iterator();
225: while (it.hasNext()) {
226: command = (ConsoleCommand) it.next();
227: console.println(command.getCommandName() + " "
228: + command.getCommandParameters());
229: console.println(" " + command.getCommandDescription());
230: }
231: }
232:
233: /**
234: * Quit this module
235: */
236: public void quit() {
237: quit = true;
238: console.getConsoleReader().removeCompletor(getCompletor());
239: console.getConsoleReader().addCompletor(
240: console.getControllerModule().getCompletor());
241: }
242:
243: /**
244: * Get all the commands for this module
245: *
246: * @return <code>TreeSet</code> of commands (commandName|commandObject)
247: */
248: public TreeSet getCommands() {
249: return commands;
250: }
251:
252: /**
253: * Get the prompt string for this module
254: *
255: * @return <code>String</code> to place before prompt
256: */
257: public abstract String getPromptString();
258:
259: /**
260: * Handle a serie of commands
261: */
262: public void handlePrompt() {
263:
264: if (quit) {
265: if (console.isInteractive())
266: console.printError(ConsoleTranslate.get(
267: "module.quitting", getDescriptionString()));
268: return;
269: }
270:
271: // login();
272: quit = false;
273: while (!quit) {
274:
275: Hashtable hashCommands = getHashCommands();
276: try {
277: String commandLine = console
278: .readLine(getPromptString());
279: if (commandLine == null) {
280: quit();
281: break;
282: }
283: if (commandLine.equals(""))
284: continue;
285:
286: handleCommandLine(commandLine, hashCommands);
287:
288: } catch (Exception e) {
289: // try to get the cause exception instead of the useless jmx
290: // encapsulating one
291: if (e instanceof UndeclaredThrowableException) {
292: try {
293: e = (Exception) ((UndeclaredThrowableException) e)
294: .getUndeclaredThrowable();
295: } catch (Throwable ignored) {
296: }
297: }
298: console.printError(ConsoleTranslate.get(
299: "module.command.got.error", e.getMessage()), e);
300: if (!console.isInteractive() && console.isExitOnError()) {
301: System.exit(1);
302: }
303: }
304: }
305: }
306:
307: /**
308: * Get the list of commands as strings for this module
309: *
310: * @return <code>Hashtable</code> list of <code>String</code> objects
311: */
312: public final Hashtable getHashCommands() {
313: Hashtable hashCommands = new Hashtable();
314: ConsoleCommand consoleCommand;
315: Iterator it = commands.iterator();
316: while (it.hasNext()) {
317: consoleCommand = (ConsoleCommand) it.next();
318: hashCommands.put(consoleCommand.getCommandName(),
319: consoleCommand);
320: }
321: return hashCommands;
322: }
323:
324: /**
325: * Handle module command
326: *
327: * @param commandLine the command line to handle
328: * @param hashCommands the list of commands available for this module
329: * @throws Exception if fails *
330: */
331: public final void handleCommandLine(String commandLine,
332: Hashtable hashCommands) throws Exception {
333: StringTokenizer st = new StringTokenizer(commandLine);
334: if (st.hasMoreTokens()) {
335: ConsoleCommand command = findConsoleCommand(commandLine,
336: hashCommands);
337: if (command != null) {
338: command.execute(commandLine.substring(command
339: .getCommandName().length()));
340: return;
341: }
342: }
343: throw new Exception(ConsoleTranslate.get(
344: "module.command.not.supported", commandLine));
345: }
346:
347: /**
348: * Find the <code>ConsoleCommand</code> based on the name of the command
349: * from the <code>commandLine</code> in the <code>hashCommands</code>. If
350: * more than one <code>ConsoleCommand</code>'s command name start the same
351: * way, return the <code>ConsoleCommand</code> with the longest one.
352: *
353: * @param commandLine the command line to handle
354: * @param hashCommands the list of commands available for this module
355: * @return the <code>ConsoleCommand</code> corresponding to the name of the
356: * command from the <code>commandLine</code> or <code>null</code>
357: * if there is no matching
358: */
359: public ConsoleCommand findConsoleCommand(String commandLine,
360: Hashtable hashCommands) {
361: ConsoleCommand foundCommand = null;
362: for (Iterator iter = hashCommands.entrySet().iterator(); iter
363: .hasNext();) {
364: Map.Entry commandEntry = (Map.Entry) iter.next();
365: String commandName = (String) commandEntry.getKey();
366: if (commandLine.startsWith(commandName)) {
367: ConsoleCommand command = (ConsoleCommand) commandEntry
368: .getValue();
369: if (foundCommand == null) {
370: foundCommand = command;
371: }
372: if (command.getCommandName().length() > foundCommand
373: .getCommandName().length()) {
374: foundCommand = command;
375: }
376: }
377: }
378: return foundCommand;
379: }
380:
381: /**
382: * Handles login in this module
383: *
384: * @param params parameters to use to login in this module
385: * @throws Exception if fails
386: */
387: public abstract void login(String[] params) throws Exception;
388:
389: /**
390: * Get access to the console
391: *
392: * @return <code>Console</code> instance
393: */
394: public Console getConsole() {
395: return console;
396: }
397:
398: /**
399: * Returns the console completor to use for this module.
400: *
401: * @return <code>Completor</code> object.
402: */
403: public Completor getCompletor() {
404: return consoleCompletor;
405: }
406:
407: /**
408: * This class defines a CommandDelimiter used to delimit a command from user
409: * input
410: */
411: class CommandDelimiter extends
412: ArgumentCompletor.AbstractArgumentDelimiter {
413: /**
414: * @see jline.ArgumentCompletor.AbstractArgumentDelimiter#isDelimiterChar(java.lang.String,
415: * int)
416: */
417: public boolean isDelimiterChar(String buffer, int pos) {
418: String tentativeCmd = buffer.substring(0, pos);
419: return isACompleteCommand(tentativeCmd);
420: }
421:
422: /**
423: * Test if the String input by the user insofar is a complete command or
424: * not.
425: *
426: * @param input Text input by the user
427: * @return <code>true</code> if the text input by the user is a complete
428: * command name, <code>false</code> else
429: */
430: private boolean isACompleteCommand(String input) {
431: boolean foundCompleteCommand = false;
432: for (Iterator iter = commands.iterator(); iter.hasNext();) {
433: ConsoleCommand command = (ConsoleCommand) iter.next();
434: if (input.equals(command.getCommandName())) {
435: foundCompleteCommand = !otherCommandsStartWith(command
436: .getCommandName());
437: }
438: }
439: return foundCompleteCommand;
440: }
441:
442: private boolean otherCommandsStartWith(String commandName) {
443: for (Iterator iter = commands.iterator(); iter.hasNext();) {
444: ConsoleCommand command = (ConsoleCommand) iter.next();
445: if (command.getCommandName().startsWith(commandName)
446: && !command.getCommandName()
447: .equals(commandName)) {
448: return true;
449: }
450: }
451: return false;
452: }
453: }
454: }
|