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