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.cmdline;
021
022 import org.crsh.cmdline.annotations.Argument;
023 import org.crsh.cmdline.annotations.Command;
024 import org.crsh.cmdline.annotations.Option;
025 import org.crsh.cmdline.annotations.Required;
026 import org.crsh.cmdline.binding.ClassFieldBinding;
027 import org.crsh.cmdline.binding.MethodArgumentBinding;
028 import org.crsh.cmdline.binding.TypeBinding;
029 import org.crsh.cmdline.type.ValueTypeFactory;
030 import org.slf4j.Logger;
031 import org.slf4j.LoggerFactory;
032
033 import java.lang.annotation.Annotation;
034 import java.lang.reflect.Field;
035 import java.lang.reflect.Method;
036 import java.lang.reflect.Type;
037 import java.util.ArrayList;
038 import java.util.Arrays;
039 import java.util.Collections;
040 import java.util.LinkedHashMap;
041 import java.util.List;
042 import java.util.Map;
043
044 public class CommandFactory {
045
046 /** . */
047 public static final CommandFactory DEFAULT = new CommandFactory();
048
049 /** . */
050 private static final Logger log = LoggerFactory.getLogger(CommandFactory.class);
051
052 /** . */
053 private final ValueTypeFactory valueTypeFactory;
054
055 private CommandFactory() {
056 this.valueTypeFactory = ValueTypeFactory.DEFAULT;
057 }
058
059 public CommandFactory(ClassLoader loader) throws NullPointerException {
060 this(new ValueTypeFactory(loader));
061 }
062
063 public CommandFactory(ValueTypeFactory valueTypeFactory) throws NullPointerException {
064 if (valueTypeFactory == null) {
065 throw new NullPointerException("No null value type factory accepted");
066 }
067
068 //
069 this.valueTypeFactory = valueTypeFactory;
070 }
071
072 private <T> List<MethodDescriptor<T>> commands(ClassDescriptor descriptor, Class<T> type, Class<?> introspected) throws IntrospectionException {
073 List<MethodDescriptor<T>> commands;
074 Class<?> superIntrospected = introspected.getSuperclass();
075 if (superIntrospected == null) {
076 commands = new ArrayList<MethodDescriptor<T>>();
077 } else {
078 commands = commands(descriptor, type, superIntrospected);
079 for (Method m : introspected.getDeclaredMethods()) {
080 MethodDescriptor<T> mDesc = create(descriptor, m);
081 if (mDesc != null) {
082 commands.add(mDesc);
083 }
084 }
085 }
086 return commands;
087 }
088
089 public <T> ClassDescriptor<T> create(Class<T> type) throws IntrospectionException {
090
091 //
092 Map<String, MethodDescriptor<T>> methodMap = new LinkedHashMap<String, MethodDescriptor<T>>();
093 ClassDescriptor<T> descriptor = new ClassDescriptor<T>(type, methodMap, new Description(type));
094 for (MethodDescriptor<T> method : commands(descriptor, type, type)) {
095 methodMap.put(method.getName(), method);
096 }
097
098 //
099 for (ParameterDescriptor<ClassFieldBinding> parameter : parameters(type)) {
100 descriptor.addParameter(parameter);
101 }
102
103 //
104 return descriptor;
105 }
106
107 protected <B extends TypeBinding> ParameterDescriptor<B> create(
108 B binding,
109 Type type,
110 Argument argumentAnn,
111 Option optionAnn,
112 boolean required,
113 Description info,
114 Annotation ann) throws IntrospectionException {
115
116 //
117 if (argumentAnn != null) {
118 if (optionAnn != null) {
119 throw new IntrospectionException();
120 }
121
122 //
123 return new ArgumentDescriptor<B>(
124 binding,
125 argumentAnn.name(),
126 ParameterType.create(valueTypeFactory, type),
127 info,
128 required,
129 argumentAnn.password(),
130 argumentAnn.unquote(),
131 argumentAnn.completer(),
132 ann);
133 } else if (optionAnn != null) {
134 return new OptionDescriptor<B>(
135 binding,
136 ParameterType.create(valueTypeFactory, type),
137 Collections.unmodifiableList(Arrays.asList(optionAnn.names())),
138 info,
139 required,
140 optionAnn.password(),
141 optionAnn.unquote(),
142 optionAnn.completer(),
143 ann);
144 } else {
145 return null;
146 }
147 }
148
149 protected static Tuple get(Annotation... ab) {
150 Argument argumentAnn = null;
151 Option optionAnn = null;
152 Boolean required = null;
153 Description description = new Description(ab);
154 Annotation info = null;
155 for (Annotation parameterAnnotation : ab) {
156 if (parameterAnnotation instanceof Option) {
157 optionAnn = (Option)parameterAnnotation;
158 } else if (parameterAnnotation instanceof Argument) {
159 argumentAnn = (Argument)parameterAnnotation;
160 } else if (parameterAnnotation instanceof Required) {
161 required = ((Required)parameterAnnotation).value();
162 } else if (info == null) {
163
164 // Look at annotated annotations
165 Class<? extends Annotation> a = parameterAnnotation.annotationType();
166 if (a.getAnnotation(Option.class) != null) {
167 optionAnn = a.getAnnotation(Option.class);
168 info = parameterAnnotation;
169 } else if (a.getAnnotation(Argument.class) != null) {
170 argumentAnn = a.getAnnotation(Argument.class);
171 info = parameterAnnotation;
172 }
173
174 //
175 if (info != null) {
176
177 //
178 description = new Description(description, new Description(a));
179
180 //
181 if (required == null) {
182 Required metaReq = a.getAnnotation(Required.class);
183 if (metaReq != null) {
184 required = metaReq.value();
185 }
186 }
187 }
188 }
189 }
190
191 //
192 return new Tuple(argumentAnn, optionAnn, required != null && required,description, info);
193 }
194
195 public <T> MethodDescriptor<T> create(ClassDescriptor<T> owner, Method m) throws IntrospectionException {
196 Command command = m.getAnnotation(Command.class);
197 if (command != null) {
198
199 //
200 Description info = new Description(m);
201 MethodDescriptor<T> descriptor = new MethodDescriptor<T>(
202 owner,
203 m,
204 m.getName().toLowerCase(),
205 info);
206
207 Type[] parameterTypes = m.getGenericParameterTypes();
208 Annotation[][] parameterAnnotationMatrix = m.getParameterAnnotations();
209 for (int i = 0;i < parameterAnnotationMatrix.length;i++) {
210
211 Annotation[] parameterAnnotations = parameterAnnotationMatrix[i];
212 Type parameterType = parameterTypes[i];
213 Tuple tuple = get(parameterAnnotations);
214
215 MethodArgumentBinding binding = new MethodArgumentBinding(i);
216 ParameterDescriptor<MethodArgumentBinding> parameter = create(
217 binding,
218 parameterType,
219 tuple.argumentAnn,
220 tuple.optionAnn,
221 tuple.required,
222 tuple.descriptionAnn,
223 tuple.ann);
224 if (parameter != null) {
225 descriptor.addParameter(parameter);
226 } else {
227 log.debug("Method argument with index " + i + " of method " + m + " is not annotated");
228 }
229 }
230
231 //
232 return descriptor;
233 } else {
234 return null;
235 }
236 }
237
238 /**
239 * Jus grouping some data for conveniency
240 */
241 protected static class Tuple {
242 final Argument argumentAnn;
243 final Option optionAnn;
244 final boolean required;
245 final Description descriptionAnn;
246 final Annotation ann;
247 private Tuple(Argument argumentAnn, Option optionAnn, boolean required, Description info, Annotation ann) {
248 this.argumentAnn = argumentAnn;
249 this.optionAnn = optionAnn;
250 this.required = required;
251 this.descriptionAnn = info;
252 this.ann = ann;
253 }
254 }
255
256 private List<ParameterDescriptor<ClassFieldBinding>> parameters(Class<?> introspected) throws IntrospectionException {
257 List<ParameterDescriptor<ClassFieldBinding>> parameters;
258 Class<?> superIntrospected = introspected.getSuperclass();
259 if (superIntrospected == null) {
260 parameters = new ArrayList<ParameterDescriptor<ClassFieldBinding>>();
261 } else {
262 parameters = parameters(superIntrospected);
263 for (Field f : introspected.getDeclaredFields()) {
264 Tuple tuple = CommandFactory.get(f.getAnnotations());
265 ClassFieldBinding binding = new ClassFieldBinding(f);
266 ParameterDescriptor<ClassFieldBinding> parameter = create(
267 binding,
268 f.getGenericType(),
269 tuple.argumentAnn,
270 tuple.optionAnn,
271 tuple.required,
272 tuple.descriptionAnn,
273 tuple.ann);
274 if (parameter != null) {
275 parameters.add(parameter);
276 }
277 }
278 }
279 return parameters;
280 }
281 }