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