001    
002    /*
003     * Copyright (C) 2011 Archie L. Cobbs. All rights reserved.
004     *
005     * $Id: MainClass.java 59 2011-03-09 16:16:29Z archie.cobbs $
006     */
007    
008    package org.dellroad.stuff.main;
009    
010    import java.io.EOFException;
011    import java.io.File;
012    import java.util.ArrayList;
013    
014    import org.gnu.readline.Readline;
015    import org.gnu.readline.ReadlineLibrary;
016    import org.slf4j.Logger;
017    import org.slf4j.LoggerFactory;
018    
019    /**
020     * Support superclass for command line classes.
021     */
022    public abstract class MainClass {
023    
024        protected final Logger log = LoggerFactory.getLogger(getClass());
025    
026        protected MainClass() {
027        }
028    
029        /**
030         * Subclass main implementation. This method is free to throw exceptions; these will
031         * be displayed on standard error and converted into non-zero exit values.
032         *
033         * @return exit value
034         */
035        public abstract int run(String[] args) throws Exception;
036    
037        /**
038         * Enter command loop. Commands are read using GNU libreadline and handed off
039         * to {@link #handleCommand} for processing.
040         */
041        protected void commandLoop(String appName, String prompt) throws Exception {
042    
043            // Setup readline
044            try {
045                Readline.load(ReadlineLibrary.GnuReadline);
046            } catch (UnsatisfiedLinkError e) {
047                // ignore
048            }
049            Readline.initReadline(appName);
050            Readline.setThrowExceptionOnUnsupportedMethod(false);
051    
052            // Read init file(s)
053            ArrayList<String> files = new ArrayList<String>(3);
054            String var = System.getenv("INPUTRC");
055            if (var != null)
056                files.add(var);
057            String home = System.getProperty("user.home");
058            if (home != null)
059                files.add(new File(new File(home), ".inputrc").getAbsolutePath());
060            files.add("/etc/.inputrc");
061            for (String file : files) {
062                try {
063                    Readline.readInitFile(file);
064                } catch (Exception e) {
065                    // ignore
066                }
067            }
068    
069            // Read history file
070            String historyFile = new File(new File(System.getProperty("user.home")), "." + appName + "_history").getAbsolutePath();
071            try {
072                Readline.readHistoryFile(historyFile);
073            } catch (Exception e) {
074                // ignore
075            }
076    
077            // Main loop
078            try {
079                while (true) {
080    
081                    // Read next line
082                    String line;
083                    try {
084                        line = Readline.readline(prompt);
085                    } catch (EOFException e) {
086                        break;
087                    }
088    
089                    // No input?
090                    if (line == null)
091                        continue;
092    
093                    // Update history
094                    Readline.addToHistory(line);
095    
096                    // Execute line
097                    if (!this.handleCommand(line))
098                        break;
099                }
100            } finally {
101    
102                // Save history
103                try {
104                    Readline.writeHistoryFile(historyFile);
105                } catch (Exception e) {
106                    // ignore
107                }
108    
109                // Clean up
110                Readline.cleanup();
111            }
112        }
113    
114        /**
115         * Callback used by {@link #commandLoop}.
116         *
117         * <p>
118         * The implementation in {@link MainClass} just returns {@code false}.
119         *
120         * @return true to continue reading the next command, false to exit
121         */
122        protected boolean handleCommand(String line) throws Exception {
123            return false;
124        }
125    
126        /**
127         * Display the usage message to standard error.
128         */
129        protected abstract void usageMessage();
130    
131        /**
132         * Print the usage message and exit with exit value 1.
133         */
134        protected void usageError() {
135            usageMessage();
136            System.exit(1);
137        }
138    
139        /**
140         * Emit an error message an exit with exit value 1.
141         */
142        protected final void errout(String message) {
143            System.err.println(getClass().getSimpleName() + ": " + message);
144            System.exit(1);
145        }
146    
147        /**
148         * Parse command line flags of the form {@code -Dname=value} and set the corresponding system properties.
149         * Parsing stops at the first argument not starting with a dash (or {@code --}).
150         *
151         * @return command line with all the property-setting flags removed
152         */
153        protected String[] parsePropertyFlags(String[] args) {
154            ArrayList<String> list = new ArrayList<String>(args.length);
155            boolean done = false;
156            for (String arg : args) {
157                if (done) {
158                    list.add(arg);
159                    continue;
160                }
161                if (arg.equals("--") || arg.length() == 0 || arg.charAt(0) != '-') {
162                    list.add(arg);
163                    done = true;
164                    continue;
165                }
166                if (arg.startsWith("-D")) {
167                    int eq = arg.indexOf('=');
168                    if (eq < 3)
169                        usageError();
170                    System.setProperty(arg.substring(2, eq), arg.substring(eq + 1));
171                    continue;
172                }
173                list.add(arg);
174            }
175            return list.toArray(new String[list.size()]);
176        }
177    
178        /**
179         * Invokes {@link #run}, catching any exceptions thrown and exiting with a non-zero
180         * value if and only if an exception was caught.
181         * <p/>
182         * <p>
183         * The concrete class' {@code main()} method should invoke this method.
184         * </p>
185         */
186        protected void doMain(String[] args) {
187            int exitValue = 1;
188            try {
189                exitValue = run(args);
190            } catch (Throwable t) {
191                t.printStackTrace(System.err);
192            } finally {
193                System.exit(exitValue);
194            }
195        }
196    }
197