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