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

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