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

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