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