001    /**
002     * Copyright (c) 2010, SIB. All rights reserved.
003     * 
004     * SIB (Swiss Institute of Bioinformatics) - http://www.isb-sib.ch Host -
005     * https://sourceforge.net/projects/javaprotlib/
006     * 
007     * Redistribution and use in source and binary forms, with or without
008     * modification, are permitted provided that the following conditions are met:
009     * Redistributions of source code must retain the above copyright notice, this
010     * list of conditions and the following disclaimer. Redistributions in binary
011     * form must reproduce the above copyright notice, this list of conditions and
012     * the following disclaimer in the documentation and/or other materials provided
013     * with the distribution. Neither the name of the SIB/GENEBIO nor the names of
014     * its contributors may be used to endorse or promote products derived from this
015     * software without specific prior written permission.
016     * 
017     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
018     * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
019     * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
020     * ARE DISCLAIMED. IN NO EVENT SHALL SIB/GENEBIO BE LIABLE FOR ANY DIRECT,
021     * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
022     * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
023     * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
024     * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
025     * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
026     * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
027     */
028    package org.expasy.jpl.commons.app;
029    
030    
031    import java.io.File;
032    import java.io.FileInputStream;
033    import java.io.IOException;
034    import java.util.Iterator;
035    import java.util.List;
036    import java.util.Properties;
037    import org.apache.commons.cli.CommandLine;
038    import org.apache.commons.cli.CommandLineParser;
039    import org.apache.commons.cli.HelpFormatter;
040    import org.apache.commons.cli.Option;
041    import org.apache.commons.cli.Options;
042    import org.apache.commons.cli.ParseException;
043    import org.apache.commons.cli.PosixParser;
044    
045    
046    /**
047     * A basic command line parameter handler.
048     * 
049     * @author nikitin
050     * 
051     * @version 1.0
052     */
053    public abstract class AbstractApplicationParameters {
054            
055            /********************* the parameters **************************/
056            protected Properties settings;
057            
058            /** the file with all settings */
059            private File configFile;
060            
061            /** display settings */
062            private boolean isVerbose;
063            
064            /** version */
065            private String version;
066            
067            /** author, copyright... */
068            private String header;
069            
070            /** usage */
071            protected final HelpFormatter usage = new HelpFormatter();
072            
073            /** the command line */
074            private String commandLine;
075            
076            private final Class<?> app;
077            
078            public AbstractApplicationParameters(final Class<?> app,
079                final String version, String mandatoryParams, final String[] params) {
080                    
081                    this.app = app;
082                    this.version = version;
083                    
084                    // the command line
085                    this.commandLine = app.getSimpleName() + " " + mandatoryParams;
086                    
087                    // make initialisations
088                    init();
089                    
090                    // set the default application settings
091                    settings = createBaseSettings();
092                    addSettings(settings);
093                    
094                    // define base options like -h, -i, -q and -v
095                    Options basicOptions = createBaseOptions();
096                    
097                    // application specific options
098                    Options appOptions = createAppOptions();
099                    
100                    // merge basic and app options
101                    Options options = mergeOptions(basicOptions, appOptions);
102                    
103                    // update settings given the command line
104                    try {
105                            parseCommandLine(params, options);
106                    } catch (ParseException e) {
107                            System.err.println(e.getMessage());
108                            usage.printHelp(commandLine, options, true);
109                            System.exit(1);
110                    }
111            }
112            
113            @SuppressWarnings("unchecked")
114            private Options mergeOptions(Options baseOptions, Options appOptions) {
115                    
116                    Options options = new Options();
117                    
118                    Iterator iter1 = baseOptions.getOptions().iterator();
119                    while (iter1.hasNext()) {
120                            options.addOption((Option) iter1.next());
121                    }
122                    
123                    if (appOptions != null) {
124                            Iterator iter2 = appOptions.getOptions().iterator();
125                            while (iter2.hasNext()) {
126                                    Option o = (Option) iter2.next();
127                                    String name = o.getOpt();
128                                    String lname = o.getLongOpt();
129                                    
130                                    if (name != null && options.hasOption(name)) {
131                                            System.err.println("Base option '-" + name
132                                                + "' already exists in application, please choose "
133                                                + "another short name option.");
134                                            System.exit(2);
135                                    }
136                                    
137                                    if (lname != null && options.hasOption(lname)) {
138                                            System.err.println("Base option '--" + name
139                                                + "' already exists in application, please choose "
140                                                + "another long name option.");
141                                            System.exit(2);
142                                    }
143                                    
144                                    options.addOption(o);
145                            }
146                    }
147                    return options;
148            }
149            
150            private Options createBaseOptions() {
151                    Options options = new Options();
152                    
153                    options.addOption("h", "help", false, "print this message.");
154                    
155                    options.addOption("v", "version", false, "print the version info.");
156                    
157                    options.addOption("q", "quiet", false, "quiet mode (verbose off)\n"
158                        + "by default: false.");
159                    
160                    options.addOption("i", "setting-file", true,
161                        "give a property file with all input settings.");
162                    
163                    return options;
164            }
165            
166            public Properties createBaseSettings() {
167                    Properties settings = new Properties();
168                    
169                    settings.setProperty("quiet", "false");
170                    
171                    return settings;
172            }
173            
174            /**
175             * Parse command line and update the settings.
176             * 
177             * @param args the command line app arguments.
178             * @param settings the stored/active app params.
179             * @param options the app options.
180             */
181            @SuppressWarnings("unchecked")
182            private void parseCommandLine(String[] args, Options options)
183                throws ParseException {
184                    
185                    // create the command line parser
186                    CommandLineParser parser = null;
187                    
188                    parser = new PosixParser();
189                    
190                    // try {
191                    // parse the command line arguments
192                    final CommandLine line = parser.parse(options, args);
193                    
194                    /** help */
195                    if (line.hasOption("h")) {
196                            usage.printHelp(commandLine, options, true);
197                            System.exit(0);
198                    }
199                    
200                    /** version */
201                    if (line.hasOption("v")) {
202                            System.out.println(app.getSimpleName() + " v." + version);
203                            System.exit(0);
204                    }
205                    
206                    /** setting property file */
207                    if (line.hasOption("i")) {
208                            final String filename = line.getOptionValue("i");
209                            configFile = new File(filename);
210                            
211                            if (configFile.exists()) {
212                                    try {
213                                            // load a properties file
214                                            settings.load(new FileInputStream(configFile));
215                                    } catch (final IOException e) {
216                                            throw new ParseException(e.getMessage()
217                                                + ": cannot load properties from " + filename);
218                                    }
219                                    
220                            } else {
221                                    throw new ParseException(filename
222                                        + ": no such settings file name");
223                            }
224                    }
225                    
226                    /** verbose mode ? */
227                    if (line.hasOption("quiet")) {
228                            settings.setProperty("quiet", "true");
229                    }
230                    
231                    // parse the specific app options
232                    parseSpecificOptions(options, settings, line);
233                    
234                    // convert non string values to correct types
235                    setPropertyValues();
236                    
237                    parseArguments(line.getArgList());
238            }
239            
240            /**
241             * This method handles all setting/conversions of parameter values to the
242             * correct type.
243             * 
244             * <ol>
245             * <li>all subclassses will have to override this method</li>
246             * <li>do not forget to call super.convertPropertyValues() to correctly
247             * convert the base params</li>
248             * </ol>
249             * 
250             * @throws ParseException if convertion failed.
251             */
252            protected void setPropertyValues() throws ParseException {
253                    /** verbosity */
254                    try {
255                            isVerbose = !Boolean.parseBoolean(settings.getProperty("quiet"));
256                    } catch (NumberFormatException e) {
257                            new ParseException(e.getMessage());
258                    }
259            }
260            
261            public String getParamInfos() {
262                    StringBuilder sb = new StringBuilder();
263                    
264                    if (header != null) {
265                            sb.append(header + "\n");
266                    }
267                    
268                    if (configFile != null) {
269                            sb.append("[Settings extracted from file " + configFile.getName()
270                                + "]\n\n");
271                    }
272                    
273                    sb.append(getSpecificParamInfos());
274                    
275                    return sb.toString();
276            }
277            
278            public abstract void init();
279            
280            public abstract void addSettings(Properties settings);
281            
282            public abstract String getSpecificParamInfos();
283            
284            // TODO: force implementation
285            // public abstract void setHeader(String header);
286            
287            /** add the app specific options */
288            public abstract Options createAppOptions();
289            
290            public abstract void parseSpecificOptions(Options options,
291                Properties settings, CommandLine line) throws ParseException;
292            
293            public abstract void parseArguments(List<String> argList)
294                throws org.apache.commons.cli.ParseException;
295            
296            public final String getCommandLine() {
297                    return commandLine;
298            }
299            
300            /**
301             * @return the version
302             */
303            public final String getVersion() {
304                    return version;
305            }
306            
307            public final void setHeader(String header) {
308                    this.header = header;
309            }
310            
311            /**
312             * @return the configFile
313             */
314            public final File getConfigFile() {
315                    return configFile;
316            }
317            
318            /**
319             * @return the true if verbose
320             */
321            public final boolean isVerbose() {
322                    return isVerbose;
323            }
324    }