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.lang.reflect.Field;
020import java.lang.reflect.Method;
021import java.util.ArrayList;
022import java.util.Collections;
023import java.util.HashMap;
024import java.util.List;
025import java.util.Map;
026
027import org.nuxeo.shell.Argument;
028import org.nuxeo.shell.Command;
029import org.nuxeo.shell.Context;
030import org.nuxeo.shell.Parameter;
031import org.nuxeo.shell.ShellException;
032
033/**
034 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
035 */
036public class DefaultCommandType extends AbstractCommandType {
037
038    protected List<Setter> injectable;
039
040    protected Map<String, Token> params;
041
042    protected List<Token> args;
043
044    @SuppressWarnings("unchecked")
045    public static DefaultCommandType fromAnnotatedClass(String className) throws ShellException {
046        Class<Runnable> cls;
047        try {
048            cls = (Class<Runnable>) Class.forName(className);
049        } catch (Exception e) {
050            throw new ShellException(e);
051        }
052        return fromAnnotatedClass(cls);
053    }
054
055    public static DefaultCommandType fromAnnotatedClass(Class<? extends Runnable> cls) throws ShellException {
056        HashMap<String, Token> params = new HashMap<String, Token>();
057        ArrayList<Token> args = new ArrayList<Token>();
058        ArrayList<Setter> injectable = new ArrayList<Setter>();
059        Command cmd = cls.getAnnotation(Command.class);
060        if (cmd == null) {
061            throw new ShellException("Class " + cls + " is not a command. You must annotated it with @Command");
062        }
063        for (Field field : cls.getDeclaredFields()) {
064            Parameter param = field.getAnnotation(Parameter.class);
065            if (param != null) {
066                Token a = new Token();
067                a.name = param.name();
068                a.help = param.help();
069                a.isRequired = param.hasValue();
070                a.setter = new FieldSetter(field);
071                a.completor = param.completor();
072                params.put(a.name, a);
073                continue;
074            }
075            Argument arg = field.getAnnotation(Argument.class);
076            if (arg != null) {
077                Token a = new Token();
078                a.name = arg.name();
079                a.index = arg.index();
080                a.help = arg.help();
081                a.completor = arg.completor();
082                a.isRequired = arg.required();
083                a.setter = new FieldSetter(field);
084                args.add(a);
085                continue;
086            }
087            Context ctx = field.getAnnotation(Context.class);
088            if (ctx != null) {
089                injectable.add(new FieldSetter(field));
090            }
091        }
092        for (Method method : cls.getDeclaredMethods()) {
093            Parameter param = method.getAnnotation(Parameter.class);
094            if (param != null) {
095                Token a = new Token();
096                a.name = param.name();
097                a.help = param.help();
098                a.isRequired = param.hasValue();
099                a.setter = new MethodSetter(method);
100                a.completor = param.completor();
101                params.put(a.name, a);
102                continue;
103            }
104            Argument arg = method.getAnnotation(Argument.class);
105            if (arg != null) {
106                Token a = new Token();
107                a.name = arg.name();
108                a.index = arg.index();
109                a.help = arg.help();
110                a.isRequired = arg.required();
111                a.setter = new MethodSetter(method);
112                a.completor = arg.completor();
113                args.add(a);
114            }
115        }
116        Collections.sort(args);
117        return new DefaultCommandType(cls, injectable, params, args);
118    }
119
120    public DefaultCommandType(Class<? extends Runnable> cmdClass, List<Setter> injectable, Map<String, Token> params,
121            List<Token> args) {
122        super(cmdClass, injectable, params, args);
123    }
124
125    public String getHelp() {
126        return cmdClass.getAnnotation(Command.class).help();
127    }
128
129    public String getName() {
130        return cmdClass.getAnnotation(Command.class).name();
131    }
132
133    public String[] getAliases() {
134        return cmdClass.getAnnotation(Command.class).aliases();
135    }
136
137    public static class MethodSetter implements Setter {
138        protected Method method;
139
140        protected Class<?> type;
141
142        public MethodSetter(Method method) {
143            this.method = method;
144            method.setAccessible(true);
145            Class<?>[] types = method.getParameterTypes();
146            if (types.length != 1) {
147                throw new IllegalArgumentException("Invalid method setter should take one argument. Method: " + method);
148            }
149            type = types[0];
150        }
151
152        public void set(Object obj, Object value) throws ShellException {
153            try {
154                method.invoke(obj, value);
155            } catch (Exception e) {
156                throw new ShellException(e);
157            }
158        }
159
160        public Class<?> getType() {
161            return type;
162        }
163    }
164
165    public static class FieldSetter implements Setter {
166        protected Field field;
167
168        protected Class<?> type;
169
170        public FieldSetter(Field field) {
171            this.field = field;
172            field.setAccessible(true);
173            this.type = field.getType();
174        }
175
176        public void set(Object obj, Object value) throws ShellException {
177            try {
178                field.set(obj, value);
179            } catch (Exception e) {
180                throw new ShellException(e);
181            }
182        }
183
184        public Class<?> getType() {
185            return type;
186        }
187    }
188
189}