001    /*
002     * 
003     * $Revision: 13085 $ $Date: 2008-02-06 18:27:24 +0100 (Mi, 06 Feb 2008) $
004     *
005     * This file is part of ***  M y C o R e  ***
006     * See http://www.mycore.de/ for details.
007     *
008     * This program is free software; you can use it, redistribute it
009     * and / or modify it under the terms of the GNU General Public License
010     * (GPL) as published by the Free Software Foundation; either version 2
011     * of the License or (at your option) any later version.
012     *
013     * This program is distributed in the hope that it will be useful, but
014     * WITHOUT ANY WARRANTY; without even the implied warranty of
015     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016     * GNU General Public License for more details.
017     *
018     * You should have received a copy of the GNU General Public License
019     * along with this program, in a file called gpl.txt or license.txt.
020     * If not, write to the Free Software Foundation Inc.,
021     * 59 Temple Place - Suite 330, Boston, MA  02111-1307 USA
022     */
023    
024    package org.mycore.frontend.cli;
025    
026    import java.lang.reflect.InvocationTargetException;
027    import java.lang.reflect.Method;
028    import java.text.Format;
029    import java.text.MessageFormat;
030    import java.text.NumberFormat;
031    import java.text.ParseException;
032    import java.util.ArrayList;
033    import java.util.List;
034    import java.util.StringTokenizer;
035    
036    import org.mycore.common.MCRConfigurationException;
037    
038    /**
039     * Represents a command understood by the command line interface. A command has
040     * an external input syntax that the user uses to invoke the command and points
041     * to a method in a class that implements the command.
042     * 
043     * @see MCRCommandLineInterface
044     * 
045     * @author Frank Lützenkirchen
046     * @author Jens Kupferschmidt
047     * @version $Revision: 13085 $ $Date: 2008-02-06 18:27:24 +0100 (Mi, 06 Feb 2008) $
048     */
049    public class MCRCommand {
050        /** The input format used for invoking this command */
051        protected MessageFormat messageFormat;
052    
053        /** The java method that implements this command */
054        protected Method method;
055    
056        /** The types of the invocation parameters */
057        protected Class[] parameterTypes;
058    
059        /** The number of invocation parameters */
060        protected int numParameters;
061    
062        /** The class providing the implementation method */
063        protected String className;
064    
065        /** The method implementing this command */
066        protected String methodName;
067    
068        /** The beginning of the message format up to the first parameter */
069        protected String suffix;
070    
071        /** The help text String */
072        protected String help;
073    
074        /**
075         * Creates a new MCRCommand.
076         * 
077         * @param format
078         *            the command syntax, e.g. "save document {0} to directory {1}"
079         * @param methodSignature
080         *            the method to invoke, e.g.
081         *            "miless.commandline.DocumentCommands.saveDoc int String"
082         * @param helpText
083         *            the helpt text for this command
084         */
085        public MCRCommand(String format, String methodSignature, String helpText) {
086            StringTokenizer st = new StringTokenizer(methodSignature, " ");
087    
088            String token = st.nextToken();
089            int point = token.lastIndexOf(".");
090    
091            className = token.substring(0, point);
092            methodName = token.substring(point + 1);
093            numParameters = st.countTokens();
094            parameterTypes = new Class[numParameters];
095            messageFormat = new MessageFormat(format);
096    
097            for (int i = 0; i < numParameters; i++) {
098                token = st.nextToken();
099    
100                Format f;
101    
102                if (token.equals("int")) {
103                    parameterTypes[i] = Integer.TYPE;
104                    f = NumberFormat.getIntegerInstance();
105                } else if (token.equals("String")) {
106                    parameterTypes[i] = String.class;
107                    f = null;
108                } else {
109                    throw new MCRConfigurationException("Error while parsing command definitions for command line interface:\n" + "Unsupported argument type '"
110                            + token + "' in command " + methodSignature);
111                }
112    
113                messageFormat.setFormat(i, f);
114            }
115    
116            int pos = format.indexOf("{");
117            suffix = ((pos == -1) ? format : format.substring(0, pos));
118    
119            if (helpText != null) {
120                help = helpText;
121            } else {
122                help = "No help text available for this command";
123            }
124        }
125    
126        /**
127         * Returns the method implementing the command behavior.
128         * 
129         * @return The method to be invoked for executing the command
130         * @throws ClassNotFoundException
131         *             when the class that implements the method was not found
132         * @throws NoSuchMethodException
133         *             When the method specified in the constructor was not found
134         */
135        protected Method getMethod(ClassLoader classLoader) throws ClassNotFoundException, NoSuchMethodException {
136            if (method == null) {
137                method = Class.forName(className, true, classLoader).getMethod(methodName, parameterTypes);
138            }
139    
140            return method;
141        }
142    
143        /**
144         * The method return the helpt text of this command.
145         * 
146         * @return the help text as String
147         */
148        protected String getHelpText() {
149            return help;
150        }
151    
152        /**
153         * Parses an input string and tries to match it with the message format used
154         * to invoke this command.
155         * 
156         * @param commandLine
157         *            The input from the command line
158         * @return null, if the input does not match the message format; otherwise
159         *         an array holding the parameter values from the command line
160         */
161        protected Object[] parseCommandLine(String commandLine) {
162            try {
163                return messageFormat.parse(commandLine);
164            } catch (ParseException ex) {
165                return null;
166            }
167        }
168    
169        /**
170         * Transforms the parameters found by the MessageFormat parse method into
171         * such that can be used to invoke the method implementing this command
172         * 
173         * @param commandParameters
174         *            The parameters as returned by the
175         *            <code>parseCommandLine</code> method
176         * @return The parameters that can be used to invoke the implementing method
177         */
178        protected Object[] buildInvocationParameters(Object[] commandParameters) {
179            Object[] parameters = new Object[numParameters];
180            int j = 0;
181    
182            for (int i = 0; i < numParameters; i++) {
183                if (parameterTypes[i] == Integer.TYPE) {
184                    parameters[i] = ((Number) commandParameters[j]).intValue();
185                    j++;
186                    continue;
187                }
188    
189                if (parameterTypes[i] == String.class) {
190                    parameters[i] = commandParameters[j];
191                    j++;
192    
193                    continue;
194                }
195            }
196    
197            return parameters;
198        }
199    
200        /**
201         * Tries to invoke the method that implements the behavior of this command
202         * given the user input from the command line. This is only done when the
203         * command line syntax matches the syntax used by this command.
204         * 
205         * @return null, if the command syntax did not match and the command was not
206         *         invoked, otherwise a List of commands is returned which may be
207         *         empty or otherwise contains commands that should be processed
208         *         next
209         * @param input
210         *            The command entered by the user at the command prompt
211         * @throws IllegalAccessException
212         *             when the method can not be invoked
213         * @throws InvocationTargetException
214         *             when an exception is thrown by the invoked method
215         * @throws ClassNotFoundException
216         *             when the class providing the method could not be found
217         * @throws NoSuchMethodException
218         *             when the method specified does not exist
219         */
220        public List<String> invoke(String input) throws IllegalAccessException, InvocationTargetException, ClassNotFoundException, NoSuchMethodException {
221            return invoke(input, MCRCommand.class.getClassLoader());
222        }
223    
224        @SuppressWarnings("unchecked")
225        public List<String> invoke(String input, ClassLoader classLoader) throws IllegalAccessException, InvocationTargetException, ClassNotFoundException,
226                NoSuchMethodException {
227            if (!input.startsWith(suffix)) {
228                return null;
229            }
230    
231            Object[] commandParameters = parseCommandLine(input);
232    
233            if (commandParameters == null) {
234                return null;
235            }
236    
237            Object result = getMethod(classLoader).invoke(null, buildInvocationParameters(commandParameters));
238            if ((result instanceof List) && (!((List) result).isEmpty()) && (((List) result).get(0) instanceof String))
239                return (List<String>) result;
240            else
241                return new ArrayList<String>();
242        }
243    
244        /**
245         * Returns the input syntax to be used for invoking this command from the
246         * command prompt.
247         * 
248         * @return the input syntax for this command
249         */
250        public final String showSyntax() {
251            return messageFormat.toPattern();
252        }
253    }