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

Last change on this file since 2283 was 2283, checked in by pharms, 5 years ago
File size: 16.4 KB
RevLine 
[927]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.
[661]14
[1]15package de.ugoe.cs.util.console;
16
[661]17import java.io.File;
18import java.io.FileInputStream;
19import java.io.FilenameFilter;
20import java.io.IOException;
[2260]21import java.lang.reflect.InvocationTargetException;
[661]22import java.net.URL;
[1]23import java.util.ArrayList;
[1271]24import java.util.Arrays;
[664]25import java.util.Comparator;
[661]26import java.util.Enumeration;
[1]27import java.util.List;
[669]28import java.util.SortedSet;
29import java.util.TreeSet;
[661]30import java.util.jar.JarEntry;
31import java.util.jar.JarInputStream;
[639]32import java.util.logging.Level;
[1]33
[1243]34import de.ugoe.cs.util.StringTools;
35
[1]36/**
37 * <p>
[661]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.
[1]41 * </p>
42 * <p>
[175]43 * This class is implemented as a <i>Singleton</i>.
[1]44 * </p>
45 *
46 * @author Steffen Herbold
[175]47 * @version 1.0
[1]48 */
49public class CommandExecuter {
50
[661]51    /**
52     * <p>
53     * Handle of the CommandExecuter instance.
54     * </p>
55     */
56    private final static CommandExecuter theInstance = new CommandExecuter();
[1]57
[661]58    /**
59     * <p>
60     * Prefix of all command classes.
61     * </p>
62     */
63    private static final String cmdPrefix = "CMD";
[175]64
[661]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";
[1]71
[661]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     */
[2281]82    private List<CommandPackage> commandPackageList;
83
[1238]84    /**
85     * <p>
86     * the list of available commands (lazy instantiation in the method
87     * {@link #getAvailableCommands()})
88     * <p>
89     */
90    private Command[] availableCommands;
[1]91
[661]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    }
[1]102
[661]103    /**
104     * <p>
105     * Creates a new CommandExecuter. Private to prevent multiple instances (Singleton).
106     * </p>
107     */
108    private CommandExecuter() {
[2281]109        commandPackageList = new ArrayList<CommandPackage>();
[661]110    }
[1]111
[661]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
[766]119     * @throws IllegalArgumentException
[661]120     *             thrown if the package name is null or empty string
121     */
[2281]122    public void addCommandPackage(String pkg, ClassLoader loader) {
[661]123        if ("".equals(pkg) || pkg == null) {
[766]124            throw new IllegalArgumentException("package name must not be null or empty string");
[661]125        }
[2281]126        commandPackageList.add(new CommandPackage(pkg, loader));
127        availableCommands = null;
[661]128    }
[1]129
[661]130    /**
131     * <p>
132     * Executes the command defined by string. A command has the following form (mix of EBNF and
133     * natural language):
134     * </p>
135     * <code>
136     * &lt;command&gt; := &lt;commandname&gt;&lt;whitespace&gt;{&lt;parameter&gt;}<br>
137     * &lt;commandname&gt; := String without whitespaces. Has to be a valid Java class name<br>
138     * &lt;parameter&gt; := &lt;string&gt;|&lt;stringarray&gt;<br>
139     * &lt;string&gt; := &lt;stringwithoutwhitespaces&gt;|&lt;stringwithwhitespaces&gt;
140     * &lt;stringwithoutwhitespaces&gt; := a string without whitespaces<br>
141     * &lt;stringwithoutwhitespaces&gt; := a string, that can have whitespaces, but must be in double quotes<br>
142     * &lt;stringarray&gt; := "["&lt;string&gt;{&lt;whitespace&gt;&lt;string&gt;"]"
143     * </code>
144     *
145     * @param command
146     *            the command as a string
147     */
148    public void exec(String command) {
149        Console.commandNotification(command);
150        CommandParser parser = new CommandParser();
151        parser.parse(command);
[2281]152
153        Command cmd = getCMD(parser.getCommandName());
154
[661]155        if (cmd == null) {
156            Console.println("Unknown command");
157        }
158        else {
159            try {
160                cmd.run(parser.getParameters());
161            }
[766]162            catch (IllegalArgumentException e) {
[1355]163                Console.println("invalid parameter provided: " + e.getMessage());
[664]164                Console.println("Usage: " + cmd.help());
[661]165            }
[1355]166            catch (Exception e) {
167                Console.println("error executing command: " + e);
168                Console.logException(e);
169                Console.println("Usage: " + cmd.help());
170            }
[661]171        }
172    }
[1]173
[661]174    /**
175     * <p>
176     * Helper method that loads a class and tries to cast it to {@link Command}.
177     * </p>
178     *
179     * @param className
180     *            qualified name of the class (including package name)
181     * @return if class is available and implement {@link Command} and instance of the class, null
182     *         otherwise
183     */
[718]184    public Command getCMD(String commandName) {
[2281]185        for (Command candidate : getAvailableCommands()) {
186            if (candidate.getClass().getSimpleName().equals(cmdPrefix + commandName)) {
187                return candidate;
188            }
[718]189        }
[2281]190
191        return null;
[718]192    }
[661]193
194    /**
195     * <p>
[669]196     * reads all available commands from the registered command packages and returns a list of their
197     * names
[661]198     * </p>
[669]199     *
[661]200     * @return an array containing the names of the available commands.
201     */
[664]202    public Command[] getAvailableCommands() {
[1238]203        if (availableCommands == null) {
[2281]204            // List<Command> commands = new ArrayList<Command>();
205            List<CommandPackage> packages = new ArrayList<CommandPackage>();
[1238]206            packages.addAll(commandPackageList);
[2281]207            packages.add(new CommandPackage(defaultPackage, this.getClass().getClassLoader()));
[669]208
[1238]209            FilenameFilter filter = new FilenameFilter() {
210                @Override
211                public boolean accept(File dir, String name) {
[2281]212                    return (name != null) && (name.startsWith(cmdPrefix)) &&
213                        (name.endsWith(".class"));
[1238]214                }
215            };
[669]216
[2281]217            SortedSet<Command> commands = new TreeSet<Command>(new Comparator<Command>() {
[1238]218                @Override
[2281]219                public int compare(Command arg1, Command arg2) {
220                    String str1 = arg1.getClass().getSimpleName().substring(cmdPrefix.length());
221                    String str2 = arg2.getClass().getSimpleName().substring(cmdPrefix.length());
[1238]222                    return str1.compareTo(str2);
223                }
[669]224
[1238]225            });
[669]226
[2281]227            for (CommandPackage commandPackage : packages) {
228                String path = commandPackage.getPackageName().replace('.', '/');
[1238]229                try {
[2281]230                    ClassLoader loader = commandPackage.getClassLoader();
[669]231
[2281]232                    if (loader == null) {
233                        loader = ClassLoader.getSystemClassLoader();
234                    }
235
236                    Enumeration<URL> resources = loader.getResources(path);
237
[1238]238                    while (resources.hasMoreElements()) {
239                        URL resource = resources.nextElement();
240                        File packageDir = new File(resource.getFile());
[669]241
[1238]242                        if (packageDir.isDirectory()) {
[2281]243                            File[] classFiles = packageDir.listFiles(filter);
244                            if (classFiles != null) {
245                                for (File classFile : classFiles) {
246                                    String className = classFile.getName()
247                                        .substring(0, classFile.getName().lastIndexOf('.'));
[2283]248                                   
[2281]249                                    Class<?> clazz =
250                                        loader.loadClass(commandPackage.getPackageName() + "." +
251                                            className);
[2283]252                                   
253                                    if (Command.class.isAssignableFrom(clazz) &&
254                                        clazz.getSimpleName().startsWith(cmdPrefix))
255                                    {
[2282]256                                        commands.add((Command) clazz.getConstructor().newInstance());
[2281]257                                    }
258                                }
259                            }
[661]260                        }
[1238]261                        else {
262                            int index = resource.getFile().lastIndexOf('!');
263                            if ((index > 0) && (resource.getFile().startsWith("file:")) &&
264                                (resource.getFile().endsWith("!/" + path)))
265                            {
266                                String jarFile =
267                                    resource.getFile().substring("file:".length(), index);
[669]268
[1238]269                                // we have to read the package content from a jar file
270                                JarInputStream jarInputStream = null;
271                                try {
272                                    jarInputStream =
273                                        new JarInputStream(new FileInputStream(jarFile));
274                                    JarEntry entry = null;
275                                    do {
276                                        entry = jarInputStream.getNextJarEntry();
277                                        if ((entry != null) && (!entry.isDirectory()) &&
[2281]278                                            (entry.getName().startsWith(path)))
[1238]279                                        {
[2281]280                                            String className = entry.getName()
281                                                .substring(path.length() + 1,
282                                                           entry.getName().lastIndexOf('.'));
[2283]283                                           
[2281]284                                            Class<?> clazz =
285                                                loader.loadClass(commandPackage.getPackageName() +
286                                                    "." + className);
[2282]287                                           
[2283]288                                            if (Command.class.isAssignableFrom(clazz) &&
289                                                clazz.getSimpleName().startsWith(cmdPrefix))
290                                            {
[2281]291                                                commands.add((Command) clazz.getConstructor().newInstance());
292                                            }
[1238]293                                        }
[749]294                                    }
[1238]295                                    while (entry != null);
[749]296                                }
[1238]297                                finally {
298                                    if (jarInputStream != null) {
299                                        jarInputStream.close();
300                                    }
301                                }
302
[661]303                            }
304                        }
305                    }
306                }
[1238]307                catch (IOException e) {
[2281]308                    Console.traceln(Level.WARNING, "could not read commands of package " +
309                        commandPackage.getPackageName());
[1238]310                }
[2281]311                catch (ClassNotFoundException e) {
312                    Console.traceln(Level.WARNING, "could not load a command of package " +
313                        commandPackage.getPackageName() + ": " + e);
[1238]314                }
[2281]315                catch (InstantiationException e) {
316                    Console.traceln(Level.WARNING, "could not load a command of package " +
317                        commandPackage.getPackageName() + ": " + e);
318                }
319                catch (IllegalAccessException e) {
320                    Console.traceln(Level.WARNING, "could not load a command of package " +
321                        commandPackage.getPackageName() + ": " + e);
322                }
323                catch (IllegalArgumentException e) {
324                    Console.traceln(Level.WARNING, "could not load a command of package " +
325                        commandPackage.getPackageName() + ": " + e);
326                }
327                catch (InvocationTargetException e) {
328                    Console.traceln(Level.WARNING, "could not load a command of package " +
329                        commandPackage.getPackageName() + ": " + e);
330                }
331                catch (NoSuchMethodException e) {
332                    Console.traceln(Level.WARNING, "could not load a command of package " +
333                        commandPackage.getPackageName() + ": " + e);
334                }
335                catch (SecurityException e) {
336                    Console.traceln(Level.WARNING, "could not load a command of package " +
337                        commandPackage.getPackageName() + ": " + e);
338                }
[661]339            }
[669]340
[1238]341            availableCommands = commands.toArray(new Command[commands.size()]);
[661]342        }
[2281]343
[1271]344        return Arrays.copyOf(availableCommands, availableCommands.length);
[661]345    }
[2281]346
[731]347    /**
348     * <p>
349     * Get a copy of the currently registered command packages.
350     * </p>
351     *
352     * @return currently registered command packages
353     */
354    public List<String> getCommandPackages() {
[2281]355        List<String> commandPackageListCopy = new ArrayList<>();
356
357        commandPackageListCopy.add(defaultPackage);
358
359        for (CommandPackage pkg : commandPackageList) {
360            commandPackageListCopy.add(pkg.getPackageName());
361        }
362
[731]363        return commandPackageListCopy;
364    }
[1238]365
366    /**
367     * <p>
368     * this method method performs an auto completion of the provided String as far as possible
[2281]369     * regarding the available commands. It auto completes to the full command name, if only one
370     * command matches the given prefix. It auto completes to the common denominator, if several
371     * commands match the prefix
[1238]372     * </p>
373     *
[2281]374     * @param commandPrefix
375     *            the prefix to be auto completed
[1238]376     *
377     * @return as described
378     */
379    public String autoCompleteCommand(String commandPrefix) {
380        Command[] commands = getAvailableCommands();
[2281]381
[1243]382        String[] completions = new String[commands.length];
[2281]383
[1243]384        for (int i = 0; i < commands.length; i++) {
385            completions[i] = commands[i].getClass().getSimpleName().substring(3);
[1238]386        }
[2281]387
[1243]388        return StringTools.autocomplete(commandPrefix, completions);
[1238]389    }
[2281]390
391    /**
392     * represents a command package with a package name and the class loader to use
393     */
394    private class CommandPackage {
395        /**
396         * the name of the represented package
397         */
398        private String packageName;
399
400        /**
401         * the class loader to use to load the package
402         */
403        private ClassLoader classLoader;
404
405        /**
406         * <p>
407         * instantiate the fields
408         * </p>
409         */
410        public CommandPackage(String packageName, ClassLoader classLoader) {
411            super();
412            this.packageName = packageName;
413            this.classLoader = classLoader;
414        }
415
416        /**
417         * @return the packageName
418         */
419        public String getPackageName() {
420            return packageName;
421        }
422
423        /**
424         * @return the classLoader
425         */
426        public ClassLoader getClassLoader() {
427            return classLoader;
428        }
429
430    }
[1]431}
Note: See TracBrowser for help on using the repository browser.