source: trunk/java-utils/src/main/java/de/ugoe/cs/util/console/CommandExecuter.java @ 718

Last change on this file since 718 was 718, checked in by sherbold, 12 years ago
  • added command man that prints manuals of commands to the console
  • added manuals für the commands exec, exit, listCommands, man, and trainMarkovModel
File size: 11.1 KB
Line 
1
2package de.ugoe.cs.util.console;
3
4import java.io.File;
5import java.io.FileInputStream;
6import java.io.FilenameFilter;
7import java.io.IOException;
8import java.net.URL;
9import java.security.InvalidParameterException;
10import java.util.ArrayList;
11import java.util.Comparator;
12import java.util.Enumeration;
13import java.util.List;
14import java.util.SortedSet;
15import java.util.TreeSet;
16import java.util.jar.JarEntry;
17import java.util.jar.JarInputStream;
18import java.util.logging.Level;
19
20/**
21 * <p>
22 * Executes commands. The commands have to implement the {@link Command} interface and be in
23 * packages registered using addCommandPackage(). Additionally, default commands are implemented in
24 * the de.ugoe.cs.util.console.defaultcommands package.
25 * </p>
26 * <p>
27 * This class is implemented as a <i>Singleton</i>.
28 * </p>
29 *
30 * @author Steffen Herbold
31 * @version 1.0
32 */
33public class CommandExecuter {
34
35    /**
36     * <p>
37     * Handle of the CommandExecuter instance.
38     * </p>
39     */
40    private final static CommandExecuter theInstance = new CommandExecuter();
41
42    /**
43     * <p>
44     * Prefix of all command classes.
45     * </p>
46     */
47    private static final String cmdPrefix = "CMD";
48
49    /**
50     * <p>
51     * Name of the package for default commands.
52     * </p>
53     */
54    private static final String defaultPackage = "de.ugoe.cs.util.console.defaultcommands";
55
56    /**
57     * <p>
58     * List of packages in which commands may be defined. The exec methods trys to load command from
59     * these packages in the order they have been added.
60     * </p>
61     * <p>
62     * The de.ugoe.cs.util.console.defaultcommands package has always lowest priority, unless it is
63     * specifically added.
64     * </p>
65     */
66    private List<String> commandPackageList;
67
68    /**
69     * <p>
70     * Returns the instance of CommandExecuter. If no instances exists yet, a new one is created.
71     * </p>
72     *
73     * @return the instance of CommandExecuter
74     */
75    public static synchronized CommandExecuter getInstance() {
76        return theInstance;
77    }
78
79    /**
80     * <p>
81     * Creates a new CommandExecuter. Private to prevent multiple instances (Singleton).
82     * </p>
83     */
84    private CommandExecuter() {
85        commandPackageList = new ArrayList<String>();
86    }
87
88    /**
89     * <p>
90     * Adds a package that will be used by {@link #exec(String)} to load command from.
91     * </p>
92     *
93     * @param pkg
94     *            package where commands are located
95     * @throws InvalidParameterException
96     *             thrown if the package name is null or empty string
97     */
98    public void addCommandPackage(String pkg) {
99        if ("".equals(pkg) || pkg == null) {
100            throw new InvalidParameterException("package name must not be null or empty string");
101        }
102        commandPackageList.add(pkg);
103    }
104
105    /**
106     * <p>
107     * Executes the command defined by string. A command has the following form (mix of EBNF and
108     * natural language):
109     * </p>
110     * <code>
111     * &lt;command&gt; := &lt;commandname&gt;&lt;whitespace&gt;{&lt;parameter&gt;}<br>
112     * &lt;commandname&gt; := String without whitespaces. Has to be a valid Java class name<br>
113     * &lt;parameter&gt; := &lt;string&gt;|&lt;stringarray&gt;<br>
114     * &lt;string&gt; := &lt;stringwithoutwhitespaces&gt;|&lt;stringwithwhitespaces&gt;
115     * &lt;stringwithoutwhitespaces&gt; := a string without whitespaces<br>
116     * &lt;stringwithoutwhitespaces&gt; := a string, that can have whitespaces, but must be in double quotes<br>
117     * &lt;stringarray&gt; := "["&lt;string&gt;{&lt;whitespace&gt;&lt;string&gt;"]"
118     * </code>
119     *
120     * @param command
121     *            the command as a string
122     */
123    public void exec(String command) {
124        Console.commandNotification(command);
125        Command cmd = null;
126        CommandParser parser = new CommandParser();
127        parser.parse(command);
128        for (int i = 0; cmd == null && i < commandPackageList.size(); i++) {
129            cmd = loadCMD(commandPackageList.get(i) + "." + cmdPrefix + parser.getCommandName());
130        }
131        if (cmd == null) { // check if command is available as default command
132            cmd = loadCMD(defaultPackage + "." + cmdPrefix + parser.getCommandName());
133        }
134        if (cmd == null) {
135            Console.println("Unknown command");
136        }
137        else {
138            try {
139                cmd.run(parser.getParameters());
140            }
141            catch (InvalidParameterException e) {
142                Console.println("Usage: " + cmd.help());
143            }
144        }
145    }
146
147    /**
148     * <p>
149     * Helper method that loads a class and tries to cast it to {@link Command}.
150     * </p>
151     *
152     * @param className
153     *            qualified name of the class (including package name)
154     * @return if class is available and implement {@link Command} and instance of the class, null
155     *         otherwise
156     */
157    public Command loadCMD(String className) {
158        Command cmd = null;
159        try {
160            Class<?> cmdClass = Class.forName(className);
161            cmd = (Command) cmdClass.newInstance();
162        }
163        catch (NoClassDefFoundError e) {
164            String[] splitResult = e.getMessage().split("CMD");
165            String correctName = splitResult[splitResult.length - 1].replace(")", "");
166            Console.println("Did you mean " + correctName + "?");
167        }
168        catch (ClassNotFoundException e) {}
169        catch (IllegalAccessException e) {}
170        catch (InstantiationException e) {}
171        catch (ClassCastException e) {
172            Console.traceln(Level.WARNING, className + "found, but does not implement Command");
173        }
174        return cmd;
175    }
176   
177    /**
178     * <p>
179     * Helper method that loads a class and tries to cast it to {@link Command}.
180     * </p>
181     *
182     * @param className
183     *            qualified name of the class (including package name)
184     * @return if class is available and implement {@link Command} and instance of the class, null
185     *         otherwise
186     */
187    public Command getCMD(String commandName) {
188        Command cmd = null;
189        for (int i = 0; cmd == null && i < commandPackageList.size(); i++) {
190            cmd = loadCMD(commandPackageList.get(i) + "." + cmdPrefix + commandName);
191        }
192        if (cmd == null) { // check if command is available as default command
193            cmd = loadCMD(defaultPackage + "." + cmdPrefix + commandName);
194        }
195        return cmd;
196    }
197
198    /**
199     * <p>
200     * reads all available commands from the registered command packages and returns a list of their
201     * names
202     * </p>
203     *
204     * @return an array containing the names of the available commands.
205     */
206    public Command[] getAvailableCommands() {
207        List<Command> commands = new ArrayList<Command>();
208        List<String> packages = new ArrayList<String>();
209        packages.addAll(commandPackageList);
210        packages.add(defaultPackage);
211
212        FilenameFilter filter = new FilenameFilter() {
213            @Override
214            public boolean accept(File dir, String name) {
215                return (name != null) && (name.startsWith(cmdPrefix)) && (name.endsWith(".class"));
216            }
217        };
218
219        SortedSet<String> classNames = new TreeSet<String>(new Comparator<String>() {
220            @Override
221            public int compare(String arg1, String arg2) {
222                String str1 = arg1.substring(arg1.lastIndexOf('.') + cmdPrefix.length() + 1);
223                String str2 = arg2.substring(arg2.lastIndexOf('.') + cmdPrefix.length() + 1);
224                return str1.compareTo(str2);
225            }
226
227        });
228
229        for (String packageName : packages) {
230            String path = packageName.replace('.', '/');
231            try {
232                Enumeration<URL> resources = ClassLoader.getSystemResources(path);
233
234                while (resources.hasMoreElements()) {
235                    URL resource = resources.nextElement();
236                    File packageDir = new File(resource.getFile());
237
238                    if (packageDir.isDirectory()) {
239                        for (File classFile : packageDir.listFiles(filter)) {
240                            String className =
241                                classFile.getName().substring(0,
242                                                              classFile.getName().lastIndexOf('.'));
243                            classNames.add(packageName + "." + className);
244                        }
245                    }
246                    else {
247                        resource.getFile().startsWith("file:");
248                        int index = resource.getFile().lastIndexOf('!');
249                        if ((index > 0) && (resource.getFile().startsWith("file:")) &&
250                            (resource.getFile().endsWith("!/" + path)))
251                        {
252                            String jarFile = resource.getFile().substring("file:".length(), index);
253
254                            // we have to read the package content from a jar file
255                            JarInputStream jarInputStream = null;
256                            try {
257                                jarInputStream = new JarInputStream(new FileInputStream(jarFile));
258                            }
259                            catch (Exception e) {
260                                e.printStackTrace();
261                                Console.traceln(Level.WARNING, "could not read contents of jar " +
262                                    jarFile);
263                            }
264
265                            JarEntry entry = null;
266                            do {
267                                entry = jarInputStream.getNextJarEntry();
268                                if ((entry != null) && (!entry.isDirectory()) &&
269                                    (entry.getName().startsWith(path)))
270                                {
271                                    String className =
272                                        entry.getName().substring(path.length(),
273                                                                  entry.getName().lastIndexOf('.'));
274                                    classNames.add(packageName + "." + className);
275                                }
276                            }
277                            while (entry != null);
278                        }
279                    }
280                }
281            }
282            catch (IOException e) {
283                Console.traceln(Level.WARNING, "could not read commands of package " + packageName);
284            }
285        }
286
287        for (String className : classNames) {
288            // class may still be inner classes. Therefore load the command, to
289            // see if it is really available and a command.
290            Command cmd = loadCMD(className);
291            if (cmd != null) {
292                commands.add(cmd);
293            }
294        }
295
296        Command[] commandArray = commands.toArray(new Command[commands.size()]);
297        return commandArray;
298    }
299}
Note: See TracBrowser for help on using the repository browser.