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

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