View Javadoc

1   /***
2    * Copyright 2004 Steven Caswell
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package com.mungoknotwise.sqlcli;
17  
18  import java.io.BufferedInputStream;
19  import java.io.BufferedOutputStream;
20  import java.io.BufferedReader;
21  import java.io.File;
22  import java.io.FileInputStream;
23  import java.io.FileNotFoundException;
24  import java.io.FileOutputStream;
25  import java.io.FileReader;
26  import java.io.IOException;
27  import java.io.InputStream;
28  import java.io.InputStreamReader;
29  import java.io.PrintStream;
30  import java.io.PrintWriter;
31  import java.sql.Connection;
32  import java.sql.DriverManager;
33  import java.sql.SQLException;
34  import java.util.ArrayList;
35  import java.util.Date;
36  import java.util.HashSet;
37  import java.util.Iterator;
38  import java.util.List;
39  import java.util.Properties;
40  import java.util.Set;
41  import org.apache.commons.cli.BasicParser;
42  import org.apache.commons.cli.CommandLine;
43  import org.apache.commons.cli.CommandLineParser;
44  import org.apache.commons.cli.HelpFormatter;
45  import org.apache.commons.cli.MissingOptionException;
46  import org.apache.commons.cli.OptionBuilder;
47  import org.apache.commons.cli.Options;
48  import org.apache.commons.cli.UnrecognizedOptionException;
49  import org.apache.commons.lang.StringUtils;
50  import org.apache.log4j.Logger;
51  
52  /***
53   * Main program for the SqlCli command line interface.
54   *
55   * @author  Steven Caswell
56   * @version $Id: SqlCli.java,v 1.5 2004/10/19 19:46:06 mungoknotwise Exp $
57   */
58  public class SqlCli
59  {
60    
61    
62    //----------------------------------------------------------------------------
63    // Static variables
64    //----------------------------------------------------------------------------
65    
66    private static Logger logger = Logger.getLogger(SqlCli.class);
67    
68    //----------------------------------------------------------------------------
69    // Static methods
70    //----------------------------------------------------------------------------
71  
72    /***
73     * Runs the program with the specified command line arguments.
74     *
75     * @param args the command line arguments
76     * @throws Exception if an error occurs
77     */
78    public static void main(final String[] args) throws Exception
79    {
80      SqlCli sqlCli = new SqlCli();
81      boolean parsed = sqlCli.parseArguments(args);
82      if(parsed)
83      {
84        try
85        {
86          sqlCli.run();
87        }
88        catch(Throwable t)
89        {
90          t.printStackTrace();
91          BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
92          reader.readLine();
93        }
94      }
95    }
96    
97    //----------------------------------------------------------------------------
98    // Constants
99    //----------------------------------------------------------------------------
100   
101   private static final int LOGGER_FILE_NAME_INDEX = 3;
102   private static final int WRITE_TO_FILE_NAME_INDEX = 3;
103   private static final int WRITE_TO_FILE_COMMAND_MINIMUM_LENGTH = 3;
104   private static final int PRINT_USAGE_WIDTH = 255;
105   private static final String PROGRAM_NAME = "java " + SqlCli.class.getName();
106   
107   private static final int CD_DIRECTIVE_MINIMUM_LENGTH = 3;
108   private static final int CD_COMMAND_MAXIMUM_OFFSET = 3;
109   private static final int PWD_DIRECTIVE_MINIMUM_LENGTH = 4;
110   private static final int PWD_COMMAND_MAXIMUM_OFFSET = 4;
111   
112   //----------------------------------------------------------------------------
113   // Instance variables
114   //----------------------------------------------------------------------------
115 
116   private DescribeCommand describeCommand;
117   private String driverClassName;
118   private boolean inComment;
119   private List lines;
120   private boolean needToClear;
121   private String password;
122   private Properties properties;
123   private String propertiesFileName;
124   private SelectCommand selectCommand;
125   private ShowTablesCommand showTablesCommand;
126   private String url;
127   private UpdateCommand updateCommand;
128   private String username;
129   private Set printStreamed;
130   private File workingDirectory;
131   private PrintStream loggerPrintStream = null;
132   
133   //----------------------------------------------------------------------------
134   // Constructors
135   //----------------------------------------------------------------------------
136   
137   /***
138    * Constructs a new instance of
139    * <code>SqlCli</code>.
140    */
141   public SqlCli()
142   {
143     this.printStreamed = new HashSet();
144   }
145 
146   //----------------------------------------------------------------------------
147   // Interface implementations
148   //----------------------------------------------------------------------------
149   //----------------------------------------------------------------------------
150   // Implementation of interface Interface1
151   //----------------------------------------------------------------------------
152   
153   //----------------------------------------------------------------------------
154   // Extends overrides
155   //----------------------------------------------------------------------------
156   //----------------------------------------------------------------------------
157   // Override of class Class1
158   //----------------------------------------------------------------------------
159   
160   
161   //----------------------------------------------------------------------------
162   // Public methods exposed by this class
163   //----------------------------------------------------------------------------
164 
165   /***
166    * Parses the command line arguments.
167    *
168    * @param args the command line arguments
169    * @return <code>true</code> if processing should continue, <code>false</code>
170    * otherwise
171    * @throws Exception if an error occurs
172    */
173   public boolean parseArguments(final String[] args) throws Exception
174   {
175     Options helpOptions = new Options();
176     Options options = new Options();
177     
178     {
179       // Create the classname option
180       OptionBuilder builder = OptionBuilder.withArgName("driverClassName");
181       builder = builder.hasArg();
182       builder = builder.withDescription("classname of the driver class");
183       builder = builder.isRequired(true);
184       options.addOption(builder.create("d"));
185     }
186     
187     {
188       // Create the url option
189       OptionBuilder builder = OptionBuilder.withArgName("connectionUrl");
190       builder = builder.hasArg();
191       builder = builder.withDescription("url of the connection");
192       builder = builder.isRequired(true);
193       options.addOption(builder.create("c"));
194     }
195 
196     {
197       // Create the properties file name option
198       OptionBuilder builder = OptionBuilder.withArgName("propertiesFileName");
199       builder = builder.hasArg();
200       builder = builder.withDescription("name of file containing properties for the connection");
201       builder = builder.isRequired(false);
202       options.addOption(builder.create("f"));
203     }
204 
205     {
206       // Create the username option
207       OptionBuilder builder = OptionBuilder.withArgName("username");
208       builder = builder.hasArg();
209       builder = builder.withDescription("username for the connection; required if needed by the driver");
210       builder = builder.isRequired(false);
211       options.addOption(builder.create("u"));
212     }
213     
214     {
215       // Create the password option
216       OptionBuilder builder = OptionBuilder.withArgName("password");
217       builder = builder.hasArg();
218       builder = builder.withDescription("password for the connection; required if needed by the driver");
219       builder = builder.isRequired(false);
220       options.addOption(builder.create("p"));
221     }
222 
223     {
224       // Create the help option
225       OptionBuilder builder = OptionBuilder.withArgName("help");
226       builder = builder.hasArg(false);
227       builder = builder.withDescription("this help listing");
228       builder = builder.isRequired(false);
229       helpOptions.addOption(builder.create("h"));
230       options.addOption(builder.create("h"));
231     }
232     
233     CommandLineParser parser = new BasicParser();
234     CommandLine line = null;
235     try
236     {
237       line = parser.parse(helpOptions, args);
238     }
239     catch(UnrecognizedOptionException e)
240     {
241       ; // don't need to do anything
242     }
243     if(line != null && line.hasOption("h"))
244     {
245       this.showHelp(options);
246       return false;
247     }
248     
249     try
250     {
251       line = parser.parse(options, args);
252     }
253     catch(UnrecognizedOptionException e)
254     {
255       System.out.println(e.getMessage());
256       this.usage(options);
257       return false;
258     }
259     catch(MissingOptionException e)
260     {
261       System.out.println("Missing required option(s): " + e.getMessage());
262       this.usage(options);
263       return false;
264     }
265     
266     if(line.hasOption("c"))
267     {
268       this.url = line.getOptionValue("c");
269     }
270     if(line.hasOption("u"))
271     {
272       this.username = line.getOptionValue("u");
273     }
274     if(line.hasOption("p"))
275     {
276       this.password = line.getOptionValue("p");
277     }
278     if(line.hasOption("f"))
279     {
280       this.password = line.getOptionValue("f");
281     }
282     if(line.hasOption("d"))
283     {
284       this.driverClassName = line.getOptionValue("d");
285     }
286     if(logger.isDebugEnabled())
287     {
288       logger.debug("username: " + (this.username == null ? "null" : this.username));
289       logger.debug("password: " + (this.password == null ? "null" : this.password));
290       logger.debug("connection: " + (this.url == null ? "null" : this.url));
291       logger.debug("driver: " + (this.driverClassName == null ? "null" : this.driverClassName));
292       logger.debug("properties: " + (this.propertiesFileName == null ? "null" : this.propertiesFileName));
293     }
294     if(this.propertiesFileName != null)
295     {
296       this.properties = new Properties();
297       InputStream in = new BufferedInputStream(new FileInputStream(this.propertiesFileName));
298       this.properties.load(in);
299     }
300 
301     return true;
302   }
303 
304   /***
305    * Runs the program logic.
306    *
307    * @throws IOException if an I/O exception occurs
308    * @throws SQLException if an SQL error occurs
309    * @throws ClassNotFoundException if the driver class is not found
310    */
311   public void run() throws IOException, SQLException, ClassNotFoundException
312   {
313     System.out.println("SqlCli v1.0-beta-2");
314     System.out.println("Enter //h for a list of commands");
315     PrintStreamedResultSetExtractor resultSetExtractor = new PrintStreamedResultSetExtractor();
316     resultSetExtractor.addPrintStream(System.out);
317     this.printStreamed.add(resultSetExtractor);
318     
319     PrintStreamedExceptionHandler exceptionHandler = new PrintStreamedExceptionHandler();
320     exceptionHandler.addPrintStream(System.out);
321     this.printStreamed.add(exceptionHandler);
322     
323     Connection connection = null;
324     connection = this.getConnection();
325 
326     this.selectCommand = new SelectCommand();
327     this.selectCommand.setConnection(connection);
328     this.selectCommand.setResultSetExtractor(resultSetExtractor);
329     this.selectCommand.setExceptionHandler(exceptionHandler);
330     
331     this.describeCommand = new DescribeCommand();
332     describeCommand.setConnection(connection);
333     PrintStreamedDescribeResultSetExtractor describeResultSetExtractor = new PrintStreamedDescribeResultSetExtractor();
334     this.printStreamed.add(describeResultSetExtractor);
335     describeResultSetExtractor.addPrintStream(System.out);
336     describeCommand.setResultSetExtractor(describeResultSetExtractor);
337     describeCommand.setExceptionHandler(exceptionHandler);
338     
339     PrintStreamedUpdateRowcountHandler rowcountHandler = new PrintStreamedUpdateRowcountHandler();
340     rowcountHandler.addPrintStream(System.out);
341     this.printStreamed.add(rowcountHandler);
342     this.updateCommand = new UpdateCommand();
343     this.updateCommand.setConnection(connection);
344     this.updateCommand.setExceptionHandler(exceptionHandler);
345     this.updateCommand.setRowcountHandler(rowcountHandler);
346 
347     PrintStreamedShowTablesResultSetExtractor showTableResultSetExtractor = new PrintStreamedShowTablesResultSetExtractor();
348     showTableResultSetExtractor.addPrintStream(System.out);
349     showTableResultSetExtractor.setDatabaseMetaData(connection.getMetaData());
350     this.printStreamed.add(showTableResultSetExtractor);
351     this.showTablesCommand = new ShowTablesCommand();
352     this.showTablesCommand.setConnection(connection);
353     this.showTablesCommand.setExceptionHandler(exceptionHandler);
354     this.showTablesCommand.setResultSetExtractor(showTableResultSetExtractor);
355     
356     BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
357     // Prompt for a statement
358     this.needToClear = false;
359     this.lines = new ArrayList();
360     this.processInput(reader, lines, true);
361     this.outputToConsoleAndLog("Done");
362     this.closeLogFile();
363   }
364   
365   //----------------------------------------------------------------------------
366   // Protected abstract methods
367   //----------------------------------------------------------------------------
368   
369   //----------------------------------------------------------------------------
370   // Protected methods for use by subclasses
371   //----------------------------------------------------------------------------
372   
373   //----------------------------------------------------------------------------
374   // Other methods
375   //----------------------------------------------------------------------------
376 
377   private void processInput(final BufferedReader reader, final List buffer, final boolean prompt)
378     throws IOException
379   {
380     boolean loop = true;
381     this.inComment = false;
382     while(loop)
383     {
384       if(prompt)
385       {
386         System.out.print("SQL> ");
387       }
388       String line = StringUtils.trim(reader.readLine());
389       if(line == null)
390       {
391         break;
392       }
393       // Skip blank lines
394       if(line.length() == 0)
395       {
396         continue;
397       }
398       // Skip block comment lines
399       if(this.inComment)
400       {
401         // If the line doesn't contain an end-of-comment marker, skip it
402         if(line.indexOf("*/") == -1)
403         {
404           continue;
405         }
406       }
407       // Strip the line of block comments and look for a block comment open or
408       // close
409       // If a comment marker is contained in single quotes, ignore it
410       // Otherwise, open or close the comment, grab the part of the line
411       // not in the block comment
412       boolean inQuote = false;
413       char[] lineBuffer = new char[line.length()];
414       int j = 0;
415       for(int i = 0; i < line.length(); i++)
416       {
417         if(line.charAt(i) == '\'')
418         {
419           inQuote = (inQuote ? false : true);
420         }
421         else if(line.startsWith("*/", i) && !inQuote)
422         {
423           this.inComment = false;
424           i += 2;
425         }
426         else if(line.startsWith("/*", i) && !inQuote)
427         {
428           this.inComment = true;
429           i += 2;
430         }
431         if(!inComment && i < line.length())
432         {
433           lineBuffer[j++] = line.charAt(i);
434         }
435       }
436       line = StringUtils.trim(new String(lineBuffer));
437       // Skip blank lines
438       if(line.length() == 0)
439       {
440         continue;
441       }
442 
443       // Remove end-of-line comments
444       if(line.indexOf("//") > -1)
445       {
446         line = line.substring(0, line.indexOf("//"));
447       }
448       if(line.indexOf("--") > -1)
449       {
450         line = line.substring(0, line.indexOf("--"));
451       }
452       if(line.length() == 0)
453       {
454         continue;
455       }
456       // Process cli directives
457       if(line.charAt(0) == '//')
458       {
459         if(line.length() == 1)
460         {
461           this.outputToConsoleAndLog("syntax error: unmatched //");
462         }
463         else if(line.charAt(1) == 'i')
464         {
465           loop = this.doCommand(line, buffer);
466         }
467         else if(line.charAt(1) == 'w')
468         {
469           loop = this.doCommand(line, buffer);
470         }
471         else if(line.charAt(1) == 'l')
472         {
473           loop = this.doCommand(line, buffer);
474         }
475         else if(line.length() >= CD_DIRECTIVE_MINIMUM_LENGTH && line.substring(1, CD_COMMAND_MAXIMUM_OFFSET).equals("cd"))
476         {
477           loop = this.doCommand(line, buffer);
478         }
479         else if(line.length() >= PWD_DIRECTIVE_MINIMUM_LENGTH && line.substring(1, PWD_COMMAND_MAXIMUM_OFFSET).equals("pwd"))
480         {
481           loop = this.doCommand(line, buffer);
482         }
483         else
484         {
485           // Parse the line for cli directives across the line
486           List commandList = new ArrayList();
487           char[] command = new char[2];
488           for(int i = 0; i < line.length(); i++)
489           {
490             command[0] = line.charAt(i);
491             if(Character.isWhitespace(line.charAt(i)))
492             {
493               continue;
494             }
495             if(line.charAt(i) != '//')
496             {
497               this.outputToConsoleAndLog("syntax error: unexpected character '" + line.charAt(i) + "'");
498               break;
499             }
500             if(i+1 >= line.length())
501             {
502               this.outputToConsoleAndLog("syntax error: unmatched //");
503               break;
504             }
505             command[1] = line.charAt(i+1);
506             String commandString = new String(command);
507             commandList.add(commandString);
508             ++i;
509           }
510           boolean commandLoop = true;
511           for(Iterator iter = commandList.iterator(); iter.hasNext() && commandLoop;)
512           {
513             String commandString = (String) iter.next();
514             commandLoop = this.doCommand(commandString, buffer);
515             loop = commandLoop;
516           }
517         }
518       }
519       else
520       {
521         // Clear the buffer if necessary
522         if(this.needToClear)
523         {
524           this.clearBuffer(buffer);
525           this.needToClear = false;
526         }
527         // Parse the line for cli directives at end of line
528         List commandList = new ArrayList();
529         char[] command = new char[2];
530         int k = line.length();
531         for(int i = line.length() - 1; i >= 1; i -= 2)
532         {
533           if(line.charAt(i-1) != '//')
534           {
535             break;
536           }
537           command[0] = line.charAt(i-1);
538           command[1] = line.charAt(i);
539           String commandString = new String(command);
540           commandList.add(0, commandString);
541           k -= 2;
542         }
543         
544         // Put line into buffer
545         buffer.add(line.substring(0, k));
546 
547         // Handle cli directives
548         if(commandList.size() > 0)
549         {
550           boolean commandLoop = true;
551           for(Iterator iter = commandList.iterator(); iter.hasNext() && commandLoop;)
552           {
553             String commandString = (String) iter.next();
554             commandLoop = this.doCommand(commandString, buffer);
555             loop = commandLoop;
556           }
557         }
558       }
559     }
560   }
561   
562   private boolean doCommand(final String command, final List buffer) throws IOException
563   {
564     if(command.length() >= CD_DIRECTIVE_MINIMUM_LENGTH && command.substring(1, CD_COMMAND_MAXIMUM_OFFSET).equals("cd"))
565     {
566       this.changeWorkingDirectory(command);
567       this.showWorkingDirectory();
568       return true;
569     }
570     else if(command.length() >= PWD_DIRECTIVE_MINIMUM_LENGTH && command.substring(1, PWD_COMMAND_MAXIMUM_OFFSET).equals("pwd"))
571     {
572       this.showWorkingDirectory();
573       return true;
574     }
575     switch(command.charAt(1))
576     {
577       case 'q':
578       {
579         if(this.loggerPrintStream != null)
580         {
581           this.loggerPrintStream.flush();
582           this.loggerPrintStream = null;
583         }
584         return false;
585       }
586       case 'g':
587       {
588         this.executeBuffer(buffer);
589         this.needToClear = true;
590         break;
591       }
592       case 'p':
593       {
594         this.printBuffer(buffer);
595         this.needToClear = false;
596         break;
597       }
598       case 'l':
599       {
600         this.toggleLogFile(command);
601         break;
602       }
603       case 'i':
604       {
605         this.includeFile(command, buffer);
606         break;
607       }
608       case 'w':
609       {
610         this.writeToFile(command, buffer);
611         break;
612       }
613       case 'r':
614       {
615         this.clearBuffer(buffer);
616         break;
617       }
618       case 't':
619       {
620         this.displayTime();
621         break;
622       }
623       case 'h':
624       {
625         this.showCommands();
626         break;
627       }
628       default:
629       {
630         this.outputToConsoleAndLog("unknown command");
631         break;
632       }
633     }
634     return true;
635   }
636 
637   private void changeWorkingDirectory(final String command)
638   {
639     String[] fields = StringUtils.split(command, " ");
640     if(fields.length < 2)
641     {
642       // remove current working directory (set back to default working directory)
643       this.workingDirectory = null;
644     }
645     else
646     {
647       // change the current working directory
648       if(logger.isDebugEnabled())
649       {
650         logger.debug("cd argument: " + fields[1]);
651       }
652       File newWorkingDirectory = new File(fields[1]);
653       if(logger.isDebugEnabled())
654       {
655         logger.debug("isAbsolute: " + newWorkingDirectory.isAbsolute());
656         logger.debug("isDirectory: " + newWorkingDirectory.isDirectory());
657       }
658       if(!newWorkingDirectory.isAbsolute())
659       {
660         newWorkingDirectory = new File(this.workingDirectory, fields[1]);
661       }
662       if(newWorkingDirectory.isDirectory() && newWorkingDirectory.canRead() && newWorkingDirectory.canWrite())
663       {
664         this.workingDirectory = newWorkingDirectory;
665       }
666       else
667       {
668         this.outputToConsoleAndLog("Directory '" + newWorkingDirectory.getPath() + "' does not exist or is not accessible");
669       }
670     }
671   }
672 
673   private void showWorkingDirectory()
674   {
675     // show the current working directory
676     this.outputToConsoleAndLog("current working directory: " + (this.workingDirectory == null ? "default working directory" : this.workingDirectory.getPath()));
677   }
678   
679   private void clearBuffer(final List buffer)
680   {
681     buffer.clear();
682   }
683 
684   private void describeTable(final String[] words)
685   {
686     if(words.length < 2)
687     {
688       this.outputToConsoleAndLog("Syntax error: expected table name (desc[ribe] table_name)");
689     }
690     else
691     {
692       this.describeCommand.setTableName(words[1]);
693       this.describeCommand.execute();
694     }
695   }
696   
697   private void show(final String[] words)
698   {
699     boolean error = false;
700     if(words.length == 2)
701     {
702       if(words[1].equalsIgnoreCase("tables"))
703       {
704         this.showTables();
705       }
706       else
707       {
708         error = true;
709       }
710     }
711     else
712     {
713       error = true;
714     }
715     if(error)
716     {
717       this.outputToConsoleAndLog("Syntax error: unknown show command");
718     }
719   }
720   
721   private void executeBuffer(final List lineBuffer)
722   {
723     List statements = lineBuffer;
724     for(Iterator iter = statements.iterator(); iter.hasNext();)
725     {
726       String line = (String) iter.next();
727       
728       // Build the full line by looking for a line terminated with a semi-colon
729       // If there is no terminating semi-colon, treat all the lines as one
730       // statement
731       StringBuffer buffer = new StringBuffer(line.length());
732       buffer.append(line);
733       while(!line.endsWith(";") && iter.hasNext())
734       {
735         line = (String) iter.next();
736         buffer.append(" ");
737         buffer.append(line);
738       }
739       String commandLine = StringUtils.trim(buffer.toString());
740       String[] commands = StringUtils.split(commandLine, ";");
741       for(int i = 0; i < commands.length; i++)
742       {
743         String command = commands[i];
744         // Get the first word off the command
745         String[] words = StringUtils.split(command);
746         if(words[0].equalsIgnoreCase("desc"))
747         {
748           // Describe table
749           this.describeTable(words);
750         }
751         else if(words[0].equalsIgnoreCase("describe"))
752         {
753           // Describe table
754           this.describeTable(words);
755         }
756         else if(words[0].equalsIgnoreCase("show"))
757         {
758           // Show command
759           this.show(words);
760         }
761         else if(words[0].equalsIgnoreCase("select"))
762         {
763           this.selectCommand.setCommand(command);
764           this.selectCommand.execute();
765         }
766         else
767         {
768           // Assume this is an update command
769           this.updateCommand.setCommand(command);
770           this.updateCommand.execute();
771         }
772       }
773     }
774   }
775 
776   private void displayTime()
777   {
778     Date now = new Date();
779     this.outputToConsoleAndLog(now);
780   }
781 
782   private void outputToConsoleAndLog(Object object)
783   {
784     System.out.println(object);
785     if(this.loggerPrintStream != null)
786     {
787       this.loggerPrintStream.println(object);
788     }
789   }
790   
791   private void outputToConsoleAndLog(String string)
792   {
793     System.out.println(string);
794     if(this.loggerPrintStream != null)
795     {
796       this.loggerPrintStream.println(string);
797     }
798   }
799   
800   private void printBuffer(final List buffer)
801   {
802     for(Iterator iter = buffer.iterator(); iter.hasNext();)
803     {
804       String line = (String) iter.next();
805       this.outputToConsoleAndLog(line);
806     }
807   }
808 
809   private void toggleLogFile(final String command) throws IOException
810   {
811     if(this.loggerPrintStream == null)
812     {
813       // Filename should start after the \l directive
814       String fileName = null;
815       int index = command.indexOf(' ');
816       if(index < 0)
817       {
818         fileName = "sqlcli.log";
819       }
820       else
821       {
822         fileName = command.substring(LOGGER_FILE_NAME_INDEX);
823       }
824       // Construct an absolute file name if the file name given is not
825       File file = new File(fileName);
826       if(!file.isAbsolute())
827       {
828         file = new File(this.workingDirectory, fileName);
829       }
830       this.loggerPrintStream = new PrintStream(new BufferedOutputStream(new FileOutputStream(file)));
831       for(Iterator iter = this.printStreamed.iterator(); iter.hasNext();)
832       {
833         PrintStreamed value = (PrintStreamed) iter.next();
834         value.addPrintStream(this.loggerPrintStream);
835       }
836       System.out.println("Logging enabled to file '" + fileName + "'");
837     }
838     else
839     {
840       for(Iterator iter = this.printStreamed.iterator(); iter.hasNext();)
841       {
842         PrintStreamed value = (PrintStreamed) iter.next();
843         value.removePrintStream(this.loggerPrintStream);
844       }
845       this.loggerPrintStream.flush();
846       this.loggerPrintStream = null;
847       System.out.println("Logging disabled.");
848     }
849   }
850 
851   private void closeLogFile()
852   {
853     if(this.loggerPrintStream != null)
854     {
855       this.loggerPrintStream.flush();
856       this.loggerPrintStream = null;
857     }
858   }
859   
860   private void includeFile(final String command, final List buffer) throws IOException
861   {
862     String[] tokens = StringUtils.split(command);
863     if(tokens.length < 2)
864     {
865       this.outputToConsoleAndLog("syntax error: //i requires file name");
866     }
867     else
868     {
869       String fileName = "";
870       for(int i = 1; i < tokens.length; i++)
871       {
872         if(i > 1)
873         {
874           fileName += " ";
875         }
876         fileName += tokens[i];
877       }
878       File includeFile = this.expandReadableFileName(fileName);
879       BufferedReader reader = null;
880       try
881       {
882         reader = new BufferedReader(new FileReader(includeFile));
883         this.processInput(reader, buffer, false);
884       }
885       catch(FileNotFoundException e)
886       {
887         this.outputToConsoleAndLog("file not found: " + tokens[1]);
888       }
889     }
890   }
891 
892   private void writeToFile(final String command, final List buffer) throws IOException
893   {
894     // Filename should start after the \w directive
895     if(command.length() <= WRITE_TO_FILE_COMMAND_MINIMUM_LENGTH)
896     {
897       System.out.println("syntax error: //w requires file name");
898       return;
899     }
900     else
901     {
902       String fileName = null;
903       try
904       {
905         fileName = command.substring(WRITE_TO_FILE_NAME_INDEX);
906       }
907       catch(IndexOutOfBoundsException e)
908       {
909         System.out.println("syntax error: //w requires file name");
910         return;
911       }
912       PrintStream writer = null;
913       try
914       {
915         writer = new PrintStream(new BufferedOutputStream(new FileOutputStream(fileName)));
916         for(Iterator iter = buffer.iterator(); iter.hasNext();)
917         {
918           String line = (String) iter.next();
919           writer.println(line);
920         }
921       }
922       finally
923       {
924         writer.close();
925       }
926     }
927     
928   }
929   
930   private void showCommands()
931   {
932     System.out.println("Directives:");
933     System.out.println("  //cd - change the working directory to the default directory");
934     System.out.println("  //cd <directory> - change the working directory to the directory specified");
935     System.out.println("  //g  - execute the SQL commands in the input buffer");
936     System.out.println("  //h  - print this list");
937     System.out.println("  //i <filename> - include the contents of the named file into the input buffer");
938     System.out.println("  //l <filename> - turn on logging of output to the named file if logging is off");
939     System.out.println("  //l - turn off logging if logging is on; otherwise turn on logging to the file 'sqlcli.log'");
940     System.out.println("  //pwd - print the working directory");
941     System.out.println("  //w <filename> - write the contents of the input buffer to the named file");
942     System.out.println("  //q - quit");
943     System.out.println("");
944     System.out.println("Extended commands:");
945     System.out.println("  describe <tablename> - print a list of the columns in the specified table");
946     System.out.println("  show tables - print a list of tables in the database");
947   }
948   
949   private void showTables()
950   {
951     this.showTablesCommand.execute();
952   }
953 
954   private Connection getConnection() throws ClassNotFoundException, SQLException
955   {
956     Class.forName(this.driverClassName);
957     if(this.properties != null)
958     {
959       DriverManager.getConnection(url, properties);
960     }
961     if(this.username != null)
962     {
963       return DriverManager.getConnection(this.url, this.username, this.password);
964     }
965     else
966     {
967       return DriverManager.getConnection(this.url);
968     } 
969   }
970 
971   private void showHelp(final Options options)
972   {
973     HelpFormatter helpFormatter = new HelpFormatter();
974     PrintWriter writer = new PrintWriter(System.out, true);
975     helpFormatter.printHelp(PRINT_USAGE_WIDTH, PROGRAM_NAME, null, options, null, true);
976   }
977   
978   private void usage(final Options options)
979   {
980     HelpFormatter helpFormatter = new HelpFormatter();
981     PrintWriter writer = new PrintWriter(System.out, true);
982     helpFormatter.printUsage(writer, PRINT_USAGE_WIDTH, PROGRAM_NAME, options);
983   }
984 
985   private File expandReadableFileName(final String fileName)
986   {
987     File file = null;
988     if(this.workingDirectory != null)
989     {
990       file = new File(this.workingDirectory.getPath() + File.separatorChar + fileName);
991       if(file != null && file.exists() && file.canRead())
992       {
993         return file;
994       }
995       else
996       {
997         file = new File(fileName);
998         if(file.exists())
999         {
1000           return file;
1001         }
1002         else
1003         {
1004           file = null;
1005         }
1006       }
1007     }
1008     if(file == null)
1009     {
1010       file = new File(fileName);
1011     }
1012     return file;
1013   }
1014   
1015   //----------------------------------------------------------------------------
1016   // Member classes
1017   //----------------------------------------------------------------------------
1018   
1019 }