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

Last change on this file since 665 was 665, checked in by sherbold, 12 years ago
  • code clean-up
File size: 10.7 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.Collections;
12import java.util.Comparator;
13import java.util.Enumeration;
14import java.util.List;
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 InvalidParameterException
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 InvalidParameterException("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 (InvalidParameterException 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    private 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     * reads all available commands from the registered command packages and returns a list of
179     * their names
180     * </p>
181     *
182     * @return an array containing the names of the available commands.
183     */
184    public Command[] getAvailableCommands() {
185        List<Command> commands = new ArrayList<Command>();
186        List<String> packages = new ArrayList<String>();
187        packages.addAll(commandPackageList);
188        packages.add(defaultPackage);
189       
190        FilenameFilter filter = new FilenameFilter() {
191            @Override
192            public boolean accept(File dir, String name) {
193                return (name != null) && (name.startsWith(cmdPrefix)) && (name.endsWith(".class"));
194            }
195        };
196       
197        List<String> classNames = new ArrayList<String>();
198        for (String packageName : packages) {
199            String path = packageName.replace('.', '/');
200            try {
201                Enumeration<URL> resources = ClassLoader.getSystemResources(path);
202               
203                while (resources.hasMoreElements()) {
204                    URL resource = resources.nextElement();
205                    File packageDir = new File(resource.getFile());
206                   
207                    if (packageDir.isDirectory()) {
208                        for (File classFile : packageDir.listFiles(filter)) {
209                            String className = classFile.getName().substring
210                                (0, classFile.getName().lastIndexOf('.'));
211                            classNames.add(packageName + "." + className);
212                        }
213                    }
214                    else {
215                        resource.getFile().startsWith("file:");
216                        int index = resource.getFile().lastIndexOf('!');
217                        if ((index > 0) && (resource.getFile().startsWith("file:")) &&
218                            (resource.getFile().endsWith("!/" + path)))
219                        {
220                            String jarFile = resource.getFile().substring("file:".length(), index);
221                           
222                            // we have to read the package content from a jar file
223                            JarInputStream jarInputStream = null;
224                            try {
225                                jarInputStream = new JarInputStream(new FileInputStream(jarFile));
226                            }
227                            catch (Exception e) {
228                                e.printStackTrace();
229                                Console.traceln
230                                    (Level.WARNING, "could not read contents of jar " + jarFile);
231                            }
232
233                            JarEntry entry = null;
234                            do {
235                                entry = jarInputStream.getNextJarEntry();
236                                if ((entry != null) && (!entry.isDirectory()) &&
237                                    (entry.getName().startsWith(path)))
238                                {
239                                    String className = entry.getName().substring
240                                        (path.length(), entry.getName().lastIndexOf('.'));
241                                    classNames.add(packageName + "." + className);
242                                }
243                            }
244                            while (entry != null);
245                        }
246                    }
247                }
248            }
249            catch (IOException e) {
250                Console.traceln(Level.WARNING, "could not read commands of package " + packageName);
251            }
252        }
253       
254        Collections.sort(classNames, new Comparator<String>() {
255            @Override
256            public int compare(String arg1, String arg2) {
257                String str1 = arg1.substring(arg1.lastIndexOf('.') + cmdPrefix.length() + 1);
258                String str2 = arg2.substring(arg2.lastIndexOf('.') + cmdPrefix.length() + 1);
259                return str1.compareTo(str2);
260            }
261           
262        });
263        for (String className : classNames) {
264            String commandStr =
265                className.substring(className.lastIndexOf('.') + cmdPrefix.length() + 1);
266           
267            // commands may be found twice as a package may be available twice on the
268            // class path. Therefore check, if the command was already dumped before
269            // dumping it.
270            if (!commands.contains(commandStr)) {
271                // class may still be inner classes. Therefore load the command, to
272                // see if it is really available and a command.
273                Command cmd = loadCMD(className);
274                if (cmd != null) {
275                    commands.add(cmd);
276                }
277            }
278        }
279       
280        Command[] commandArray = commands.toArray(new Command[commands.size()]);
281        return commandArray;
282    }
283}
Note: See TracBrowser for help on using the repository browser.