001 /*
002 * Copyright (C) 2010 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.slf4j.Logger;
030 import org.slf4j.LoggerFactory;
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.List;
040
041 /**
042 * @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a>
043 * @version $Revision$
044 */
045 public class CommandFactory {
046
047 /** . */
048 private static final Logger log = LoggerFactory.getLogger(CommandFactory.class);
049
050 public static <T> ClassDescriptor<T> create(Class<T> type) throws IntrospectionException {
051 ClassDescriptor<T> descriptor = new ClassDescriptor<T>(type, new Description(type));
052 for (ParameterDescriptor<ClassFieldBinding> parameter : parameters(type)) {
053 descriptor.addParameter(parameter);
054 }
055 return descriptor;
056 }
057
058 protected static <B extends TypeBinding> ParameterDescriptor<B> create(
059 B binding,
060 Type type,
061 Argument argumentAnn,
062 Option optionAnn,
063 boolean required,
064 Description info,
065 Annotation ann) throws IntrospectionException {
066
067 //
068 if (argumentAnn != null) {
069 if (optionAnn != null) {
070 throw new IntrospectionException();
071 }
072
073 //
074 return new ArgumentDescriptor<B>(
075 binding,
076 argumentAnn.name(),
077 type,
078 info,
079 required,
080 argumentAnn.password(),
081 argumentAnn.unquote(),
082 argumentAnn.completer(),
083 ann);
084 } else if (optionAnn != null) {
085 return new OptionDescriptor<B>(
086 binding,
087 type,
088 Collections.unmodifiableList(Arrays.asList(optionAnn.names())),
089 info,
090 required,
091 optionAnn.password(),
092 optionAnn.unquote(),
093 optionAnn.completer(),
094 ann);
095 } else {
096 return null;
097 }
098 }
099
100 protected static Tuple get(Annotation... ab) {
101 Argument argumentAnn = null;
102 Option optionAnn = null;
103 Boolean required = null;
104 Description description = new Description(ab);
105 Annotation info = null;
106 for (Annotation parameterAnnotation : ab) {
107 if (parameterAnnotation instanceof Option) {
108 optionAnn = (Option)parameterAnnotation;
109 } else if (parameterAnnotation instanceof Argument) {
110 argumentAnn = (Argument)parameterAnnotation;
111 } else if (parameterAnnotation instanceof Required) {
112 required = ((Required)parameterAnnotation).value();
113 } else if (info == null) {
114
115 // Look at annotated annotations
116 Class<? extends Annotation> a = parameterAnnotation.annotationType();
117 if (a.getAnnotation(Option.class) != null) {
118 optionAnn = a.getAnnotation(Option.class);
119 info = parameterAnnotation;
120 } else if (a.getAnnotation(Argument.class) != null) {
121 argumentAnn = a.getAnnotation(Argument.class);
122 info = parameterAnnotation;
123 }
124
125 //
126 if (info != null) {
127
128 //
129 description = new Description(description, new Description(a));
130
131 //
132 if (required == null) {
133 Required metaReq = a.getAnnotation(Required.class);
134 if (metaReq != null) {
135 required = metaReq.value();
136 }
137 }
138 }
139 }
140 }
141
142 //
143 return new Tuple(argumentAnn, optionAnn, required != null && required,description, info);
144 }
145
146 public static <T> MethodDescriptor<T> create(ClassDescriptor<T> owner, Method m) throws IntrospectionException {
147 Command command = m.getAnnotation(Command.class);
148 if (command != null) {
149
150 //
151 Description info = new Description(m);
152 MethodDescriptor<T> descriptor = new MethodDescriptor<T>(
153 owner,
154 m,
155 m.getName().toLowerCase(),
156 info);
157
158 Type[] parameterTypes = m.getGenericParameterTypes();
159 Annotation[][] parameterAnnotationMatrix = m.getParameterAnnotations();
160 for (int i = 0;i < parameterAnnotationMatrix.length;i++) {
161
162 Annotation[] parameterAnnotations = parameterAnnotationMatrix[i];
163 Type parameterType = parameterTypes[i];
164 Tuple tuple = get(parameterAnnotations);
165
166 MethodArgumentBinding binding = new MethodArgumentBinding(i);
167 ParameterDescriptor<MethodArgumentBinding> parameter = create(
168 binding,
169 parameterType,
170 tuple.argumentAnn,
171 tuple.optionAnn,
172 tuple.required,
173 tuple.descriptionAnn,
174 tuple.ann);
175 if (parameter != null) {
176 descriptor.addParameter(parameter);
177 } else {
178 log.debug("Method argument with index " + i + " of method " + m + " is not annotated");
179 }
180 }
181
182 //
183 return descriptor;
184 } else {
185 return null;
186 }
187 }
188
189 /**
190 * Jus grouping some data for conveniency
191 */
192 protected static class Tuple {
193 final Argument argumentAnn;
194 final Option optionAnn;
195 final boolean required;
196 final Description descriptionAnn;
197 final Annotation ann;
198 private Tuple(Argument argumentAnn, Option optionAnn, boolean required, Description info, Annotation ann) {
199 this.argumentAnn = argumentAnn;
200 this.optionAnn = optionAnn;
201 this.required = required;
202 this.descriptionAnn = info;
203 this.ann = ann;
204 }
205 }
206
207 private static List<ParameterDescriptor<ClassFieldBinding>> parameters(Class<?> introspected) throws IntrospectionException {
208 List<ParameterDescriptor<ClassFieldBinding>> parameters;
209 Class<?> superIntrospected = introspected.getSuperclass();
210 if (superIntrospected == null) {
211 parameters = new ArrayList<ParameterDescriptor<ClassFieldBinding>>();
212 } else {
213 parameters = parameters(superIntrospected);
214 for (Field f : introspected.getDeclaredFields()) {
215 Tuple tuple = CommandFactory.get(f.getAnnotations());
216 ClassFieldBinding binding = new ClassFieldBinding(f);
217 ParameterDescriptor<ClassFieldBinding> parameter = CommandFactory.create(
218 binding,
219 f.getGenericType(),
220 tuple.argumentAnn,
221 tuple.optionAnn,
222 tuple.required,
223 tuple.descriptionAnn,
224 tuple.ann);
225 if (parameter != null) {
226 parameters.add(parameter);
227 }
228 }
229 }
230 return parameters;
231 }
232 }