File: AbstractCommandLineMode.java

package info (click to toggle)
libgoby-java 3.3.1%2Bdfsg2-12
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 58,108 kB
  • sloc: java: 78,105; cpp: 5,011; xml: 3,170; python: 2,108; sh: 1,575; ansic: 277; makefile: 114
file content (479 lines) | stat: -rw-r--r-- 19,481 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
/*
 * Copyright (C) 2009-2011 Institute for Computational Biomedicine,
 *                    Weill Medical College of Cornell University
 *
 *  This file is part of the Goby IO API.
 *
 *     The Goby IO API is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU Lesser General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     The Goby IO API is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU Lesser General Public License for more details.
 *
 *     You should have received a copy of the GNU Lesser General Public License
 *     along with the Goby IO API.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.campagnelab.goby.modes;

import com.martiansoftware.jsap.*;
import org.campagnelab.goby.util.dynoptions.DynamicOptionRegistry;
import org.apache.commons.lang.*;

import java.io.IOException;
import java.io.PrintStream;
import java.util.Iterator;
import java.util.Map;

/**
 * Base abstract class for Goby modes.
 *
 * @author Kevin Dorff
 */
public abstract class AbstractCommandLineMode {
    private String jarFilename;

    protected AbstractCommandLineMode(final String jarFilename) {
        setJarFilename(jarFilename);
    }

    public String getJarFilename() {
        return jarFilename;
    }

    public void setJarFilename(final String jarFilename) {
        this.jarFilename = jarFilename;
    }

    /**
     * Returns the mode name defined by subclasses.
     *
     * @return The name of the mode
     */
    public abstract String getModeName();

    /**
     * The short version of the mode name. This default implementation is based on modeName, split by "-",
     * keeping only the first character of each split, such as "compact-file-stats" becomes "cfs".
     * This allows a short mode name for specifying goby modes on the command line, so the following
     * two command line options are synomymous:
     * goby -jar goby.jar --mode compact-file-stats
     * goby -jar goby.jar --mode cfs
     * The Goby command line parser will not accept duplicate short mode names, so if two modes return
     * the same shortModeName, the command line parser will not offer/accept a short mode name for these modes.
     * In this case, it is recommended that you create overriding getShortModeName() methods for all of these modes.
     *
     * @return The short name of the mode
     */
    public String getShortModeName() {
        final String[] shortModeNameParts = getModeName().split("-");
        final StringBuilder shortModeNameBuilder = new StringBuilder(shortModeNameParts.length);
        for (final String shortModeNamePart : shortModeNameParts) {
            shortModeNameBuilder.append(shortModeNamePart.charAt(0));
        }
        return shortModeNameBuilder.toString();
    }

    /**
     * Returns the mode description defined by subclasses.
     *
     * @return A description of the mode
     */
    public abstract String getModeDescription();

    /**
     * Returns true when the id represents a help option.
     *
     * @param id The id to check
     * @return true if the id is a known help option.
     */
    private boolean isJSAPHelpId(final String id) {
        return "help".equals(id) || "wikihelp".equals(id) || "htmlhelp".equals(id)|| "help-dynamic".equals(id);
    }

    /**
     * Print usage for the mode.
     *
     * @param jsap the configured JSAP object
     */
    public void printUsage(final JSAP jsap) {
        final String modeName = getModeName();
        final String modeDescription = getModeDescription();
        final Parameter modeId = jsap.getByID("mode");
        if (modeId != null) {
            jsap.unregisterParameter(modeId);
        }
        System.err.println("Usage: ");
        System.err.println("java -jar " + jarFilename
                + " (-m|--mode) " + modeName + " " + jsap.getUsage());
        System.err.println();
        System.err.println("Description: ");
        System.err.println(WordUtils.wrap(modeDescription, JSAP.DEFAULT_SCREENWIDTH - 1));
        System.err.println();
        System.err.println("Options: ");
          System.err.println(jsap.getHelp(JSAP.DEFAULT_SCREENWIDTH - 1));
    }

