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    }