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