    /**
     * Print the usage for a mode in the format appropriate for a
     * <a href="http://www.wikimedia.org/">Wikimedia</a> table.
     *
     * @param jsap The parameters for the mode
     */
    public void printUsageAsWikiTable(final JSAP jsap) {
        final PrintStream stream = System.out;

        // Table definition/header
        stream.println("{| class=\"wikitable\" style=\"margin: 1em auto 1em auto; background:#efefef;\" valign=\"top\" align=\"center\" border=\"1\" cellpadding=\"5\" width=\"80%\"");
        stream.println("|- valign=\"bottom\" align=\"left\" style=\"background:#ffdead;\"");
        stream.println("!width=\"15%\"| Flag");
        stream.println("!width=\"10%\" | Arguments");
        stream.println("!width=\"5%\"| Required");
        stream.println("! Description");
        stream.println();

        // iterate through help entries for the mode
        final IDMap idMap = jsap.getIDMap();
        final Iterator iterator = idMap.idIterator();
        while (iterator.hasNext()) {
            final String id = (String) iterator.next();
            if (!"mode".equals(id) && !isJSAPHelpId(id)) { // skip the mode and help ids
                final Parameter parameter = jsap.getByID(id);
                stream.println("|- valign=\"top\" align=\"left\"");
                stream.print("| <nowiki>");
                if (parameter instanceof Flagged) {
                    final Flagged flagged = (Flagged) parameter;
                    final Character characterFlag = flagged.getShortFlagCharacter();
                    final String longFlag = flagged.getLongFlag();
                    if (characterFlag != null && StringUtils.isNotBlank(longFlag)) {
                        stream.print("(-" + characterFlag + "|--" + longFlag + ")");
                    } else if (characterFlag != null) {
                        stream.print("-" + characterFlag);
                    } else if (StringUtils.isNotBlank(longFlag)) {
                        stream.print("--" + longFlag);
                    }
                } else {
                    stream.print("n/a");
                }
                stream.println("</nowiki>");
                stream.print("| ");
                if (parameter instanceof Switch) {
                    stream.println("n/a");
                } else {
                    stream.println(id);
                }

                final boolean required;
                if (parameter instanceof Option) {
                    final Option option = (Option) parameter;
                    required = option.required();
                } else {
                    required = !(parameter instanceof Switch);
                }
                stream.println("| " + BooleanUtils.toStringYesNo(required));
                stream.println("| " + parameter.getHelp());
                if (parameter.getDefault() != null) {
                    stream.print("Default value: ");
                    for (final String defaultValue : parameter.getDefault()) {
                        stream.print(" ");
                        stream.print(defaultValue);
                    }
                }
                stream.println();
            }
        }

        // table close
        stream.println("|}");
    }

    /**
     * Print the usage for a mode in the format appropriate for a table in HTML.
     *
     * @param jsap The parameters for the mode
     */
    public void printUsageAsHtmlTable(final JSAP jsap) {
        final PrintStream writer = System.out;

        // Table definition/header
        writer.println("<table style=\"background: #efefef;\" border=\"1\" cellpadding=\"5\">");
        writer.println("<tbody>");
        writer.println("<tr style=\"background: #ffdead;\" align=\"left\" valign=\"bottom\">");
        writer.println("<th style=\"width: 15%\">Flag</th>");
        writer.println("<th style=\"width: 10%\">Arguments</th>");
        writer.println("<th style=\"width: 5%\">Required</th>");
        writer.println("<th>Description</th>");
        writer.println("</tr>");

        // iterate through help entries for the mode
        final IDMap idMap = jsap.getIDMap();
        final Iterator iterator = idMap.idIterator();
        while (iterator.hasNext()) {
            final String id = (String) iterator.next();
            if (!"mode".equals(id) && !isJSAPHelpId(id)) { // skip the mode and help ids
                final Parameter parameter = jsap.getByID(id);
                writer.println("<tr align=\"left\" valign=\"top\">");
                writer.print("<td><code>");
                if (parameter instanceof Flagged) {
                    final Flagged flagged = (Flagged) parameter;
                    final Character characterFlag = flagged.getShortFlagCharacter();
                    final String longFlag = flagged.getLongFlag();
                    if (characterFlag != null && StringUtils.isNotBlank(longFlag)) {
                        writer.print("(-" + characterFlag + "|--" + longFlag + ")");
                    } else if (characterFlag != null) {
                        writer.print("-" + characterFlag);
                    } else if (StringUtils.isNotBlank(longFlag)) {
                        writer.print("--" + longFlag);
                    }
                } else {
                    writer.print("n/a");
                }
                writer.println("</code></td>");
                writer.print("<td>");
                if (parameter instanceof Switch) {
                    writer.print("n/a");
                } else {
                    writer.print(id);
                }
                writer.println("</td>");
                final boolean required;
                if (parameter instanceof Option) {
                    final Option option = (Option) parameter;
                    required = option.required();
                } else {
                    required = !(parameter instanceof Switch);
                }
                writer.println("<td>" + BooleanUtils.toStringYesNo(required) + "</td>");
                writer.print("<td>");

                // convert any strange characters to html codes
                final String htmlHelpString = StringEscapeUtils.escapeHtml(parameter.getHelp());
                // and also convert "-" to the hyphen character code
                writer.print(StringUtils.replace(htmlHelpString, "-", "&#45;"));
                if (parameter.getDefault() != null) {
                    writer.print(" Default value: ");
                    for (final String defaultValue : parameter.getDefault()) {
                        writer.print(" ");
                        writer.print(StringUtils.replace(defaultValue, "-", "&#45;"));
                    }
                }
                writer.println("</td>");
                writer.println("</tr>");
            }
        }

        // table close
        writer.println("</tbody>");
        writer.println("</table>");
    }

