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