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