    /**
     * Load JSAP from resource with no modifications to help / defaults.
     *
     * @return the configured JSAP object
     * @throws IOException   error reading or configuring
     * @throws JSAPException error reading or configuring
     */
    public JSAP loadJsapFromResource() throws IOException, JSAPException {
        return loadJsapFromResource(null);
    }

    /**
     * Load JSAP XML configuration for the current class. With potential
     * modifications to help / defaults stored in the helpValues map.
     *
     * @param helpValues a map of values to replace in the jsap help with other values
     * @return the configured JSAP object
     * @throws IOException   error reading or configuring
     * @throws JSAPException error reading or configuring
     */
    @SuppressWarnings("unchecked")
    public JSAP loadJsapFromResource(final Map<String, String> helpValues) throws IOException, JSAPException {
        final Class thisClass = this.getClass();
        final String className = ClassUtils.getShortCanonicalName(thisClass);
        JSAP jsap;
        try {
            jsap = new JSAP(thisClass.getResource(className + ".jsap"));
            reformatHelp(jsap);


        } catch (NullPointerException e) {  // NOPMD
            // No JSAP for "this", no additional args for the specific driver
            jsap = new JSAP();
        }

        // add options from driver to mode specific JSAP:
        final JSAP jsapSource = new JSAP(GenericToolsDriver.class.getResource(
                ClassUtils.getShortCanonicalName(GenericToolsDriver.class) + ".jsap"));
        final Iterator<String> idsIt = jsapSource.getIDMap().idIterator();
        while (idsIt.hasNext()) {
            final String id = idsIt.next();
            // remove parameters if they already exist in the destination.
            // This makes sure we use the source parameters, such as mode, help,
            // which contain variables to be replaced.
            if (jsap.getByID(id) != null) {
                jsap.unregisterParameter(jsap.getByID(id));
            }
            jsap.registerParameter(jsapSource.getByID(id));

        }
        if (helpValues == null) {
            return jsap;
        }
        final IDMap idMap = jsap.getIDMap();
        final Iterator<String> idIterator = idMap.idIterator();

        while (idIterator.hasNext()) {
            final String id = idIterator.next();
            final Parameter param = jsap.getByID(id);
            final String help = param.getHelp();
            final String[] defaults = param.getDefault();
            for (final Map.Entry<String, String> entry : helpValues.entrySet()) {
                // replace values in help
                if (help.contains(entry.getKey())) {
                    param.setHelp(StringUtils.replace(help, entry.getKey(), entry.getValue()));
                }
                // replace values in defaults
                if (defaults != null) {
                    for (int i = 0; i < defaults.length; i++) {
                        if (defaults[i].contains(entry.getKey())) {
                            defaults[i] = StringUtils.replace(
                                    defaults[i], entry.getKey(), entry.getValue());
                        }
                    }
                }

            }
        }

        return jsap;
    }

