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

Last change on this file since 1899 was 1355, checked in by pharms, 10 years ago
  • improved logging
File size: 14.2 KB
Line 
1//   Copyright 2012 Georg-August-Universität Göttingen, Germany
2//
3//   Licensed under the Apache License, Version 2.0 (the "License");
4//   you may not use this file except in compliance with the License.
5//   You may obtain a copy of the License at
6//
7//       http://www.apache.org/licenses/LICENSE-2.0
8//
9//   Unless required by applicable law or agreed to in writing, software
10//   distributed under the License is distributed on an "AS IS" BASIS,
11//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//   See the License for the specific language governing permissions and
13//   limitations under the License.
14
15
16package de.ugoe.cs.util.console;
17
18import java.io.File;
19import java.io.FileInputStream;
20import java.io.FilenameFilter;
21import java.io.IOException;
22import java.net.URL;
23import java.util.ArrayList;
24import java.util.Arrays;
25import java.util.Comparator;
26import java.util.Enumeration;
27import java.util.List;
28import java.util.SortedSet;
29import java.util.TreeSet;
30import java.util.jar.JarEntry;
31import java.util.jar.JarInputStream;
32import java.util.logging.Level;
33
34import de.ugoe.cs.util.StringTools;
35
36/**
37 * <p>
38 * Executes commands. The commands have to implement the {@link Command} interface and be in
39 * packages registered using addCommandPackage(). Additionally, default commands are implemented in
40 * the de.ugoe.cs.util.console.defaultcommands package.
41 * </p>
42 * <p>
43 * This class is implemented as a <i>Singleton</i>.
44 * </p>
45 *
46 * @author Steffen Herbold
47 * @version 1.0
48 */
49public class CommandExecuter {
50
51    /**
52     * <p>
53     * Handle of the CommandExecuter instance.
54     * </p>
55     */
56    private final static CommandExecuter theInstance = new CommandExecuter();
57
58    /**
59     * <p>
60     * Prefix of all command classes.
61     * </p>
62     */
63    private static final String cmdPrefix = "CMD";
64
65    /**
66     * <p>
67     * Name of the package for default commands.
68     * </p>
69     */
70    private static final String defaultPackage = "de.ugoe.cs.util.console.defaultcommands";
71
72    /**
73     * <p>
74     * List of packages in which commands may be defined. The exec methods trys to load command from
75     * these packages in the order they have been added.
76     * </p>
77     * <p>
78     * The de.ugoe.cs.util.console.defaultcommands package has always lowest priority, unless it is
79     * specifically added.
80     * </p>
81     */
82    private List<String> commandPackageList;
83   
84    /**
85     * <p>
86     * the list of available commands (lazy instantiation in the method
87     * {@link #getAvailableCommands()})
88     * <p>
89     */
90    private Command[] availableCommands;
91
92    /**
93     * <p>
94     * Returns the instance of CommandExecuter. If no instances exists yet, a new one is created.
95     * </p>
96     *
97     * @return the instance of CommandExecuter
98     */
99    public static synchronized CommandExecuter getInstance() {
100        return theInstance;
101    }
102
103    /**
104     * <p>
105     * Creates a new CommandExecuter. Private to prevent multiple instances (Singleton).
106     * </p>
107     */
108    private CommandExecuter() {
109        commandPackageList = new ArrayList<String>();
110    }
111
112    /**
113     * <p>
114     * Adds a package that will be used by {@link #exec(String)} to load command from.
115     * </p>
116     *
117     * @param pkg
118     *            package where commands are located
119     * @throws IllegalArgumentException
120     *             thrown if the package name is null or empty string
121     */
122    public void addCommandPackage(String pkg) {
123        if ("".equals(pkg) || pkg == null) {
124            throw new IllegalArgumentException("package name must not be null or empty string");
125        }
126        commandPackageList.add(pkg);
127    }
128
129    /**
130     * <p>
131     * Executes the command defined by string. A command has the following form (mix of EBNF and
132     * natural language):
133     * </p>
134     * <code>
135     * &lt;command&gt; := &lt;commandname&gt;&lt;whitespace&gt;{&lt;parameter&gt;}<br>
136     * &lt;commandname&gt; := String without whitespaces. Has to be a valid Java class name<br>
137     * &lt;parameter&gt; := &lt;string&gt;|&lt;stringarray&gt;<br>
138     * &lt;string&gt; := &lt;stringwithoutwhitespaces&gt;|&lt;stringwithwhitespaces&gt;
139     * &lt;stringwithoutwhitespaces&gt; := a string without whitespaces<br>
140     * &lt;stringwithoutwhitespaces&gt; := a string, that can have whitespaces, but must be in double quotes<br>
141     * &lt;stringarray&gt; := "["&lt;string&gt;{&lt;whitespace&gt;&lt;string&gt;"]"
142     * </code>
143     *
144     * @param command
145     *            the command as a string
146     */
147    public void exec(String command) {
148        Console.commandNotification(command);
149        Command cmd = null;
150        CommandParser parser = new CommandParser();
151        parser.parse(command);
152        for (int i = 0; cmd == null && i < commandPackageList.size(); i++) {
153            cmd = loadCMD(commandPackageList.get(i) + "." + cmdPrefix + parser.getCommandName());
154        }
155        if (cmd == null) { // check if command is available as default command
156            cmd = loadCMD(defaultPackage + "." + cmdPrefix + parser.getCommandName());
157        }
158        if (cmd == null) {
159            Console.println("Unknown command");
160        }
161        else {
162            try {
163                cmd.run(parser.getParameters());
164            }
165            catch (IllegalArgumentException e) {
166                Console.println("invalid parameter provided: " + e.getMessage());
167                Console.println("Usage: " + cmd.help());
168            }
169            catch (Exception e) {
170                Console.println("error executing command: " + e);
171                Console.logException(e);
172                Console.println("Usage: " + cmd.help());
173            }
174        }
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 loadCMD(String className) {
188        Command cmd = null;
189        try {
190            Class<?> cmdClass = Class.forName(className);
191            cmd = (Command) cmdClass.newInstance();
192        }
193        catch (NoClassDefFoundError e) {
194            String[] splitResult = e.getMessage().split("CMD");
195            String correctName = splitResult[splitResult.length - 1].replace(")", "");
196            Console.println("Did you mean " + correctName + "?");
197        }
198        catch (ClassNotFoundException e) {}
199        catch (IllegalAccessException e) {}
200        catch (InstantiationException e) {}
201        catch (ClassCastException e) {
202            Console.traceln(Level.WARNING, className + "found, but does not implement Command");
203        }
204        return cmd;
205    }
206   
207    /**
208     * <p>
209     * Helper method that loads a class and tries to cast it to {@link Command}.
210     * </p>
211     *
212     * @param className
213     *            qualified name of the class (including package name)
214     * @return if class is available and implement {@link Command} and instance of the class, null
215     *         otherwise
216     */
217    public Command getCMD(String commandName) {
218        Command cmd = null;
219        for (int i = 0; cmd == null && i < commandPackageList.size(); i++) {
220            cmd = loadCMD(commandPackageList.get(i) + "." + cmdPrefix + commandName);
221        }
222        if (cmd == null) { // check if command is available as default command
223            cmd = loadCMD(defaultPackage + "." + cmdPrefix + commandName);
224        }
225        return cmd;
226    }
227
228    /**
229     * <p>
230     * reads all available commands from the registered command packages and returns a list of their
231     * names
232     * </p>
233     *
234     * @return an array containing the names of the available commands.
235     */
236    public Command[] getAvailableCommands() {
237        if (availableCommands == null) {
238            List<Command> commands = new ArrayList<Command>();
239            List<String> packages = new ArrayList<String>();
240            packages.addAll(commandPackageList);
241            packages.add(defaultPackage);
242
243            FilenameFilter filter = new FilenameFilter() {
244                @Override
245                public boolean accept(File dir, String name) {
246                    return
247                        (name != null) && (name.startsWith(cmdPrefix)) && (name.endsWith(".class"));
248                }
249            };
250
251            SortedSet<String> classNames = new TreeSet<String>(new Comparator<String>() {
252                @Override
253                public int compare(String arg1, String arg2) {
254                    String str1 = arg1.substring(arg1.lastIndexOf('.') + cmdPrefix.length() + 1);
255                    String str2 = arg2.substring(arg2.lastIndexOf('.') + cmdPrefix.length() + 1);
256                    return str1.compareTo(str2);
257                }
258
259            });
260
261            for (String packageName : packages) {
262                String path = packageName.replace('.', '/');
263                try {
264                    Enumeration<URL> resources = ClassLoader.getSystemResources(path);
265
266                    while (resources.hasMoreElements()) {
267                        URL resource = resources.nextElement();
268                        File packageDir = new File(resource.getFile());
269
270                        if (packageDir.isDirectory()) {
271                            for (File classFile : packageDir.listFiles(filter)) {
272                                String className = classFile.getName().substring
273                                    (0, classFile.getName().lastIndexOf('.'));
274                                classNames.add(packageName + "." + className);
275                            }
276                        }
277                        else {
278                            int index = resource.getFile().lastIndexOf('!');
279                            if ((index > 0) && (resource.getFile().startsWith("file:")) &&
280                                (resource.getFile().endsWith("!/" + path)))
281                            {
282                                String jarFile =
283                                    resource.getFile().substring("file:".length(), index);
284
285                                // we have to read the package content from a jar file
286                                JarInputStream jarInputStream = null;
287                                try {
288                                    jarInputStream =
289                                        new JarInputStream(new FileInputStream(jarFile));
290                                    JarEntry entry = null;
291                                    do {
292                                        entry = jarInputStream.getNextJarEntry();
293                                        if ((entry != null) && (!entry.isDirectory()) &&
294                                                (entry.getName().startsWith(path)))
295                                        {
296                                            String className = entry.getName().substring
297                                                (path.length() + 1, entry.getName().lastIndexOf('.'));
298                                            classNames.add(packageName + "." + className);
299                                        }
300                                    }
301                                    while (entry != null);
302                                }
303                                catch (Exception e) {
304                                    e.printStackTrace();
305                                    Console.traceln(Level.WARNING, "could not read contents of " +
306                                                    "jar " + jarFile);
307                                }
308                                finally {
309                                    if (jarInputStream != null) {
310                                        jarInputStream.close();
311                                    }
312                                }
313
314                            }
315                        }
316                    }
317                }
318                catch (IOException e) {
319                    Console.traceln
320                        (Level.WARNING, "could not read commands of package " + packageName);
321                }
322            }
323
324            for (String className : classNames) {
325                // class may still be inner classes. Therefore load the command, to
326                // see if it is really available and a command.
327                Command cmd = loadCMD(className);
328                if (cmd != null) {
329                    commands.add(cmd);
330                }
331            }
332
333            availableCommands = commands.toArray(new Command[commands.size()]);
334        }
335       
336        return Arrays.copyOf(availableCommands, availableCommands.length);
337    }
338   
339    /**
340     * <p>
341     * Get a copy of the currently registered command packages.
342     * </p>
343     *
344     * @return currently registered command packages
345     */
346    public List<String> getCommandPackages() {
347        List<String> commandPackageListCopy = new ArrayList<String>(commandPackageList);
348        commandPackageListCopy.add(0, defaultPackage);
349        return commandPackageListCopy;
350    }
351
352    /**
353     * <p>
354     * this method method performs an auto completion of the provided String as far as possible
355     * regarding the available commands. It auto completes to the full command name, if only
356     * one command matches the given prefix. It auto completes to the common denominator, if
357     * several commands match the prefix
358     * </p>
359     *
360     * @param commandPrefix the prefix to be auto completed
361     *
362     * @return as described
363     */
364    public String autoCompleteCommand(String commandPrefix) {
365        Command[] commands = getAvailableCommands();
366       
367        String[] completions = new String[commands.length];
368       
369        for (int i = 0; i < commands.length; i++) {
370            completions[i] = commands[i].getClass().getSimpleName().substring(3);
371        }
372       
373        return StringTools.autocomplete(commandPrefix, completions);
374    }
375}
Note: See TracBrowser for help on using the repository browser.