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    }