[661] | 1 |
|
---|
[1] | 2 | package de.ugoe.cs.util.console;
|
---|
| 3 |
|
---|
[661] | 4 | import java.io.File;
|
---|
| 5 | import java.io.FileInputStream;
|
---|
| 6 | import java.io.FilenameFilter;
|
---|
| 7 | import java.io.IOException;
|
---|
| 8 | import java.net.URL;
|
---|
[1] | 9 | import java.security.InvalidParameterException;
|
---|
| 10 | import java.util.ArrayList;
|
---|
[664] | 11 | import java.util.Collections;
|
---|
| 12 | import java.util.Comparator;
|
---|
[661] | 13 | import java.util.Enumeration;
|
---|
[1] | 14 | import java.util.List;
|
---|
[661] | 15 | import java.util.jar.JarEntry;
|
---|
| 16 | import java.util.jar.JarInputStream;
|
---|
[639] | 17 | import java.util.logging.Level;
|
---|
[1] | 18 |
|
---|
| 19 | /**
|
---|
| 20 | * <p>
|
---|
[661] | 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.
|
---|
[1] | 24 | * </p>
|
---|
| 25 | * <p>
|
---|
[175] | 26 | * This class is implemented as a <i>Singleton</i>.
|
---|
[1] | 27 | * </p>
|
---|
| 28 | *
|
---|
| 29 | * @author Steffen Herbold
|
---|
[175] | 30 | * @version 1.0
|
---|
[1] | 31 | */
|
---|
| 32 | public class CommandExecuter {
|
---|
| 33 |
|
---|
[661] | 34 | /**
|
---|
| 35 | * <p>
|
---|
| 36 | * Handle of the CommandExecuter instance.
|
---|
| 37 | * </p>
|
---|
| 38 | */
|
---|
| 39 | private final static CommandExecuter theInstance = new CommandExecuter();
|
---|
[1] | 40 |
|
---|
[661] | 41 | /**
|
---|
| 42 | * <p>
|
---|
| 43 | * Prefix of all command classes.
|
---|
| 44 | * </p>
|
---|
| 45 | */
|
---|
| 46 | private static final String cmdPrefix = "CMD";
|
---|
[175] | 47 |
|
---|
[661] | 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";
|
---|
[1] | 54 |
|
---|
[661] | 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;
|
---|
[1] | 66 |
|
---|
[661] | 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 | }
|
---|
[1] | 77 |
|
---|
[661] | 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 | }
|
---|
[1] | 86 |
|
---|
[661] | 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 | }
|
---|
[1] | 103 |
|
---|
[661] | 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 | * <command> := <commandname><whitespace>{<parameter>}<br>
|
---|
| 111 | * <commandname> := String without whitespaces. Has to be a valid Java class name<br>
|
---|
| 112 | * <parameter> := <string>|<stringarray><br>
|
---|
| 113 | * <string> := <stringwithoutwhitespaces>|<stringwithwhitespaces>
|
---|
| 114 | * <stringwithoutwhitespaces> := a string without whitespaces<br>
|
---|
| 115 | * <stringwithoutwhitespaces> := a string, that can have whitespaces, but must be in double quotes<br>
|
---|
| 116 | * <stringarray> := "["<string>{<whitespace><string>"]"
|
---|
| 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) {
|
---|
[664] | 141 | Console.println("Usage: " + cmd.help());
|
---|
[661] | 142 | }
|
---|
| 143 | }
|
---|
| 144 | }
|
---|
[1] | 145 |
|
---|
[661] | 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 | */
|
---|
[664] | 184 | public Command[] getAvailableCommands() {
|
---|
| 185 | List<Command> commands = new ArrayList<Command>();
|
---|
[661] | 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 |
|
---|
[664] | 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 | });
|
---|
[661] | 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.
|
---|
[664] | 273 | Command cmd = loadCMD(className);
|
---|
| 274 | if (cmd != null) {
|
---|
| 275 | commands.add(cmd);
|
---|
[661] | 276 | }
|
---|
| 277 | }
|
---|
| 278 | }
|
---|
| 279 |
|
---|
[664] | 280 | Command[] commandArray = commands.toArray(new Command[commands.size()]);
|
---|
[661] | 281 | return commandArray;
|
---|
| 282 | }
|
---|
[1] | 283 | }
|
---|