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