    private void reformatHelp(JSAP jsap) {
        final Iterator<String> idIterator = jsap.getIDMap().idIterator();
        while (idIterator.hasNext()) {
            final String id = idIterator.next();
            final Parameter param = jsap.getByID(id);
            final String help = param.getHelp();
              // remove new lines so that justification works best:

                final String replaced = help.replaceAll("[ ]*\n[ ]+", " ");
                param.setHelp(replaced);
        }
    }


    /**
     * Parse the JSAP to JSAPResult. Handle if "help" was requested.
     *
     * @param jsap the parser to use when parsing arguments
     * @param args the arguments to parse
     * @return the JSAPResult
     */
    public JSAPResult parseJsap(final JSAP jsap, final String[] args) {
        final JSAPResult jsapResult = jsap.parse(args);
        if (getModeName() != null) {
            if (jsap.getByID("help") != null && jsapResult.getBoolean("help")) {
                printUsage(jsap);
                System.exit(1);
            } if (jsap.getByID("help-dynamic") != null && jsapResult.getBoolean("help-dynamic")) {
                printDynamicHelpOptions(jsap);
                System.exit(1);
            }
            else if (jsap.getByID("htmlhelp") != null && jsapResult.getBoolean("htmlhelp")) {
                printUsageAsHtmlTable(jsap);
                System.exit(2);
            } else if (jsap.getByID("wikihelp") != null && jsapResult.getBoolean("wikihelp")) {
                printUsageAsWikiTable(jsap);
                System.exit(3);
            }
        }
        return jsapResult;
    }

    protected void printDynamicHelpOptions(JSAP jsap) {
        DynamicOptionRegistry.printHelp();
    }

    /**
     * Configure the mode via command line arguments.
     *
     * @param args command line arguments
     * @return this object for chaining
     * @throws IOException   error configuring
     * @throws JSAPException error configuring
     */
    public abstract AbstractCommandLineMode configure(final String[] args)
            throws IOException, JSAPException;

    /**
     * Execute the mode.
     *
     * @throws IOException io error
     */
    public abstract void execute() throws IOException;

    /**
     * Parse the JSAP arguments defined for the mode. Different arguments can be defined for
     * each mode.
     *
     * @param args       command line arguments.
     * @param helpValues A map of option names to help strings
     * @return Parsed arguments.
     * @throws IOException   if there was a problem configuring the parser
     * @throws JSAPException if there was a problem parsing the args
     * @see #loadJsapFromResource()
     * @see #loadJsapFromResource(java.util.Map)
     */
    protected JSAPResult parseJsapArguments(final String[] args, final Map<String, String> helpValues)
            throws IOException, JSAPException {
        final JSAP jsap = loadJsapFromResource(helpValues);
        final JSAPResult jsapResult = parseJsap(jsap, args);
        abortOnError(jsap, jsapResult);
        return jsapResult;
    }

    /**
     * Parse the JSAP arguments defined for the mode. Different arguments can be defined for
     * each mode.
     *
     * @param args command line arguments.
     * @return Parsed arguments.
     * @throws IOException   if there was a problem parsing the help text
     * @throws JSAPException if there was a problem parsing the args
     * @see #loadJsapFromResource()
     * @see #loadJsapFromResource(java.util.Map)
     */
    protected JSAPResult parseJsapArguments(final String[] args) throws IOException, JSAPException {
        final JSAP jsap = loadJsapFromResource();
        final JSAPResult jsapResult = parseJsap(jsap, args);
        abortOnError(jsap, jsapResult);
        return jsapResult;
    }

    /**
     * Print out specific error messages describing the problems
     * with the command line, THEN print usage, THEN print full
     * help.  This is called "beating the user with a clue stick."
     *
     * @param jsap       The argument parser
     * @param jsapResult The results of the parse
     */
    private void abortOnError(final JSAP jsap, final JSAPResult jsapResult) {
        if (!jsapResult.success()) {
            System.err.println();

            printUsage(jsap);
            System.err.println("One or more errors were encountered parsing the command line. See usage above to correct these errors:");
            for (final Iterator errs = jsapResult.getErrorMessageIterator(); errs.hasNext(); ) {
                System.err.println("Error: " + errs.next());
            }
            System.err.println();
            System.exit(1);
        }
    }
}