View Javadoc

1   /*
2   Karma core - Core of the Karma application
3   Copyright (C) 2004  Toolforge <www.toolforge.nl>
4   
5   This library is free software; you can redistribute it and/or
6   modify it under the terms of the GNU Lesser General Public
7   License as published by the Free Software Foundation; either
8   version 2.1 of the License, or (at your option) any later version.
9   
10  This library is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  Lesser General Public License for more details.
14  
15  You should have received a copy of the GNU Lesser General Public
16  License along with this library; if not, write to the Free Software
17  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  */
19  package nl.toolforge.karma.core.cmd;
20  
21  import net.sf.sillyexceptions.OutOfTheBlueException;
22  import nl.toolforge.karma.core.KarmaRuntimeException;
23  import org.apache.commons.cli.CommandLineParser;
24  import org.apache.commons.cli.MissingArgumentException;
25  import org.apache.commons.cli.MissingOptionException;
26  import org.apache.commons.cli.Options;
27  import org.apache.commons.cli.ParseException;
28  import org.apache.commons.cli.PosixParser;
29  import org.apache.commons.cli.UnrecognizedOptionException;
30  
31  import java.lang.reflect.Constructor;
32  import java.lang.reflect.InvocationTargetException;
33  import java.util.ArrayList;
34  import java.util.Collection;
35  import java.util.HashSet;
36  import java.util.Iterator;
37  import java.util.List;
38  import java.util.Map;
39  import java.util.Set;
40  import java.util.TreeMap;
41  
42  //import net.sf.sillyexceptions.OutOfTheBlueException;
43  
44  /***
45   * This factory is the single resource of Command objects. <code>KarmaRuntimeException</code>s are thrown when
46   * something fails.
47   *
48   * @author W.H. Schraal
49   * @version $Id: CommandFactory.java,v 1.34 2004/11/10 23:53:07 asmedes Exp $
50   */
51  public final class CommandFactory {
52  
53    // Singleton
54    //
55    private static CommandFactory factory = null;
56  
57    // Reference ro all command names
58    //
59    private static Set commandNames = null;
60  
61    private static Map commandsByName = null;
62    private static Map commandsByAlias = null;
63  
64    /***
65     * Only static methods
66     */
67    private CommandFactory() throws CommandLoadException {
68      init();
69    }
70  
71    private synchronized void init() throws CommandLoadException {
72  
73      CommandDescriptorMap descriptors = CommandLoader.getInstance().load();
74  
75      commandsByName = new TreeMap();
76      commandsByAlias = new TreeMap();
77      commandNames = new HashSet();
78  
79      // Store all commands by name in a hash
80      // Create a set of all command names.
81      //
82      for (Iterator i = descriptors.values().iterator(); i.hasNext();) {
83        CommandDescriptor descriptor = (CommandDescriptor) i.next();
84  
85        commandsByName.put(descriptor.getName(), descriptor);
86        commandNames.add(descriptor.getName());
87  
88        for (Iterator j = descriptor.getAliasList().iterator(); j.hasNext();) {
89          String alias = (String) j.next();
90          commandsByAlias.put(alias, descriptor);
91          commandNames.add(alias);
92        }
93      }
94    }
95  
96    /***
97     * Gets the singleton <code>CommandFactory</code>.
98     *
99     * @return The singleton <code>CommandFactory</code> instance.
100    */
101   public static CommandFactory getInstance() throws CommandLoadException {
102     if (factory == null) {
103       factory = new CommandFactory();
104     }
105     return factory;
106   }
107 
108   /***
109    * Retrieves the correct <code>Command</code>-instance, by looking up the implementation class through
110    * <code>commandLine</code>.
111    *
112    * @param commandLineString Command arguments (e.g. <code>select-manifest -m karma-1.0</code>).
113    * @return The implementation of a <code>Command</code> object.
114    * @throws CommandException When a correct command could not be constructed. See
115    *                          {@link CommandException#INVALID_COMMAND}.
116    */
117   public Command getCommand(String commandLineString) throws CommandException {
118 
119     String commandName = null;
120     commandLineString = commandLineString.trim();
121 
122     if (commandLineString.indexOf(' ') > 0) {
123       commandName = commandLineString.substring(0, commandLineString.indexOf(' '));
124     } else {
125       commandName = commandLineString;
126     }
127 
128     char[] chars = commandLineString.substring(commandName.length()).toCharArray();
129 
130     List commandOptionsList = new ArrayList();
131 
132     int j = 0;
133     String part = null;
134 
135     while (j < chars.length) {
136 
137       // Go to the next part while we are a 'space' (chr(32))
138       //
139       while (chars[j] == ' ') {
140         j++;
141       }
142 
143       part = "";
144       if (chars[j] != '"') {
145         // Start of options or 'normal' arguments.
146         //
147         part += chars[j];
148         j++;
149 
150         while ((j < chars.length) && (chars[j] != ' ') && (chars[j] != '\"')) {
151           // Continue until a space or \" is reached.
152           //
153           part += chars[j];
154           j++;
155         }
156 
157       } else if (chars[j] == '"') {
158         // Begin van een argument dat bestaat uit een block data, gedemarkeerd door dubbele quotes, escaped dubbel
159         // quote wordt eruit gevist.
160         //
161         part += chars[j];
162         j++;
163 
164         while (j < chars.length) {
165 
166           if (chars[j] == '"') {
167             if (chars[j-1] != '//') {
168               // End of demarkated piece of text.
169               //
170               j++;
171               break;
172             }
173           }
174 
175           // doorlopen totdat een '"' is bereikt, behalve \" (escaped).
176           part += chars[j];
177           j++;
178         }
179         // We have reached a '"', which must be added.
180         //
181         part += '"';
182         j++;
183       }
184 
185       if (part.startsWith("\"") && part.endsWith("\"")) {
186         part = part.substring(1, part.length() - 1).trim();
187       }
188 
189       commandOptionsList.add(part);
190     }
191 
192     String[] commandOptions = (String[]) commandOptionsList.toArray(new String[commandOptionsList.size()]);
193 
194     return getCommand(commandName, commandOptions);
195   }
196 
197   public Command getCommand(String commandName, String[] arguments) throws CommandException {
198 
199     Command cmd = null;
200 
201     if (isCommand(commandName)) {
202 
203       CommandDescriptor descriptor = null;
204       try {
205         descriptor = getCommandDescriptor(commandName);
206 
207         // Construct the command implementation, with the default constructor
208         //
209         Class implementingClass = null;
210         try {
211           implementingClass = Class.forName(descriptor.getClassName());
212         } catch (ClassNotFoundException c) {
213           throw new CommandException(CommandException.NO_IMPLEMENTING_CLASS, new Object[]{descriptor.getClassName()});
214         }
215 
216         Constructor defaultConstructor = implementingClass.getConstructor(new Class[]{CommandDescriptor.class});
217         cmd = (Command) defaultConstructor.newInstance(new Object[]{descriptor});
218 
219         // Parse the command line options.
220         //
221         CommandLineParser parser = new PosixParser();
222 
223         Options parserOptions = descriptor.getOptions();
224 
225 //        if (parserOptions.getOptions().size() == 0 && arguments.length > 0) {
226 //          // The issue is that this is 1. not an exception and 2. no mechanism to propagate this back in a nice way.
227 //          throw new CommandException(CommandException.NO_OPTIONS_REQUIRED);
228 //        }
229 
230         cmd.setCommandLine(parser.parse(parserOptions, arguments));
231 
232       } catch (NoSuchMethodException e) {
233         throw new KarmaRuntimeException(e.getLocalizedMessage(), e);
234       } catch (SecurityException e) {
235         throw new KarmaRuntimeException(e.getLocalizedMessage(), e);
236       } catch (InstantiationException e) {
237         throw new KarmaRuntimeException(e.getLocalizedMessage(), e);
238       } catch (IllegalAccessException e) {
239         throw new KarmaRuntimeException(e.getLocalizedMessage(), e);
240       } catch (IllegalArgumentException e) {
241         throw new KarmaRuntimeException(e.getLocalizedMessage(), e);
242       } catch (InvocationTargetException e) {
243         throw new KarmaRuntimeException(e.getLocalizedMessage(), e);
244       } catch (ParseException e) {
245         if (e instanceof MissingOptionException) {
246           throw new CommandException(e, CommandException.MISSING_OPTION, new Object[]{e.getMessage()});
247         }
248         if (e instanceof UnrecognizedOptionException) {
249           throw new CommandException(e, CommandException.INVALID_OPTION, new Object[]{arguments});
250         }
251         if (e instanceof MissingArgumentException) {
252           throw new CommandException(e, CommandException.MISSING_ARGUMENT, new Object[]{e.getMessage()});
253         }
254       }
255       return cmd;
256     }
257     // At this point, we have no command
258     //
259     throw new CommandException(CommandException.UNKNOWN_COMMAND, new Object[]{commandName});
260   }
261 
262   /***
263    * Checks if some string is a command within this context.
264    *
265    * @param name
266    * @return
267    */
268   private boolean isCommand(String name) {
269     return commandNames.contains(name);
270   }
271 
272   public Collection getCommands() {
273     return commandsByName.values();
274   }
275 
276   /***
277    * Retrieves the correct command descriptor either by name or by alias (whichever is passed as a
278    * parameter). Returns <code>null</code> if the descriptor could not be found.
279    */
280   public CommandDescriptor getCommandDescriptor(String commandId) {
281 
282     try {
283       init(); //this fixes the bug where the previous value of an option is returned
284     } catch (CommandLoadException cle) {
285       //this can not happen, since the init has already been called in the constructor
286       //and it was successful then.
287       throw new OutOfTheBlueException("Tried to reload the commands, but failed. This is strange because they have been loaded earlier with success");
288     }
289 
290     if (commandsByName.containsKey(commandId)) {
291       return (CommandDescriptor) commandsByName.get(commandId);
292     } else {
293       if (commandsByAlias.containsKey(commandId)) {
294         return (CommandDescriptor) commandsByAlias.get(commandId);
295       }
296     }
297     return null;
298   }
299 
300 }