001/*
002 * (C) Copyright 2006-2010 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     bstefanescu
018 */
019package org.nuxeo.shell.impl;
020
021import java.util.ArrayList;
022import java.util.HashMap;
023import java.util.List;
024import java.util.Map;
025
026import jline.Completor;
027import jline.SimpleCompletor;
028
029import org.nuxeo.shell.CommandType;
030import org.nuxeo.shell.Shell;
031import org.nuxeo.shell.ShellException;
032
033/**
034 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
035 */
036public abstract class AbstractCommandType implements CommandType {
037
038    protected Class<? extends Runnable> cmdClass;
039
040    protected List<Setter> injectable;
041
042    protected Map<String, Token> params;
043
044    protected List<Token> args;
045
046    public AbstractCommandType(Class<? extends Runnable> cmdClass, List<Setter> injectable, Map<String, Token> params,
047            List<Token> args) {
048        this.cmdClass = cmdClass;
049        this.params = params == null ? new HashMap<String, Token>() : params;
050        this.args = args == null ? new ArrayList<Token>() : args;
051        this.injectable = injectable == null ? new ArrayList<Setter>() : injectable;
052    }
053
054    public Class<?> getCommandClass() {
055        return cmdClass;
056    }
057
058    public List<Token> getArguments() {
059        return args;
060    }
061
062    public Map<String, Token> getParameters() {
063        return params;
064    }
065
066    public String getSyntax() {
067        ArrayList<String> argNames = new ArrayList<String>();
068        for (Token arg : args) {
069            if (arg.isArgument()) {
070                if (arg.isRequired) {
071                    argNames.add(arg.name);
072                } else {
073                    argNames.add("[" + arg.name + "]");
074                }
075            }
076        }
077        StringBuilder buf = new StringBuilder();
078        buf.append(getName());
079        if (!params.isEmpty()) {
080            buf.append(" [options]");
081        }
082        for (String name : argNames) {
083            buf.append(" ").append(name);
084        }
085        return buf.toString();
086    }
087
088    public Runnable newInstance(Shell shell, String... line) throws ShellException {
089        Runnable cmd;
090        try {
091            cmd = createInstance(shell);
092        } catch (Throwable t) {
093            throw new ShellException(t);
094        }
095        inject(shell, cmd, line);
096        return cmd;
097    }
098
099    protected Runnable createInstance(Shell shell) throws Exception {
100        return cmdClass.newInstance();
101    }
102
103    /**
104     * The last element in line must be an empty element (e.g. "") if you need the next argument that may match. If the
105     * last element is not empty then this element will be returned as an argument or parameter.
106     *
107     * @param line
108     * @return
109     */
110    protected Token getLastToken(String... line) {
111        int index = -1;
112        Token last = null;
113        if (params != null) {
114            for (int i = 1; i < line.length; i++) {
115                String key = line[i];
116                if (key.startsWith("-")) { // a param
117                    Token arg = params.get(key);
118                    if (arg != null && arg.isRequired) {
119                        i++;
120                        last = arg;
121                        continue;
122                    }
123                } else { // an arg
124                    last = null;
125                    index++;
126                }
127            }
128        }
129        if (last != null) {
130            return last;
131        }
132        if (index == -1 || index >= args.size()) {
133            return null;
134        }
135        if (args != null) {
136            return args.get(index);
137        }
138        return null;
139    }
140
141    protected Completor getParamCompletor(String prefix) {
142        ArrayList<String> result = new ArrayList<String>();
143        for (String key : params.keySet()) {
144            if (key.startsWith(prefix)) {
145                result.add(key);
146            }
147        }
148        return result.isEmpty() ? null : new SimpleCompletor(result.toArray(new String[result.size()]));
149    }
150
151    public Completor getLastTokenCompletor(Shell shell, String... line) {
152        // check first for a param key completor
153        String last = line[line.length - 1];
154        if (last.startsWith("-")) { // may be a param
155            Completor c = getParamCompletor(last);
156            if (c != null) {
157                return c;
158            }
159        }
160        // check now for a value completor
161        Token arg = getLastToken(line);
162        if (arg == null) {
163            return null;
164        }
165        if (arg.completor != null && !arg.completor.isInterface()) {
166            try {
167                return arg.completor.newInstance();
168            } catch (Throwable t) {
169                throw new ShellException("Failed to load completor: " + arg.completor, t);
170            }
171        }
172        return shell.getCompletorProvider().getCompletor(shell, this, arg.setter.getType());
173    }
174
175    protected void inject(Shell shell, Runnable cmd, String... line) throws ShellException {
176        for (Setter s : injectable) {
177            s.set(cmd, shell.getContextObject(s.getType()));
178        }
179        int index = 0;
180        int argCount = args.size();
181        for (int i = 1; i < line.length; i++) {
182            String key = line[i];
183            if (key.startsWith("-")) {
184                Token arg = params.get(key);
185                if (arg == null) {
186                    throw new ShellException("Unknown parameter: " + key);
187                }
188                String v = null;
189                if (!arg.isRequired) {
190                    v = "true";
191                } else if (i == line.length - 1) {
192                    throw new ShellException("Parameter " + key + " must have a value");
193                } else {
194                    v = line[++i];
195                }
196                arg.setter.set(cmd, shell.getValueAdapter().getValue(shell, arg.setter.getType(), v));
197            } else {
198                if (index >= argCount) {
199                    throw new ShellException("Too many arguments");
200                }
201                Token arg = args.get(index++);
202                arg.setter.set(cmd, shell.getValueAdapter().getValue(shell, arg.setter.getType(), key));
203            }
204        }
205        for (int i = index; i < argCount; i++) {
206            if (args.get(i).isRequired) {
207                throw new ShellException("Required argument " + args.get(i).name + " is missing");
208            }
209        }
210    }
211
212    public int compareTo(CommandType o) {
213        return getName().compareTo(o.getName());
214    }
215}