View Javadoc
1   /*
2    * This file is a part of the SchemaSpy project (http://schemaspy.sourceforge.net).
3    * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010 John Currier
4    *
5    * SchemaSpy is free software; you can redistribute it and/or
6    * modify it under the terms of the GNU Lesser General Public
7    * License as published by the Free Software Foundation; either
8    * version 2.1 of the License, or (at your option) any later version.
9    *
10   * SchemaSpy is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13   * Lesser General Public License for more details.
14   *
15   * You should have received a copy of the GNU Lesser General Public
16   * License along with this library; if not, write to the Free Software
17   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18   */
19  package net.sourceforge.schemaspy;
20  
21  import java.beans.BeanInfo;
22  import java.beans.IntrospectionException;
23  import java.beans.Introspector;
24  import java.beans.PropertyDescriptor;
25  import java.io.File;
26  import java.io.FileInputStream;
27  import java.io.FileNotFoundException;
28  import java.io.IOException;
29  import java.lang.reflect.InvocationTargetException;
30  import java.lang.reflect.Method;
31  import java.net.URISyntaxException;
32  import java.net.URL;
33  import java.net.URLDecoder;
34  import java.sql.DatabaseMetaData;
35  import java.util.ArrayList;
36  import java.util.Arrays;
37  import java.util.Enumeration;
38  import java.util.HashMap;
39  import java.util.LinkedHashMap;
40  import java.util.List;
41  import java.util.Map;
42  import java.util.Map.Entry;
43  import java.util.Properties;
44  import java.util.PropertyResourceBundle;
45  import java.util.ResourceBundle;
46  import java.util.Set;
47  import java.util.StringTokenizer;
48  import java.util.TreeSet;
49  import java.util.jar.JarEntry;
50  import java.util.jar.JarInputStream;
51  import java.util.logging.Level;
52  import java.util.regex.Pattern;
53  import java.util.regex.PatternSyntaxException;
54  import net.sourceforge.schemaspy.model.InvalidConfigurationException;
55  import net.sourceforge.schemaspy.util.DbSpecificConfig;
56  import net.sourceforge.schemaspy.util.Dot;
57  import net.sourceforge.schemaspy.view.DefaultSqlFormatter;
58  import net.sourceforge.schemaspy.view.SqlFormatter;
59  
60  /**
61   * Configuration of a SchemaSpy run
62   *
63   * @author John Currier
64   */
65  public class Config
66  {
67      private static Config instance;
68      private final List<String> options;
69      private Map<String, String> dbSpecificOptions;
70      private Map<String, String> originalDbSpecificOptions;
71      private boolean helpRequired;
72      private boolean dbHelpRequired;
73      private File outputDir;
74      private File graphvizDir;
75      private String dbType;
76      private String schema;
77      private List<String> schemas;
78      private String user;
79      private Boolean singleSignOn;
80      private Boolean noSchema;
81      private String password;
82      private Boolean promptForPassword;
83      private String db;
84      private String host;
85      private Integer port;
86      private String server;
87      private String meta;
88      private Pattern tableInclusions;
89      private Pattern tableExclusions;
90      private Pattern columnExclusions;
91      private Pattern indirectColumnExclusions;
92      private String userConnectionPropertiesFile;
93      private Properties userConnectionProperties;
94      private Integer maxDbThreads;
95      private Integer maxDetailedTables;
96      private String driverPath;
97      private String css;
98      private String charset;
99      private String font;
100     private Integer fontSize;
101     private String description;
102     private String dbPropertiesLoadedFrom;
103     private Level logLevel;
104     private SqlFormatter sqlFormatter;
105     private String sqlFormatterClass;
106     private Boolean generateHtml;
107     private Boolean includeImpliedConstraints;
108     private Boolean logoEnabled;
109     private Boolean rankDirBugEnabled;
110     private Boolean encodeCommentsEnabled;
111     private Boolean numRowsEnabled;
112     private Boolean viewsEnabled;
113     private Boolean meterEnabled;
114     private Boolean railsEnabled;
115     private Boolean evaluteAll;
116     private Boolean highQuality;
117     private Boolean lowQuality;
118     private Boolean adsEnabled;
119     private String schemaSpec;  // used in conjunction with evaluateAll
120     private boolean populating = false;
121     public static final String DOT_CHARSET = "UTF-8";
122     private static final String ESCAPED_EQUALS = "\\=";
123 
124     /**
125      * Default constructor. Intended for when you want to inject properties
126      * independently (i.e. not from a command line interface).
127      */
128     public Config()
129     {
130         if (instance == null)
131             setInstance(this);
132         options = new ArrayList<String>();
133     }
134 
135     /**
136      * Construct a configuration from an array of options (e.g. from a command
137      * line interface).
138      *
139      */
140     public Config(String[] argv)
141     {
142         setInstance(this);
143         options = fixupArgs(Arrays.asList(argv));
144 
145         helpRequired =  options.remove("-?") ||
146                         options.remove("/?") ||
147                         options.remove("?") ||
148                         options.remove("-h") ||
149                         options.remove("-help") ||
150                         options.remove("--help");
151         dbHelpRequired =  options.remove("-dbHelp") || options.remove("-dbhelp");
152     }
153 
154     public static Config getInstance() {
155         if (instance == null)
156             instance = new Config();
157 
158         return instance;
159     }
160 
161     /**
162      * Sets the global instance.
163      *
164      * Useful for things like selecting a specific configuration in a UI.
165      *
166      */
167     public static void setInstance(Config config) {
168         instance = config;
169     }
170 
171     public void setHtmlGenerationEnabled(boolean generateHtml) {
172         this.generateHtml = generateHtml;
173     }
174 
175     public boolean isHtmlGenerationEnabled() {
176         if (generateHtml == null)
177             generateHtml = !options.remove("-nohtml");
178 
179         return generateHtml;
180     }
181 
182     public void setImpliedConstraintsEnabled(boolean includeImpliedConstraints) {
183         this.includeImpliedConstraints = includeImpliedConstraints;
184     }
185 
186     public boolean isImpliedConstraintsEnabled() {
187         if (includeImpliedConstraints == null)
188             includeImpliedConstraints = !options.remove("-noimplied");
189 
190         return includeImpliedConstraints;
191     }
192 
193     public void setOutputDir(String outputDirName) {
194         if (outputDirName.endsWith("\""))
195             outputDirName = outputDirName.substring(0, outputDirName.length() - 1);
196 
197         setOutputDir(new File(outputDirName));
198     }
199 
200     public void setOutputDir(File outputDir) {
201         this.outputDir = outputDir;
202     }
203 
204     public File getOutputDir() {
205         if (outputDir == null) {
206             setOutputDir(pullRequiredParam("-o"));
207         }
208 
209         return outputDir;
210     }
211 
212     /**
213      * Set the path to Graphviz so we can find dot to generate ER diagrams
214      *
215      */
216     public void setGraphvizDir(String graphvizDir) {
217         if (graphvizDir.endsWith("\""))
218             graphvizDir = graphvizDir.substring(0, graphvizDir.length() - 1);
219 
220         setGraphvizDir(new File(graphvizDir));
221     }
222 
223     /**
224      * Set the path to Graphviz so we can find dot to generate ER diagrams
225      */
226     public void setGraphvizDir(File graphvizDir) {
227         this.graphvizDir = graphvizDir;
228     }
229 
230     /**
231      * Return the path to Graphviz (used to find the dot executable to run to
232      * generate ER diagrams).
233      *
234      * Returns path to Graphviz if a specific Graphviz path
235      * was not specified.
236      *
237      */
238     public File getGraphvizDir() {
239         if (graphvizDir == null) {
240             String gv = pullParam("-gv");
241             if (gv != null) {
242                 setGraphvizDir(gv);
243             } else {
244                 // expect to find Graphviz's bin directory on the PATH
245             }
246         }
247 
248         return graphvizDir;
249     }
250 
251     /**
252      * Meta files are XML-based files that provide additional metadata
253      * about the schema being evaluated.<p>
254      * <code>meta</code> is either the name of an individual XML file or
255      * the directory that contains meta files.<p>
256      * If a directory is specified then it is expected to contain files
257      * matching the pattern <code>[schema].meta.xml</code>.
258      * For databases that don't have schema substitute database for schema.
259      */
260     public void setMeta(String meta) {
261         this.meta = meta;
262     }
263 
264     public String getMeta() {
265         if (meta == null)
266             meta = pullParam("-meta");
267         return meta;
268     }
269 
270     public void setDbType(String dbType) {
271         this.dbType = dbType;
272     }
273 
274     public String getDbType() {
275         if (dbType == null) {
276             dbType = pullParam("-t");
277             if (dbType == null)
278                 dbType = "ora";
279         }
280 
281         return dbType;
282     }
283 
284     public void setDb(String db) {
285         this.db = db;
286     }
287 
288     public String getDb() {
289         if (db == null)
290             db = pullParam("-db");
291         return db;
292     }
293 
294     public void setSchema(String schema) {
295         this.schema = schema;
296     }
297 
298     public String getSchema() {
299         if (schema == null)
300             schema = pullParam("-s");
301         return schema;
302     }
303 
304     /**
305      * Some databases types (e.g. older versions of Informix) don't really
306      * have the concept of a schema but still return true from
307      * {@link DatabaseMetaData#supportsSchemasInTableDefinitions()}.
308      * This option lets you ignore that and treat all the tables
309      * as if they were in one flat namespace.
310      */
311     public boolean isSchemaDisabled() {
312         if (noSchema == null)
313             noSchema = options.remove("-noschema");
314 
315         return noSchema;
316     }
317 
318     public void setHost(String host) {
319         this.host = host;
320     }
321 
322     public String getHost() {
323         if (host == null)
324             host = pullParam("-host");
325         return host;
326     }
327 
328     public void setPort(Integer port) {
329         this.port = port;
330     }
331 
332     public Integer getPort() {
333         if (port == null)
334             try {
335                 port = Integer.valueOf(pullParam("-port"));
336             } catch (Exception notSpecified) {}
337         return port;
338     }
339 
340     public void setServer(String server) {
341         this.server = server;
342     }
343 
344     public String getServer() {
345         if (server == null) {
346             server = pullParam("-server");
347         }
348 
349         return server;
350     }
351 
352     public void setUser(String user) {
353         this.user = user;
354     }
355 
356     /**
357      * User used to connect to the database.
358      * Required unless single sign-on is enabled
359      * (see {@link #setSingleSignOn(boolean)}).
360 
361      */
362     public String getUser() {
363         if (user == null) {
364             if (!isSingleSignOn())
365                 user = pullRequiredParam("-u");
366             else
367                 user = pullParam("-u");
368         }
369         return user;
370     }
371 
372     /**
373      * By default a "user" (as specified with -u) is required.
374      * This option allows disabling of that requirement for
375      * single sign-on environments.
376      *
377      * @param enabled defaults to <code>false</code>
378      */
379     public void setSingleSignOn(boolean enabled) {
380         singleSignOn = enabled;
381     }
382 
383     /**
384      * @see #setSingleSignOn(boolean)
385      */
386     public boolean isSingleSignOn() {
387         if (singleSignOn == null)
388             singleSignOn = options.remove("-sso");
389 
390         return singleSignOn;
391     }
392 
393     /**
394      * Set the password used to connect to the database.
395      * @param password
396      */
397     public void setPassword(String password) {
398         this.password = password;
399     }
400 
401     /**
402      * @see #setPassword(String)
403 
404      */
405     public String getPassword() {
406         if (password == null)
407             password = pullParam("-p");
408         return password;
409     }
410 
411     /**
412      * Set to <code>true</code> to prompt for the password
413      * @param promptForPassword
414      */
415     public void setPromptForPasswordEnabled(boolean promptForPassword) {
416         this.promptForPassword = promptForPassword;
417     }
418 
419     /**
420      * @see #setPromptForPasswordEnabled(boolean)
421 
422      */
423     public boolean isPromptForPasswordEnabled() {
424         if (promptForPassword == null) {
425             promptForPassword = options.remove("-pfp");
426         }
427 
428         return promptForPassword;
429     }
430 
431     public void setMaxDetailedTabled(int maxDetailedTables) {
432         this.maxDetailedTables = new Integer(maxDetailedTables);
433     }
434 
435     public int getMaxDetailedTables() {
436         if (maxDetailedTables == null) {
437             int max = 300; // default
438             try {
439                 max = Integer.parseInt(pullParam("-maxdet"));
440             } catch (Exception notSpecified) {}
441 
442             maxDetailedTables = new Integer(max);
443         }
444 
445         return maxDetailedTables.intValue();
446     }
447 
448     public String getConnectionPropertiesFile() {
449         return userConnectionPropertiesFile;
450     }
451 
452     /**
453      * Properties from this file (in key=value pair format) are passed to the
454      * database connection.<br>
455      * user (from -u) and password (from -p) will be passed in the
456      * connection properties if specified.
457      * @param propertiesFilename
458      * @throws FileNotFoundException
459      * @throws IOException
460      */
461     public void setConnectionPropertiesFile(String propertiesFilename) throws FileNotFoundException, IOException {
462         if (userConnectionProperties == null)
463             userConnectionProperties = new Properties();
464         userConnectionProperties.load(new FileInputStream(propertiesFilename));
465         userConnectionPropertiesFile = propertiesFilename;
466     }
467 
468     /**
469      * Returns a {@link Properties} populated either from the properties file specified
470      * by {@link #setConnectionPropertiesFile(String)}, the properties specified by
471      * {@link #setConnectionProperties(String)} or not populated.
472 
473      * @throws FileNotFoundException
474      * @throws IOException
475      */
476     public Properties getConnectionProperties() throws FileNotFoundException, IOException {
477         if (userConnectionProperties == null) {
478             String props = pullParam("-connprops");
479             if (props != null) {
480                 if (props.indexOf(ESCAPED_EQUALS) != -1) {
481                     setConnectionProperties(props);
482                 } else {
483                     setConnectionPropertiesFile(props);
484                 }
485             } else {
486                 userConnectionProperties = new Properties();
487             }
488         }
489 
490         return userConnectionProperties;
491     }
492 
493     /**
494      * Specifies connection properties to use in the format:
495      * <code>key1\=value1;key2\=value2</code><br>
496      * user (from -u) and password (from -p) will be passed in the
497      * connection properties if specified.<p>
498      * This is an alternative form of passing connection properties than by file
499      * (see {@link #setConnectionPropertiesFile(String)})
500      *
501      * @param properties
502      */
503     public void setConnectionProperties(String properties) {
504         userConnectionProperties = new Properties();
505 
506         StringTokenizer tokenizer = new StringTokenizer(properties, ";");
507         while (tokenizer.hasMoreElements()) {
508             String pair = tokenizer.nextToken();
509             int index = pair.indexOf(ESCAPED_EQUALS);
510             if (index != -1) {
511                 String key = pair.substring(0, index);
512                 String value = pair.substring(index + ESCAPED_EQUALS.length());
513                 userConnectionProperties.put(key, value);
514             }
515         }
516     }
517 
518     public void setDriverPath(String driverPath) {
519         this.driverPath = driverPath;
520     }
521 
522     public String getDriverPath() {
523         if (driverPath == null)
524             driverPath = pullParam("-dp");
525 
526         // was previously -cp:
527         if (driverPath == null)
528             driverPath = pullParam("-cp");
529 
530         return driverPath;
531     }
532 
533     /**
534      * The filename of the cascading style sheet to use.
535      * Note that this file is parsed and used to determine characteristics
536      * of the generated diagrams, so it must contain specific settings that
537      * are documented within schemaSpy.css.<p>
538      *
539      * Defaults to <code>"schemaSpy.css"</code>.
540      *
541      * @param css
542      */
543     public void setCss(String css) {
544         this.css = css;
545     }
546 
547     public String getCss() {
548         if (css == null) {
549             css = pullParam("-css");
550             if (css == null)
551                 css = "schemaSpy.css";
552         }
553         return css;
554     }
555 
556     /**
557      * The font to use within diagrams.  Modify the .css to specify HTML fonts.
558      *
559      * @param font
560      */
561     public void setFont(String font) {
562         this.font = font;
563     }
564 
565     /**
566      * @see #setFont(String)
567      */
568     public String getFont() {
569         if (font == null) {
570             font = pullParam("-font");
571             if (font == null)
572                 font = "Helvetica";
573         }
574         return font;
575     }
576 
577     /**
578      * The font size to use within diagrams.  This is the size of the font used for
579      * 'large' (e.g. not 'compact') diagrams.<p>
580      *
581      * Modify the .css to specify HTML font sizes.<p>
582      *
583      * Defaults to 11.
584      *
585      * @param fontSize
586      */
587     public void setFontSize(int fontSize) {
588         this.fontSize = new Integer(fontSize);
589     }
590 
591     /**
592      * @see #setFontSize(int)
593 
594      */
595     public int getFontSize() {
596         if (fontSize == null) {
597             int size = 11; // default
598             try {
599                 size = Integer.parseInt(pullParam("-fontsize"));
600             } catch (Exception notSpecified) {}
601 
602             fontSize = new Integer(size);
603         }
604 
605         return fontSize.intValue();
606     }
607 
608     /**
609      * The character set to use within HTML pages (defaults to <code>"ISO-8859-1"</code>).
610      *
611      * @param charset
612      */
613     public void setCharset(String charset) {
614         this.charset = charset;
615     }
616 
617     /**
618      * @see #setCharset(String)
619      */
620     public String getCharset() {
621         if (charset == null) {
622             charset = pullParam("-charset");
623             if (charset == null)
624                 charset = "ISO-8859-1";
625         }
626         return charset;
627     }
628 
629     /**
630      * Description of schema that gets display on main pages.
631      *
632      * @param description
633      */
634     public void setDescription(String description) {
635         this.description = description;
636     }
637 
638     /**
639      * @see #setDescription(String)
640      */
641     public String getDescription() {
642         if (description == null)
643             description = pullParam("-desc");
644         return description;
645     }
646 
647     /**
648      * Maximum number of threads to use when querying database metadata information.
649      *
650      * @param maxDbThreads
651      */
652     public void setMaxDbThreads(int maxDbThreads) {
653         this.maxDbThreads = new Integer(maxDbThreads);
654     }
655 
656     /**
657      * @see #setMaxDbThreads(int)
658      * @throws InvalidConfigurationException if unable to load properties
659      */
660     public int getMaxDbThreads() throws InvalidConfigurationException {
661         if (maxDbThreads == null) {
662             Properties properties;
663             try {
664                 properties = getDbProperties(getDbType());
665             } catch (IOException exc) {
666                 throw new InvalidConfigurationException("Failed to load properties for " + getDbType() + ": " + exc)
667                                 .setParamName("-type");
668             }
669 
670             int max = Integer.MAX_VALUE;
671             String threads = properties.getProperty("dbThreads");
672             if (threads == null)
673                 threads = properties.getProperty("dbthreads");
674             if (threads != null)
675                 max = Integer.parseInt(threads);
676             threads = pullParam("-dbThreads");
677             if (threads == null)
678                 threads = pullParam("-dbthreads");
679             if (threads != null)
680                 max = Integer.parseInt(threads);
681             if (max < 0)
682                 max = Integer.MAX_VALUE;
683             else if (max == 0)
684                 max = 1;
685 
686             maxDbThreads = new Integer(max);
687         }
688 
689         return maxDbThreads.intValue();
690     }
691 
692     public boolean isLogoEnabled() {
693         if (logoEnabled == null)
694             logoEnabled = !options.remove("-nologo");
695 
696         return logoEnabled;
697     }
698 
699     /**
700      * Don't use this unless absolutely necessary as it screws up the layout
701      *
702      * @param enabled
703      */
704     public void setRankDirBugEnabled(boolean enabled) {
705         rankDirBugEnabled = enabled;
706     }
707 
708     /**
709      * @see #setRankDirBugEnabled(boolean)
710      */
711     public boolean isRankDirBugEnabled() {
712         if (rankDirBugEnabled == null)
713             rankDirBugEnabled = options.remove("-rankdirbug");
714 
715         return rankDirBugEnabled;
716     }
717 
718     /**
719      * Look for Ruby on Rails-based naming conventions in
720      * relationships between logical foreign keys and primary keys.<p>
721      *
722      * Basically all tables have a primary key named <code>ID</code>.
723      * All tables are named plural names.
724      * The columns that logically reference that <code>ID</code> are the singular
725      * form of the table name suffixed with <code>_ID</code>.<p>
726      *
727      * @param enabled
728      */
729     public void setRailsEnabled(boolean enabled) {
730         railsEnabled = enabled;
731     }
732 
733     /**
734      * @see #setRailsEnabled(boolean)
735      *
736 
737      */
738     public boolean isRailsEnabled() {
739         if (railsEnabled == null)
740             railsEnabled = options.remove("-rails");
741 
742         return railsEnabled;
743     }
744 
745     /**
746      * Allow Html In Comments - encode them unless otherwise specified
747      */
748     public void setEncodeCommentsEnabled(boolean enabled) {
749         encodeCommentsEnabled = enabled;
750     }
751 
752     /**
753      * @see #setEncodeCommentsEnabled(boolean)
754      */
755     public boolean isEncodeCommentsEnabled() {
756         if (encodeCommentsEnabled == null)
757             encodeCommentsEnabled = !options.remove("-ahic");
758 
759         return encodeCommentsEnabled;
760     }
761 
762     /**
763      * If enabled we'll attempt to query/render the number of rows that
764      * each table contains.<p/>
765      *
766      * Defaults to <code>true</code> (enabled).
767      *
768      * @param enabled
769      */
770     public void setNumRowsEnabled(boolean enabled) {
771         numRowsEnabled = enabled;
772     }
773 
774     /**
775      * @see #setNumRowsEnabled(boolean)
776 
777      */
778     public boolean isNumRowsEnabled() {
779         if (numRowsEnabled == null)
780             numRowsEnabled = !options.remove("-norows");
781 
782         return numRowsEnabled;
783     }
784 
785     /**
786      * If enabled we'll include views in the analysis.<p/>
787      *
788      * Defaults to <code>true</code> (enabled).
789      *
790      * @param enabled
791      */
792     public void setViewsEnabled(boolean enabled) {
793         viewsEnabled = enabled;
794     }
795 
796     /**
797      * @see #setViewsEnabled(boolean)
798 
799      */
800     public boolean isViewsEnabled() {
801         if (viewsEnabled == null)
802             viewsEnabled = !options.remove("-noviews");
803 
804         return viewsEnabled;
805     }
806 
807     /**
808      * Returns <code>true</code> if metering should be embedded in
809      * the generated pages.<p/>
810      * Defaults to <code>false</code> (disabled).
811 
812      */
813     public boolean isMeterEnabled() {
814         if (meterEnabled == null)
815             meterEnabled = options.remove("-meter");
816 
817         return meterEnabled;
818     }
819 
820     /**
821      * Set the columns to exclude from all relationship diagrams.
822      *
823      * @param columnExclusions regular expression of the columns to
824      *        exclude
825      */
826     public void setColumnExclusions(String columnExclusions) {
827         this.columnExclusions = Pattern.compile(columnExclusions);
828     }
829 
830     /**
831      * See {@link #setColumnExclusions(String)}
832 
833      */
834     public Pattern getColumnExclusions() {
835         if (columnExclusions == null) {
836             String strExclusions = pullParam("-X");
837             if (strExclusions == null)
838                 strExclusions = "[^.]";   // match nothing
839 
840             columnExclusions = Pattern.compile(strExclusions);
841         }
842 
843         return columnExclusions;
844     }
845 
846     /**
847      * Set the columns to exclude from relationship diagrams where the specified
848      * columns aren't directly referenced by the focal table.
849      *
850      * @param fullColumnExclusions regular expression of the columns to
851      *        exclude
852      */
853     public void setIndirectColumnExclusions(String fullColumnExclusions) {
854         indirectColumnExclusions = Pattern.compile(fullColumnExclusions);
855     }
856 
857     /**
858      * @see #setIndirectColumnExclusions(String)
859      *
860 
861      */
862     public Pattern getIndirectColumnExclusions() {
863         if (indirectColumnExclusions == null) {
864             String strExclusions = pullParam("-x");
865             if (strExclusions == null)
866                 strExclusions = "[^.]";   // match nothing
867 
868             indirectColumnExclusions = Pattern.compile(strExclusions);
869         }
870 
871         return indirectColumnExclusions;
872     }
873 
874     /**
875      * Set the tables to include as a regular expression
876      * @param tableInclusions
877      */
878     public void setTableInclusions(String tableInclusions) {
879         this.tableInclusions = Pattern.compile(tableInclusions);
880     }
881 
882     /**
883      * Get the regex {@link Pattern} for which tables to include in the analysis.
884      *
885 
886      */
887     public Pattern getTableInclusions() {
888         if (tableInclusions == null) {
889             String strInclusions = pullParam("-i");
890             if (strInclusions == null)
891                 strInclusions = ".*";     // match anything
892 
893             try {
894                 tableInclusions = Pattern.compile(strInclusions);
895             } catch (PatternSyntaxException badPattern) {
896                 throw new InvalidConfigurationException(badPattern).setParamName("-i");
897             }
898         }
899 
900         return tableInclusions;
901     }
902 
903     /**
904      * Set the tables to exclude as a regular expression
905      * @param tableExclusions
906      */
907     public void setTableExclusions(String tableExclusions) {
908         this.tableExclusions = Pattern.compile(tableExclusions);
909     }
910 
911     /**
912      * Get the regex {@link Pattern} for which tables to exclude from the analysis.
913      *
914 
915      */
916     public Pattern getTableExclusions() {
917         if (tableExclusions == null) {
918             String strExclusions = pullParam("-I");
919             if (strExclusions == null)
920                 strExclusions = "";  // match nothing
921 
922             try {
923                 tableExclusions = Pattern.compile(strExclusions);
924             } catch (PatternSyntaxException badPattern) {
925                 throw new InvalidConfigurationException(badPattern).setParamName("-I");
926             }
927         }
928 
929         return tableExclusions;
930     }
931 
932     /**
933 
934      */
935     public List<String> getSchemas() {
936         if (schemas == null) {
937             String tmp = pullParam("-schemas");
938             if (tmp == null)
939                 tmp = pullParam("-schemata");
940             if (tmp != null) {
941                 schemas = new ArrayList<String>();
942 
943                 for (String name : tmp.split("[ ,\"]"))
944                     schemas.add(name);
945 
946                 if (schemas.isEmpty())
947                     schemas = null;
948             }
949         }
950 
951         return schemas;
952     }
953 
954     /**
955      * Set the name of the {@link SqlFormatter SQL formatter} class to use to
956      * format SQL into HTML.<p/>
957      * The implementation of the class must be made available to the class
958      * loader, typically by specifying the path to its jar with <em>-dp</em>
959      * ({@link #setDriverPath(String)}).
960      */
961     public void setSqlFormatter(String formatterClassName) {
962         sqlFormatterClass = formatterClassName;
963         sqlFormatter = null;
964     }
965 
966     /**
967      * Set the {@link SqlFormatter SQL formatter} to use to format
968      * SQL into HTML.
969      */
970     public void setSqlFormatter(SqlFormatter sqlFormatter) {
971         this.sqlFormatter = sqlFormatter;
972         if (sqlFormatter != null)
973             sqlFormatterClass = sqlFormatter.getClass().getName();
974     }
975 
976     /**
977      * Returns an implementation of {@link SqlFormatter SQL formatter} to use to format
978      * SQL into HTML.  The default implementation is {@link DefaultSqlFormatter}.
979      *
980 
981      * @throws InvalidConfigurationException if unable to instantiate an instance
982      */
983     @SuppressWarnings("unchecked")
984     public SqlFormatter getSqlFormatter() throws InvalidConfigurationException {
985         if (sqlFormatter == null) {
986             if (sqlFormatterClass == null) {
987                 sqlFormatterClass = pullParam("-sqlFormatter");
988 
989                 if (sqlFormatterClass == null)
990                     sqlFormatterClass = DefaultSqlFormatter.class.getName();
991             }
992 
993             try {
994                 Class<SqlFormatter> clazz = (Class<SqlFormatter>)Class.forName(sqlFormatterClass);
995                 sqlFormatter = clazz.newInstance();
996             } catch (Exception exc) {
997                 throw new InvalidConfigurationException("Failed to initialize instance of SQL formatter: ", exc)
998                             .setParamName("-sqlFormatter");
999             }
1000         }
1001 
1002         return sqlFormatter;
1003     }
1004 
1005     public void setEvaluateAllEnabled(boolean enabled) {
1006         evaluteAll = enabled;
1007     }
1008 
1009     public boolean isEvaluateAllEnabled() {
1010         if (evaluteAll == null)
1011             evaluteAll = options.remove("-all");
1012         return evaluteAll;
1013     }
1014 
1015     /**
1016      * Returns true if we're evaluating a bunch of schemas in one go and
1017      * at this point we're evaluating a specific schema.
1018      *
1019  boolean
1020      */
1021     public boolean isOneOfMultipleSchemas() {
1022         // set by MultipleSchemaAnalyzer
1023         return Boolean.getBoolean("oneofmultipleschemas");
1024     }
1025 
1026     /**
1027      * When -all (evaluateAll) is specified then this is the regular
1028      * expression that determines which schemas to evaluate.
1029      *
1030      * @param schemaSpec
1031      */
1032     public void setSchemaSpec(String schemaSpec) {
1033         this.schemaSpec = schemaSpec;
1034     }
1035 
1036     public String getSchemaSpec() {
1037         if (schemaSpec == null)
1038             schemaSpec = pullParam("-schemaSpec");
1039 
1040         return schemaSpec;
1041     }
1042 
1043     /**
1044      * Set the renderer to use for the -Tpng[:renderer[:formatter]] dot option as specified
1045      * at <a href='http://www.graphviz.org/doc/info/command.html'>
1046      * http://www.graphviz.org/doc/info/command.html</a>.<p>
1047      * Note that the leading ":" is required while :formatter is optional.<p>
1048      * The default renderer is typically GD.<p>
1049      * Note that using {@link #setHighQuality(boolean)} is the preferred approach
1050      * over using this method.
1051      */
1052     public void setRenderer(String renderer) {
1053         Dot.getInstance().setRenderer(renderer);
1054     }
1055 
1056     /**
1057      * @see #setRenderer(String)
1058 
1059      */
1060     public String getRenderer() {
1061         String renderer = pullParam("-renderer");
1062         if (renderer != null)
1063             setRenderer(renderer);
1064 
1065         return Dot.getInstance().getRenderer();
1066     }
1067 
1068     /**
1069      * If <code>false</code> then generate output of "lower quality"
1070      * than the default.
1071      * Note that the default is intended to be "higher quality",
1072      * but various installations of Graphviz may have have different abilities.
1073      * That is, some might not have the "lower quality" libraries and others might
1074      * not have the "higher quality" libraries.<p>
1075      * Higher quality output takes longer to generate and results in significantly
1076      * larger image files (which take longer to download / display), but it generally
1077      * looks better.
1078      */
1079     public void setHighQuality(boolean highQuality) {
1080         this.highQuality = highQuality;
1081         lowQuality = !highQuality;
1082         Dot.getInstance().setHighQuality(highQuality);
1083     }
1084 
1085     /**
1086      * @see #setHighQuality(boolean)
1087      */
1088     public boolean isHighQuality() {
1089         if (highQuality == null) {
1090             highQuality = options.remove("-hq");
1091             if (highQuality) {
1092                 // use whatever is the default unless explicitly specified otherwise
1093                 Dot.getInstance().setHighQuality(highQuality);
1094             }
1095         }
1096 
1097         highQuality = Dot.getInstance().isHighQuality();
1098         return highQuality;
1099     }
1100 
1101     /**
1102      * @see #setHighQuality(boolean)
1103      */
1104     public boolean isLowQuality() {
1105         if (lowQuality == null) {
1106             lowQuality = options.remove("-lq");
1107             if (lowQuality) {
1108                 // use whatever is the default unless explicitly specified otherwise
1109                 Dot.getInstance().setHighQuality(!lowQuality);
1110             }
1111         }
1112 
1113         lowQuality = !Dot.getInstance().isHighQuality();
1114         return lowQuality;
1115     }
1116 
1117     /**
1118      * <code>true</code> if we should display advertisements.
1119      * Defaults to <code>true</code>.<p>
1120      * <b>Please do not disable ads unless absolutely necessary</b>.
1121      *
1122 
1123      */
1124     public void setAdsEnabled(boolean enabled) {
1125         adsEnabled = enabled;
1126     }
1127 
1128     /**
1129      * Returns <code>true</code> if we should display advertisements.<p>
1130      * <b>Please do not disable ads unless absolutely necessary</b>.
1131      *
1132      * @deprecated will be removed
1133      */
1134     @Deprecated
1135     public boolean isAdsEnabled() {
1136         if (adsEnabled == null) {
1137             adsEnabled = !options.remove("-noads");
1138         }
1139 
1140         return adsEnabled ;
1141     }
1142 
1143     /**
1144      * Set the level of logging to perform.<p/>
1145      * The levels in descending order are:
1146      * <ul>
1147      *  <li><code>severe</code> (highest - least detail)
1148      *  <li><code>warning</code> (default)
1149      *  <li><code>info</code>
1150      *  <li><code>config</code>
1151      *  <li><code>fine</code>
1152      *  <li><code>finer</code>
1153      *  <li><code>finest</code>  (lowest - most detail)
1154      * </ul>
1155      *
1156      * @param logLevel
1157      */
1158     public void setLogLevel(String logLevel) {
1159         if (logLevel == null) {
1160             this.logLevel = Level.WARNING;
1161             return;
1162         }
1163 
1164         Map<String, Level> levels = new LinkedHashMap<String, Level>();
1165         levels.put("severe", Level.SEVERE);
1166         levels.put("warning", Level.WARNING);
1167         levels.put("info", Level.INFO);
1168         levels.put("config", Level.CONFIG);
1169         levels.put("fine", Level.FINE);
1170         levels.put("finer", Level.FINER);
1171         levels.put("finest", Level.FINEST);
1172 
1173         this.logLevel = levels.get(logLevel.toLowerCase());
1174         if (this.logLevel == null) {
1175             throw new InvalidConfigurationException("Invalid logLevel: '" + logLevel +
1176                     "'. Must be one of: " + levels.keySet());
1177         }
1178     }
1179 
1180     /**
1181      * Returns the level of logging to perform.
1182      * See {@link #setLogLevel(String)}.
1183      *
1184 
1185      */
1186     public Level getLogLevel() {
1187         if (logLevel == null) {
1188             setLogLevel(pullParam("-loglevel"));
1189         }
1190 
1191         return logLevel;
1192     }
1193 
1194     /**
1195      * Returns <code>true</code> if the options indicate that the user wants
1196      * to see some help information.
1197      *
1198 
1199      */
1200     public boolean isHelpRequired() {
1201         return helpRequired;
1202     }
1203 
1204     public boolean isDbHelpRequired() {
1205         return dbHelpRequired;
1206     }
1207 
1208     public static String getLoadedFromJar() {
1209         String classpath = System.getProperty("java.class.path");
1210         //return new StringTokenizer(classpath, File.pathSeparator).nextToken();
1211       String originalPath = new StringTokenizer(classpath, File.pathSeparator).nextToken();
1212       String loadedFrom = null; 
1213 
1214       /*
1215        *Get the JAR file path from the URL. 
1216        *We canot simply use location.getPath() because the returned value is invalid in Windows
1217        * 
1218        *First we try to get a File from the URL, returning the canonical path.
1219        *If we cannot create a File from the URL we fall back to the original return value
1220        * 
1221        */
1222       URL location = Config.class.getProtectionDomain().getCodeSource().getLocation();
1223       File urlFile = null; 
1224       try
1225       {
1226         try
1227         {
1228           urlFile= new File(location.toURI()); 
1229         }
1230         catch (URISyntaxException use)
1231         {
1232           urlFile= new File(location.getPath()); 
1233         }
1234         loadedFrom=urlFile.getCanonicalPath();
1235       }
1236       catch (IOException ioe)
1237       {
1238 	//Fallback to the original path returned 
1239         loadedFrom=originalPath;
1240       }
1241       return loadedFrom ;
1242     }
1243 
1244     /**
1245      * @param type
1246 
1247      * @throws IOException
1248      * @throws InvalidConfigurationException if db properties are incorrectly formed
1249      */
1250     public Properties getDbProperties(String type) throws IOException, InvalidConfigurationException {
1251         ResourceBundle bundle = null;
1252 
1253         try {
1254             File propertiesFile = new File(type);
1255             bundle = new PropertyResourceBundle(new FileInputStream(propertiesFile));
1256             dbPropertiesLoadedFrom = propertiesFile.getAbsolutePath();
1257         } catch (FileNotFoundException notFoundOnFilesystemWithoutExtension) {
1258             try {
1259                 File propertiesFile = new File(type + ".properties");
1260                 bundle = new PropertyResourceBundle(new FileInputStream(propertiesFile));
1261                 dbPropertiesLoadedFrom = propertiesFile.getAbsolutePath();
1262             } catch (FileNotFoundException notFoundOnFilesystemWithExtensionTackedOn) {
1263                 try {
1264                     bundle = ResourceBundle.getBundle(type);
1265                     dbPropertiesLoadedFrom = "[" + getLoadedFromJar() + "]" + File.separator + type + ".properties";
1266                 } catch (Exception notInJarWithoutPath) {
1267                     try {
1268                         String path = TableOrderer.class.getPackage().getName() + ".dbTypes." + type;
1269                         path = path.replace('.', '/');
1270                         bundle = ResourceBundle.getBundle(path);
1271                         dbPropertiesLoadedFrom = "[" + getLoadedFromJar() + "]/" + path + ".properties";
1272                     } catch (Exception notInJar) {
1273                         notInJar.printStackTrace();
1274                         notFoundOnFilesystemWithExtensionTackedOn.printStackTrace();
1275                         throw notFoundOnFilesystemWithoutExtension;
1276                     }
1277                 }
1278             }
1279         }
1280 
1281         Properties props = asProperties(bundle);
1282         bundle = null;
1283         String saveLoadedFrom = dbPropertiesLoadedFrom; // keep original thru recursion
1284 
1285         // bring in key/values pointed to by the include directive
1286         // example: include.1=mysql::selectRowCountSql
1287         for (int i = 1; true; ++i) {
1288             String include = (String)props.remove("include." + i);
1289             if (include == null)
1290                 break;
1291 
1292             int separator = include.indexOf("::");
1293             if (separator == -1)
1294                 throw new InvalidConfigurationException("include directive in " + dbPropertiesLoadedFrom + " must have '::' between dbType and key");
1295 
1296             String refdType = include.substring(0, separator).trim();
1297             String refdKey = include.substring(separator + 2).trim();
1298 
1299             // recursively resolve the ref'd properties file and the ref'd key
1300             Properties refdProps = getDbProperties(refdType);
1301             props.put(refdKey, refdProps.getProperty(refdKey));
1302         }
1303 
1304         // bring in base properties files pointed to by the extends directive
1305         String baseDbType = (String)props.remove("extends");
1306         if (baseDbType != null) {
1307             baseDbType = baseDbType.trim();
1308             Properties baseProps = getDbProperties(baseDbType);
1309 
1310             // overlay our properties on top of the base's
1311             baseProps.putAll(props);
1312             props = baseProps;
1313         }
1314 
1315         // done with this level of recursion...restore original
1316         dbPropertiesLoadedFrom = saveLoadedFrom;
1317 
1318         return props;
1319     }
1320 
1321     protected String getDbPropertiesLoadedFrom() throws IOException {
1322         if (dbPropertiesLoadedFrom == null)
1323             getDbProperties(getDbType());
1324         return dbPropertiesLoadedFrom;
1325     }
1326 
1327     public List<String> getRemainingParameters()
1328     {
1329         try {
1330             populate();
1331         } catch (IllegalArgumentException exc) {
1332             throw new InvalidConfigurationException(exc);
1333         } catch (IllegalAccessException exc) {
1334             throw new InvalidConfigurationException(exc);
1335         } catch (InvocationTargetException exc) {
1336             if (exc.getCause() instanceof InvalidConfigurationException)
1337                 throw (InvalidConfigurationException)exc.getCause();
1338             throw new InvalidConfigurationException(exc.getCause());
1339         } catch (IntrospectionException exc) {
1340             throw new InvalidConfigurationException(exc);
1341         }
1342 
1343         return options;
1344     }
1345 
1346     /**
1347      * Options that are specific to a type of database.  E.g. things like <code>host</code>,
1348      * <code>port</code> or <code>db</code>, but <b>don't</b> have a setter in this class.
1349      *
1350      * @param dbSpecificOptions
1351      */
1352     public void setDbSpecificOptions(Map<String, String> dbSpecificOptions) {
1353         this.dbSpecificOptions = dbSpecificOptions;
1354         originalDbSpecificOptions = new HashMap<String, String>(dbSpecificOptions);
1355     }
1356 
1357     public Map<String, String> getDbSpecificOptions() {
1358         if (dbSpecificOptions ==  null)
1359             dbSpecificOptions = new HashMap<String, String>();
1360         return dbSpecificOptions;
1361     }
1362 
1363     /**
1364      * Returns a {@link Properties} populated with the contents of <code>bundle</code>
1365      *
1366      * @param bundle ResourceBundle
1367  Properties
1368      */
1369     public static Properties asProperties(ResourceBundle bundle) {
1370         Properties props = new Properties();
1371         Enumeration<String> iter = bundle.getKeys();
1372         while (iter.hasMoreElements()) {
1373             String key = iter.nextElement();
1374             props.put(key, bundle.getObject(key));
1375         }
1376 
1377         return props;
1378     }
1379 
1380     /**
1381      * 'Pull' the specified parameter from the collection of options. Returns
1382      * null if the parameter isn't in the list and removes it if it is.
1383      *
1384      * @param paramId
1385 
1386      */
1387     private String pullParam(String paramId) {
1388         return pullParam(paramId, false, false);
1389     }
1390 
1391     private String pullRequiredParam(String paramId) {
1392         return pullParam(paramId, true, false);
1393     }
1394 
1395     /**
1396      * @param paramId
1397      * @param required
1398      * @param dbTypeSpecific
1399 
1400      * @throws MissingRequiredParameterException
1401      */
1402     private String pullParam(String paramId, boolean required, boolean dbTypeSpecific)
1403                                 throws MissingRequiredParameterException {
1404         int paramIndex = options.indexOf(paramId);
1405         if (paramIndex < 0) {
1406             if (required)
1407                 throw new MissingRequiredParameterException(paramId, dbTypeSpecific);
1408             return null;
1409         }
1410         options.remove(paramIndex);
1411         String param = options.get(paramIndex).toString();
1412         options.remove(paramIndex);
1413         return param;
1414     }
1415 
1416     /**
1417      * Thrown to indicate that a required parameter is missing
1418      */
1419     public static class MissingRequiredParameterException extends RuntimeException {
1420         private static final long serialVersionUID = 1L;
1421         private final boolean dbTypeSpecific;
1422 
1423         public MissingRequiredParameterException(String paramId, boolean dbTypeSpecific) {
1424             this(paramId, null, dbTypeSpecific);
1425         }
1426 
1427         public MissingRequiredParameterException(String paramId, String description, boolean dbTypeSpecific) {
1428             super("Required parameter '" + paramId + "' " +
1429                     (description == null ? "" : "(" + description + ") ") +
1430                     "was not specified." +
1431                     (dbTypeSpecific ? "  It is required for this database type." : ""));
1432             this.dbTypeSpecific = dbTypeSpecific;
1433         }
1434 
1435         public boolean isDbTypeSpecific() {
1436             return dbTypeSpecific;
1437         }
1438     }
1439 
1440     /**
1441      * Allow an equal sign in args...like "-o=foo.bar". Useful for things like
1442      * Ant and Maven.
1443      *
1444      * @param args
1445      *            List
1446  List
1447      */
1448     protected List<String> fixupArgs(List<String> args) {
1449         List<String> expandedArgs = new ArrayList<String>();
1450 
1451         for (String arg : args) {
1452             int indexOfEquals = arg.indexOf('=');
1453             if (indexOfEquals != -1 && indexOfEquals -1 != arg.indexOf(ESCAPED_EQUALS)) {
1454                 expandedArgs.add(arg.substring(0, indexOfEquals));
1455                 expandedArgs.add(arg.substring(indexOfEquals + 1));
1456             } else {
1457                 expandedArgs.add(arg);
1458             }
1459         }
1460 
1461         // some OSes/JVMs do filename expansion with runtime.exec() and some don't,
1462         // so MultipleSchemaAnalyzer has to surround params with double quotes...
1463         // strip them here for the OSes/JVMs that don't do anything with the params
1464         List<String> unquotedArgs = new ArrayList<String>();
1465 
1466         for (String arg : expandedArgs) {
1467             if (arg.startsWith("\"") && arg.endsWith("\""))  // ".*" becomes .*
1468                 arg = arg.substring(1, arg.length() - 1);
1469             unquotedArgs.add(arg);
1470         }
1471 
1472         return unquotedArgs;
1473     }
1474 
1475     /**
1476      * Call all the getters to populate all the lazy initialized stuff.
1477      *
1478      * @throws InvocationTargetException
1479      * @throws IllegalAccessException
1480      * @throws IllegalArgumentException
1481      * @throws IntrospectionException
1482      */
1483     private void populate() throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, IntrospectionException {
1484         if (!populating) { // prevent recursion
1485             populating = true;
1486 
1487             BeanInfo beanInfo = Introspector.getBeanInfo(Config.class);
1488             PropertyDescriptor[] props = beanInfo.getPropertyDescriptors();
1489             for (int i = 0; i < props.length; ++i) {
1490                 Method readMethod = props[i].getReadMethod();
1491                 if (readMethod != null)
1492                     readMethod.invoke(this, (Object[])null);
1493             }
1494 
1495             populating = false;
1496         }
1497     }
1498 
1499     public static Set<String> getBuiltInDatabaseTypes(String loadedFromJar) {
1500         Set<String> databaseTypes = new TreeSet<String>();
1501         JarInputStream jar = null;
1502 
1503         try {
1504             jar = new JarInputStream(new FileInputStream(loadedFromJar));
1505             JarEntry entry;
1506 
1507             while ((entry = jar.getNextJarEntry()) != null) {
1508                 String entryName = entry.getName();
1509                 int dotPropsIndex = entryName.indexOf(".properties");
1510                 if (dotPropsIndex != -1)
1511                     databaseTypes.add(entryName.substring(0, dotPropsIndex));
1512             }
1513         } catch (IOException exc) {
1514         } finally {
1515             if (jar != null) {
1516                 try {
1517                     jar.close();
1518                 } catch (IOException ignore) {}
1519             }
1520         }
1521 
1522         return databaseTypes;
1523     }
1524 
1525     protected void dumpUsage(String errorMessage, boolean detailedDb) {
1526         if (errorMessage != null) {
1527             System.out.flush();
1528             System.err.println("*** " + errorMessage + " ***");
1529         } else {
1530             System.out.println("SchemaSpy generates an HTML representation of a database schema's relationships.");
1531         }
1532 
1533         System.err.flush();
1534         System.out.println();
1535 
1536         if (!detailedDb) {
1537             System.out.println("Usage:");
1538             System.out.println(" java -jar " + getLoadedFromJar() + " [options]");
1539             System.out.println("   -t databaseType       type of database - defaults to ora");
1540             System.out.println("                           use -dbhelp for a list of built-in types");
1541             System.out.println("   -u user               connect to the database with this user id");
1542             System.out.println("   -s schema             defaults to the specified user");
1543             System.out.println("   -p password           defaults to no password");
1544             System.out.println("   -o outputDirectory    directory to place the generated output in");
1545             System.out.println("   -dp pathToDrivers     optional - looks for JDBC drivers here before looking");
1546             System.out.println("                           in driverPath in [databaseType].properties.");
1547             System.out.println("Go to http://schemaspy.sourceforge.net for a complete list/description");
1548             System.out.println(" of additional parameters.");
1549             System.out.println();
1550         }
1551 
1552         if (detailedDb) {
1553             System.out.println("Built-in database types and their required connection parameters:");
1554             for (String type : getBuiltInDatabaseTypes(getLoadedFromJar())) {
1555                 new DbSpecificConfig(type).dumpUsage();
1556             }
1557             System.out.println();
1558         }
1559 
1560         if (detailedDb) {
1561             System.out.println("You can use your own database types by specifying the filespec of a .properties file with -t.");
1562             System.out.println("Grab one out of " + getLoadedFromJar() + " and modify it to suit your needs.");
1563             System.out.println();
1564         }
1565 
1566         System.out.println("Sample usage using the default database type (implied -t ora):");
1567         System.out.println(" java -jar schemaSpy.jar -db mydb -s myschema -u devuser -p password -o output");
1568         System.out.println();
1569         System.out.flush();
1570     }
1571 
1572     /**
1573      * Get the value of the specified parameter.
1574      * Used for properties that are common to most db's, but aren't required.
1575      *
1576      * @param paramName
1577 
1578      */
1579     public String getParam(String paramName) {
1580         try {
1581             BeanInfo beanInfo = Introspector.getBeanInfo(Config.class);
1582             PropertyDescriptor[] props = beanInfo.getPropertyDescriptors();
1583             for (int i = 0; i < props.length; ++i) {
1584                 PropertyDescriptor prop = props[i];
1585                 if (prop.getName().equalsIgnoreCase(paramName)) {
1586                     Object result = prop.getReadMethod().invoke(this, (Object[])null);
1587                     return result == null ? null : result.toString();
1588                 }
1589             }
1590         } catch (Exception failed) {
1591             failed.printStackTrace();
1592         }
1593 
1594         return null;
1595     }
1596 
1597     /**
1598      * Return all of the configuration options as a List of Strings, with
1599      * each parameter and its value as a separate element.
1600      *
1601 
1602      * @throws IOException
1603      */
1604     public List<String> asList() throws IOException {
1605         List<String> params = new ArrayList<String>();
1606 
1607         if (originalDbSpecificOptions != null) {
1608             for (String key : originalDbSpecificOptions.keySet()) {
1609                 String value = originalDbSpecificOptions.get(key);
1610                 if (!key.startsWith("-"))
1611                     key = "-" + key;
1612                 params.add(key);
1613                 params.add(value);
1614             }
1615         }
1616         if (isEncodeCommentsEnabled())
1617             params.add("-ahic");
1618         if (isEvaluateAllEnabled())
1619             params.add("-all");
1620         if (!isHtmlGenerationEnabled())
1621             params.add("-nohtml");
1622         if (!isImpliedConstraintsEnabled())
1623             params.add("-noimplied");
1624         if (!isLogoEnabled())
1625             params.add("-nologo");
1626         if (isMeterEnabled())
1627             params.add("-meter");
1628         if (!isNumRowsEnabled())
1629             params.add("-norows");
1630         if (!isViewsEnabled())
1631             params.add("-noviews");
1632         if (isRankDirBugEnabled())
1633             params.add("-rankdirbug");
1634         if (isRailsEnabled())
1635             params.add("-rails");
1636         if (isSingleSignOn())
1637             params.add("-sso");
1638         if (!isAdsEnabled())
1639             params.add("-noads");
1640         if (isSchemaDisabled())
1641             params.add("-noschema");
1642 
1643         String value = getDriverPath();
1644         if (value != null) {
1645             params.add("-dp");
1646             params.add(value);
1647         }
1648         params.add("-css");
1649         params.add(getCss());
1650         params.add("-charset");
1651         params.add(getCharset());
1652         params.add("-font");
1653         params.add(getFont());
1654         params.add("-fontsize");
1655         params.add(String.valueOf(getFontSize()));
1656         params.add("-t");
1657         params.add(getDbType());
1658         params.add("-renderer");  // instead of -hq and/or -lq
1659         params.add(getRenderer());
1660         value = getDescription();
1661         if (value != null) {
1662             params.add("-desc");
1663             params.add(value);
1664         }
1665         value = getPassword();
1666         if (value != null) {
1667             params.add("-p");
1668             params.add(value);
1669         }
1670         if (isPromptForPasswordEnabled())
1671             params.add("-pfp");
1672         value = getSchema();
1673         if (value != null) {
1674             params.add("-s");
1675             params.add(value);
1676         }
1677         value = getUser();
1678         if (value != null) {
1679             params.add("-u");
1680             params.add(value);
1681         }
1682         value = getConnectionPropertiesFile();
1683         if (value != null) {
1684             params.add("-connprops");
1685             params.add(value);
1686         } else {
1687             Properties props = getConnectionProperties();
1688             if (!props.isEmpty() ) {
1689                 params.add("-connprops");
1690                 StringBuilder buf = new StringBuilder();
1691                 for (Entry<Object, Object> entry : props.entrySet()) {
1692                     buf.append(entry.getKey());
1693                     buf.append(ESCAPED_EQUALS);
1694                     buf.append(entry.getValue());
1695                     buf.append(';');
1696                 }
1697                 params.add(buf.toString());
1698             }
1699         }
1700         value = getDb();
1701         if (value != null) {
1702             params.add("-db");
1703             params.add(value);
1704         }
1705         value = getHost();
1706         if (value != null) {
1707             params.add("-host");
1708             params.add(value);
1709         }
1710         if (getPort() != null) {
1711             params.add("-port");
1712             params.add(getPort().toString());
1713         }
1714         value = getServer();
1715         if (value != null) {
1716             params.add("-server");
1717             params.add(value);
1718         }
1719         value = getMeta();
1720         if (value != null) {
1721             params.add("-meta");
1722             params.add(value);
1723         }
1724         if (getGraphvizDir() != null) {
1725             params.add("-gv");
1726             params.add(getGraphvizDir().toString());
1727         }
1728         params.add("-loglevel");
1729         params.add(getLogLevel().toString().toLowerCase());
1730         params.add("-sqlFormatter");
1731         params.add(getSqlFormatter().getClass().getName());
1732         params.add("-i");
1733         params.add(getTableInclusions().pattern());
1734         params.add("-I");
1735         params.add(getTableExclusions().pattern());
1736         params.add("-x");
1737         params.add(getColumnExclusions().pattern());
1738         params.add("-X");
1739         params.add(getIndirectColumnExclusions().pattern());
1740         params.add("-dbthreads");
1741         params.add(String.valueOf(getMaxDbThreads()));
1742         params.add("-maxdet");
1743         params.add(String.valueOf(getMaxDetailedTables()));
1744         params.add("-o");
1745         params.add(getOutputDir().toString());
1746 
1747         return params;
1748     }
1749 }