001 /*
002 * Copyright (C) 2012 eXo Platform SAS.
003 *
004 * This is free software; you can redistribute it and/or modify it
005 * under the terms of the GNU Lesser General Public License as
006 * published by the Free Software Foundation; either version 2.1 of
007 * the License, or (at your option) any later version.
008 *
009 * This software 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 * You should have received a copy of the GNU Lesser General Public
015 * License along with this software; if not, write to the Free
016 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
017 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
018 */
019
020 package org.crsh.cli.impl.lang;
021
022 import org.crsh.cli.descriptor.Description;
023 import org.crsh.cli.impl.descriptor.IntrospectionException;
024 import org.crsh.cli.descriptor.ParameterDescriptor;
025 import org.crsh.cli.impl.ParameterType;
026 import org.crsh.cli.Argument;
027 import org.crsh.cli.Command;
028 import org.crsh.cli.Option;
029 import org.crsh.cli.Required;
030 import org.crsh.cli.type.ValueTypeFactory;
031
032 import java.lang.annotation.Annotation;
033 import java.lang.reflect.Field;
034 import java.lang.reflect.Method;
035 import java.lang.reflect.Type;
036 import java.util.ArrayList;
037 import java.util.Arrays;
038 import java.util.Collections;
039 import java.util.LinkedHashMap;
040 import java.util.List;
041 import java.util.Map;
042 import java.util.logging.Level;
043 import java.util.logging.Logger;
044
045 /** @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a> */
046 public class CommandFactory {
047
048 /** . */
049 public static final CommandFactory DEFAULT = new CommandFactory();
050
051 /** . */
052 private static final Logger log = Logger.getLogger(CommandFactory.class.getName());
053
054 /** . */
055 protected final ValueTypeFactory valueTypeFactory;
056
057 public CommandFactory() {
058 this.valueTypeFactory = ValueTypeFactory.DEFAULT;
059 }
060
061 public CommandFactory(ClassLoader loader) throws NullPointerException {
062 this(new ValueTypeFactory(loader));
063 }
064
065 public CommandFactory(ValueTypeFactory valueTypeFactory) throws NullPointerException {
066 if (valueTypeFactory == null) {
067 throw new NullPointerException("No null value type factory accepted");
068 }
069
070 //
071 this.valueTypeFactory = valueTypeFactory;
072 }
073
074
075 private List<Method> findAllMethods(Class<?> introspected) throws IntrospectionException {
076 List<Method> methods;
077 Class<?> superIntrospected = introspected.getSuperclass();
078 if (superIntrospected == null) {
079 methods = new ArrayList<Method>();
080 } else {
081 methods = findAllMethods(superIntrospected);
082 for (Method method : introspected.getDeclaredMethods()) {
083 if (method.getAnnotation(Command.class) != null) {
084 methods.add(method);
085 }
086 }
087 }
088 return methods;
089 }
090
091 public <T> ObjectCommandDescriptor<T> create(Class<T> type) throws IntrospectionException {
092
093 // Find all command methods
094 List<Method> methods = findAllMethods(type);
095
096 //
097 if (methods.size() == 1 && methods.get(0).getName().equals("main")) {
098 MethodDescriptor<T> methodDescriptor = create(null, type.getSimpleName().toLowerCase(), methods.get(0));
099 for (ParameterDescriptor parameter : parameters(type)) {
100 methodDescriptor.addParameter(parameter);
101 }
102 return methodDescriptor;
103 } else {
104 Map<String, MethodDescriptor<T>> methodMap = new LinkedHashMap<String, MethodDescriptor<T>>();
105 ClassDescriptor<T> classDescriptor = new ClassDescriptor<T>(type, methodMap, new Description(type));
106 for (Method method : methods) {
107 MethodDescriptor<T> methodDescriptor = create(classDescriptor, method.getName().toLowerCase(), method);
108 methodMap.put(methodDescriptor.getName(), methodDescriptor);
109 }
110 for (ParameterDescriptor parameter : parameters(type)) {
111 classDescriptor.addParameter(parameter);
112 }
113 return classDescriptor;
114 }
115 }
116
117 private <T> MethodDescriptor<T> create(ClassDescriptor<T> classDescriptor, String name, Method method) {
118 Description info = new Description(method);
119 MethodDescriptor<T> methodDescriptor = new MethodDescriptor<T>(
120 classDescriptor,
121 method,
122 name,
123 info);
124
125 Type[] parameterTypes = method.getGenericParameterTypes();
126 Annotation[][] parameterAnnotationMatrix = method.getParameterAnnotations();
127 for (int i = 0;i < parameterAnnotationMatrix.length;i++) {
128
129 Annotation[] parameterAnnotations = parameterAnnotationMatrix[i];
130 Type parameterType = parameterTypes[i];
131 Tuple tuple = get(parameterAnnotations);
132
133 MethodArgumentBinding binding = new MethodArgumentBinding(i);
134 ParameterDescriptor parameter = create(
135 binding,
136 parameterType,
137 tuple.argumentAnn,
138 tuple.optionAnn,
139 tuple.required,
140 tuple.descriptionAnn,
141 tuple.ann);
142 if (parameter != null) {
143 methodDescriptor.addParameter(parameter);
144 } else {
145 log.log(Level.FINE, "Method argument with index " + i + " of method " + method + " is not annotated");
146 }
147 }
148 return methodDescriptor;
149 }
150
151 private ParameterDescriptor create(
152 Binding binding,
153 Type type,
154 Argument argumentAnn,
155 Option optionAnn,
156 boolean required,
157 Description info,
158 Annotation ann) throws IntrospectionException {
159
160 //
161 if (argumentAnn != null) {
162 if (optionAnn != null) {
163 throw new IntrospectionException();
164 }
165
166 //
167 return new BoundArgumentDescriptor(
168 binding,
169 argumentAnn.name(),
170 ParameterType.create(valueTypeFactory, type),
171 info,
172 required,
173 false,
174 argumentAnn.unquote(),
175 argumentAnn.completer(),
176 ann);
177 } else if (optionAnn != null) {
178 return new BoundOptionDescriptor(
179 binding,
180 ParameterType.create(valueTypeFactory, type),
181 Collections.unmodifiableList(Arrays.asList(optionAnn.names())),
182 info,
183 required,
184 false,
185 optionAnn.unquote(),
186 optionAnn.completer(),
187 ann);
188 } else {
189 return null;
190 }
191 }
192
193 private static Tuple get(Annotation... ab) {
194 Argument argumentAnn = null;
195 Option optionAnn = null;
196 Boolean required = null;
197 Description description = new Description(ab);
198 Annotation info = null;
199 for (Annotation parameterAnnotation : ab) {
200 if (parameterAnnotation instanceof Option) {
201 optionAnn = (Option)parameterAnnotation;
202 } else if (parameterAnnotation instanceof Argument) {
203 argumentAnn = (Argument)parameterAnnotation;
204 } else if (parameterAnnotation instanceof Required) {
205 required = ((Required)parameterAnnotation).value();
206 } else if (info == null) {
207
208 // Look at annotated annotations
209 Class<? extends Annotation> a = parameterAnnotation.annotationType();
210 if (a.getAnnotation(Option.class) != null) {
211 optionAnn = a.getAnnotation(Option.class);
212 info = parameterAnnotation;
213 } else if (a.getAnnotation(Argument.class) != null) {
214 argumentAnn = a.getAnnotation(Argument.class);
215 info = parameterAnnotation;
216 }
217
218 //
219 if (info != null) {
220
221 //
222 description = new Description(description, new Description(a));
223
224 //
225 if (required == null) {
226 Required metaReq = a.getAnnotation(Required.class);
227 if (metaReq != null) {
228 required = metaReq.value();
229 }
230 }
231 }
232 }
233 }
234
235 //
236 return new Tuple(argumentAnn, optionAnn, required != null && required,description, info);
237 }
238
239 /**
240 * Jus grouping some data for conveniency
241 */
242 protected static class Tuple {
243 final Argument argumentAnn;
244 final Option optionAnn;
245 final boolean required;
246 final Description descriptionAnn;
247 final Annotation ann;
248 private Tuple(Argument argumentAnn, Option optionAnn, boolean required, Description info, Annotation ann) {
249 this.argumentAnn = argumentAnn;
250 this.optionAnn = optionAnn;
251 this.required = required;
252 this.descriptionAnn = info;
253 this.ann = ann;
254 }
255 }
256
257 private List<ParameterDescriptor> parameters(Class<?> introspected) throws IntrospectionException {
258 List<ParameterDescriptor> parameters;
259 Class<?> superIntrospected = introspected.getSuperclass();
260 if (superIntrospected == null) {
261 parameters = new ArrayList<ParameterDescriptor>();
262 } else {
263 parameters = parameters(superIntrospected);
264 for (Field f : introspected.getDeclaredFields()) {
265 Tuple tuple = get(f.getAnnotations());
266 ClassFieldBinding binding = new ClassFieldBinding(f);
267 ParameterDescriptor parameter = create(
268 binding,
269 f.getGenericType(),
270 tuple.argumentAnn,
271 tuple.optionAnn,
272 tuple.required,
273 tuple.descriptionAnn,
274 tuple.ann);
275 if (parameter != null) {
276 parameters.add(parameter);
277 }
278 }
279 }
280 return parameters;
281 }
282 }