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