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