001: /*
002: * This is free software, licensed under the Gnu Public License (GPL)
003: * get a copy from <http://www.gnu.org/licenses/gpl.html>
004: * $Id: AliasCommand.java,v 1.17 2005/11/27 16:20:27 hzeller Exp $
005: * author: Henner Zeller <H.Zeller@acm.org>
006: */
007: package henplus.commands;
008:
009: import henplus.AbstractCommand;
010: import henplus.Command;
011: import henplus.CommandDispatcher;
012: import henplus.HenPlus;
013: import henplus.SQLSession;
014: import henplus.io.ConfigurationContainer;
015: import henplus.view.Column;
016: import henplus.view.ColumnMetaData;
017: import henplus.view.TableRenderer;
018: import henplus.view.util.SortedMatchIterator;
019:
020: import java.util.HashSet;
021: import java.util.Iterator;
022: import java.util.Map;
023: import java.util.Set;
024: import java.util.SortedMap;
025: import java.util.StringTokenizer;
026: import java.util.TreeMap;
027:
028: /**
029: * A Command that handles Aliases.
030: */
031: public final class AliasCommand extends AbstractCommand {
032: private final static String ALIAS_FILENAME = "aliases";
033: private final static ColumnMetaData[] DRV_META;
034: static {
035: DRV_META = new ColumnMetaData[2];
036: DRV_META[0] = new ColumnMetaData("alias");
037: DRV_META[1] = new ColumnMetaData("execute command");
038: }
039:
040: private final ConfigurationContainer _config;
041: private final SortedMap/*<ClassName-String,Command-Class>*/_aliases;
042: private final CommandDispatcher _dispatcher;
043:
044: /**
045: * to determine, if we got a recursion: one alias calls another
046: * alias which in turn calls the first one ..
047: */
048: private final Set _currentExecutedAliases;
049:
050: /**
051: * returns the command-strings this command can handle.
052: */
053: public String[] getCommandList() {
054: return new String[] { "list-aliases", "alias", "unalias" };
055: }
056:
057: public AliasCommand(HenPlus henplus) {
058: _dispatcher = henplus.getDispatcher();
059: _aliases = new TreeMap();
060: _currentExecutedAliases = new HashSet();
061: _config = henplus.createConfigurationContainer(ALIAS_FILENAME);
062: }
063:
064: /**
065: * initial load of aliases.
066: */
067: public void load() {
068: Map props = _config.readProperties();
069: Iterator it = props.entrySet().iterator();
070: while (it.hasNext()) {
071: Map.Entry entry = (Map.Entry) it.next();
072: putAlias((String) entry.getKey(), (String) entry.getValue());
073: }
074: }
075:
076: public boolean requiresValidSession(String cmd) {
077: return false;
078: }
079:
080: private void putAlias(String alias, String value) {
081: _aliases.put(alias, value);
082: _dispatcher.registerAdditionalCommand(alias, this );
083: }
084:
085: private void removeAlias(String alias) {
086: _aliases.remove(alias);
087: _dispatcher.unregisterAdditionalCommand(alias);
088: }
089:
090: /**
091: * execute the command given.
092: */
093: public int execute(SQLSession currentSession, String cmd,
094: String param) {
095: StringTokenizer st = new StringTokenizer(param);
096: int argc = st.countTokens();
097:
098: if ("list-aliases".equals(cmd)) {
099: if (argc != 0)
100: return SYNTAX_ERROR;
101: showAliases();
102: return SUCCESS;
103: }
104:
105: else if ("alias".equals(cmd)) {
106: if (argc < 2)
107: return SYNTAX_ERROR;
108: String alias = (String) st.nextElement();
109: // no quoted aliases..
110: if (alias.startsWith("\"") || alias.startsWith("'")) {
111: return SYNTAX_ERROR;
112: }
113: // unless we override an alias, moan, if this command already
114: // exists.
115: if (!_aliases.containsKey(alias)
116: && _dispatcher.containsCommand(alias)) {
117: HenPlus.msg().println("cannot alias built-in command!");
118: return EXEC_FAILED;
119: }
120: param = param.trim();
121: for (int i = 0; i < param.length(); ++i) {
122: if (Character.isWhitespace(param.charAt(i))) {
123: param = param.substring(i).trim();
124: break;
125: }
126: }
127: String value = stripQuotes(param); // rest of values.
128: putAlias(alias, value);
129: }
130:
131: else if ("unalias".equals(cmd)) {
132: if (argc >= 1) {
133: while (st.hasMoreElements()) {
134: String alias = (String) st.nextElement();
135: if (!_aliases.containsKey(alias)) {
136: HenPlus.msg().println(
137: "unknown alias '" + alias + "'");
138: } else {
139: removeAlias(alias);
140: }
141: }
142: return SUCCESS;
143: }
144: return SYNTAX_ERROR;
145: }
146:
147: else {
148: String toExecute = (String) _aliases.get(cmd);
149: //HenPlus.msg().println("key: '" + cmd + "' - exec: " + toExecute);
150: if (toExecute == null) {
151: return EXEC_FAILED;
152: }
153: // not session-proof:
154: if (_currentExecutedAliases.contains(cmd)) {
155: HenPlus
156: .msg()
157: .println(
158: "Recursive call to aliases ["
159: + cmd
160: + "]. Stopping this senseless venture.");
161: _currentExecutedAliases.clear();
162: return EXEC_FAILED;
163: }
164: HenPlus.msg()
165: .println("execute alias: " + toExecute + param);
166: _currentExecutedAliases.add(cmd);
167: _dispatcher.execute(currentSession, toExecute + param);
168: _currentExecutedAliases.clear();
169: }
170: return SUCCESS;
171: }
172:
173: private void showAliases() {
174: DRV_META[0].resetWidth();
175: DRV_META[1].resetWidth();
176: TableRenderer table = new TableRenderer(DRV_META, HenPlus.out());
177: Iterator it = _aliases.entrySet().iterator();
178: while (it.hasNext()) {
179: Map.Entry entry = (Map.Entry) it.next();
180: Column[] row = new Column[2];
181: row[0] = new Column((String) entry.getKey());
182: row[1] = new Column((String) entry.getValue());
183: table.addRow(row);
184: }
185: table.closeTable();
186: }
187:
188: private String stripQuotes(String value) {
189: if (value.startsWith("\"") && value.endsWith("\"")) {
190: value = value.substring(1, value.length() - 1);
191: } else if (value.startsWith("\'") && value.endsWith("\'")) {
192: value = value.substring(1, value.length() - 1);
193: }
194: return value;
195: }
196:
197: public Iterator complete(CommandDispatcher disp,
198: String partialCommand, final String lastWord) {
199: StringTokenizer st = new StringTokenizer(partialCommand);
200: String cmd = (String) st.nextElement();
201: int argc = st.countTokens();
202:
203: // list-aliases gets no names.
204: if ("list-aliases".equals(cmd)) {
205: return null;
206: }
207:
208: /*
209: * some completion within the alias/unalias commands.
210: */
211: if ("alias".equals(cmd) || "unalias".equals(cmd)) {
212: final HashSet alreadyGiven = new HashSet();
213:
214: if ("alias".equals(cmd)) {
215: // do not complete beyond first word.
216: if (argc > ("".equals(lastWord) ? 0 : 1)) {
217: return null;
218: }
219: } else {
220: /*
221: * remember all aliases, that have already been given on
222: * the commandline and exclude from completion..
223: * cool, isn't it ?
224: */
225: while (st.hasMoreElements()) {
226: alreadyGiven.add(st.nextElement());
227: }
228: }
229:
230: // ok, now return the list.
231: return new SortedMatchIterator(lastWord, _aliases) {
232: protected boolean exclude(String current) {
233: return alreadyGiven.contains(current);
234: }
235: };
236: }
237:
238: /* ok, someone tries to complete something that is a command.
239: * try to find the actual command and ask that command to do
240: * the completion.
241: */
242: String toExecute = (String) _aliases.get(cmd);
243: if (toExecute != null) {
244: Command c = disp.getCommandFrom(toExecute);
245: if (c != null) {
246: int i = 0;
247: String param = partialCommand;
248: while (param.length() < i
249: && Character.isWhitespace(param.charAt(i))) {
250: ++i;
251: }
252: while (param.length() < i
253: && !Character.isWhitespace(param.charAt(i))) {
254: ++i;
255: }
256: return c.complete(disp, toExecute + param.substring(i),
257: lastWord);
258: }
259: }
260:
261: return null;
262: }
263:
264: public void shutdown() {
265: _config.storeProperties(_aliases, true, "Aliases...");
266: }
267:
268: /**
269: * return a descriptive string.
270: */
271: public String getShortDescription() {
272: return "handle Aliases";
273: }
274:
275: public String getSynopsis(String cmd) {
276: if ("list-aliases".equals(cmd))
277: return cmd;
278: else if ("alias".equals(cmd)) {
279: return cmd + " <alias-name> <command-to-execute>";
280: } else if ("unalias".equals(cmd)) {
281: return cmd + " <alias-name>";
282: } else {
283: /*
284: * special aliased name..
285: */
286: return cmd;
287: }
288: }
289:
290: public String getLongDescription(String cmd) {
291: String dsc = null;
292: if ("list-aliases".equals(cmd)) {
293: dsc = "\tList all aliases, that have been stored with the\n"
294: + "\t'alias' command";
295: } else if ("alias".equals(cmd)) {
296: dsc = "\tAdd an alias for a command. This means, that you can\n"
297: + "\tgive a short name for a command you often use. This\n"
298: + "\tmight be as simple as\n"
299: + "\t alias ls tables\n"
300: + "\tto execute the tables command with a short 'ls'.\n"
301: + "\n\tFor longer commands it is even more helpful:\n"
302: + "\t alias size select count(*) from\n"
303: + "\tThis command needs a table name as a parameter to\n"
304: + "\texpand to a complete command. So 'size students'\n"
305: + "\texpands to 'select count(*) from students' and yields\n"
306: + "\tthe expected result.\n"
307: + "\n\tTo make life easier, HenPlus tries to determine the\n"
308: + "\tcommand to be executed so that the tab-completion\n"
309: + "\tworks even here; in this latter case it would help\n"
310: + "\tcomplete table names.";
311: } else if ("unalias".equals(cmd)) {
312: dsc = "\tremove an alias name";
313: } else {
314: // not session-proof:
315: if (_currentExecutedAliases.contains(cmd)) {
316: dsc = "\t[ this command cyclicly references itself ]";
317: } else {
318: _currentExecutedAliases.add(cmd);
319: dsc = "\tThis is an alias for the command\n" + "\t "
320: + _aliases.get(cmd);
321:
322: String actualCmdStr = (String) _aliases.get(cmd);
323: if (actualCmdStr != null) {
324: StringTokenizer st = new StringTokenizer(
325: actualCmdStr);
326: actualCmdStr = st.nextToken();
327: Command c = _dispatcher
328: .getCommandFrom(actualCmdStr);
329: String longDesc = null;
330: if (c != null
331: && (longDesc = c
332: .getLongDescription(actualCmdStr)) != null) {
333: dsc += "\n\n\t..the following description could be determined for this";
334: dsc += "\n\t------- [" + actualCmdStr
335: + "] ---\n";
336: dsc += longDesc;
337: }
338: _currentExecutedAliases.clear();
339: }
340: }
341: }
342: return dsc;
343: }
344: }
345:
346: /*
347: * Local variables:
348: * c-basic-offset: 4
349: * compile-command: "ant -emacs -find build.xml"
350: * End:
351: */
|