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