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.ArgumentDescriptor;
023    import org.crsh.cli.descriptor.CommandDescriptor;
024    import org.crsh.cli.descriptor.Description;
025    import org.crsh.cli.impl.descriptor.IntrospectionException;
026    import org.crsh.cli.descriptor.OptionDescriptor;
027    import org.crsh.cli.descriptor.ParameterDescriptor;
028    import org.crsh.cli.impl.SyntaxException;
029    import org.crsh.cli.impl.invocation.CommandInvoker;
030    import org.crsh.cli.impl.invocation.InvocationException;
031    import org.crsh.cli.impl.invocation.InvocationMatch;
032    import org.crsh.cli.impl.invocation.ParameterMatch;
033    
034    import java.lang.reflect.InvocationTargetException;
035    import java.lang.reflect.Method;
036    import java.lang.reflect.Type;
037    import java.util.Collections;
038    import java.util.Map;
039    
040    class MethodDescriptor<T> extends ObjectCommandDescriptor<T> {
041    
042      /** . */
043      private final ClassDescriptor<T> owner;
044    
045      /** . */
046      private final Method method;
047    
048      public MethodDescriptor(
049        ClassDescriptor<T> owner,
050        Method method,
051        String name,
052        Description info) throws IntrospectionException {
053        super(name, info);
054    
055        //
056        this.owner = owner;
057        this.method = method;
058      }
059    
060      @Override
061      protected void addParameter(ParameterDescriptor parameter) throws IntrospectionException, NullPointerException, IllegalArgumentException {
062        super.addParameter(parameter);
063      }
064    
065      @Override
066      public CommandDescriptor<Instance<T>> getOwner() {
067        return owner;
068      }
069    
070      @Override
071      public Map<String, ? extends CommandDescriptor<Instance<T>>> getSubordinates() {
072        return Collections.emptyMap();
073      }
074    
075      public Method getMethod() {
076        return method;
077      }
078    
079      @Override
080      public CommandInvoker<Instance<T>, ?> getInvoker(InvocationMatch<Instance<T>> match) {
081        Class<?> type = method.getReturnType();
082        return getInvoker2(match, type);
083      }
084    
085      static void bind(InvocationMatch<?> match, Iterable<ParameterDescriptor> parameters, Object target, Object[] args) throws SyntaxException, InvocationException {
086        for (ParameterDescriptor parameter : parameters) {
087          ParameterMatch parameterMatch = match.getParameter(parameter);
088          Object value = parameterMatch != null ? parameterMatch.computeValue() : null;
089          if (value == null) {
090            if (parameter.getDeclaredType().isPrimitive() || parameter.isRequired()) {
091              if (parameter instanceof ArgumentDescriptor) {
092                ArgumentDescriptor argument = (ArgumentDescriptor)parameter;
093                throw new SyntaxException("Missing argument " + argument.getName());
094              } else {
095                OptionDescriptor option = (OptionDescriptor)parameter;
096                throw new SyntaxException("Missing option " + option.getNames());
097              }
098            }
099          } else {
100            ((Binding)parameter).set(target, args, value);
101          }
102        }
103      }
104    
105      private <V> ObjectCommandInvoker<T, V> getInvoker2(final InvocationMatch<Instance<T>> match, final Class<V> returnType) {
106        return new ObjectCommandInvoker<T, V>(match) {
107          @Override
108          public Class<V> getReturnType() {
109            return returnType;
110          }
111          @Override
112          public Type getGenericReturnType() {
113            return getMethod().getGenericReturnType();
114          }
115          @Override
116          public Class<?>[] getParameterTypes() {
117            return getMethod().getParameterTypes();
118          }
119          @Override
120          public Type[] getGenericParameterTypes() {
121            return getMethod().getGenericParameterTypes();
122          }
123          @Override
124          public V invoke(Instance<T> commandInstance) throws InvocationException, SyntaxException {
125    
126            //
127            T command = null;
128            try {
129              command = commandInstance.get();
130            }
131            catch (Exception e) {
132              throw new InvocationException(e);
133            }
134    
135            //
136            if (owner != null) {
137              bind(match.owner(), owner.getParameters(), command, Util.EMPTY_ARGS);
138            }
139    
140            // Prepare invocation
141            Method m = getMethod();
142            Class<?>[] parameterTypes = m.getParameterTypes();
143            Object[] mArgs = new Object[parameterTypes.length];
144    
145            // Bind method parameter first
146            bind(match, getParameters(), command, mArgs);
147    
148            // Fill missing contextual parameters and make primitive check
149            for (int i = 0;i < mArgs.length;i++) {
150              Class<?> parameterType = parameterTypes[i];
151              if (mArgs[i] == null) {
152                Object v = commandInstance.resolve(parameterType);
153                if (v != null) {
154                  mArgs[i] = v;
155                }
156              }
157              if (mArgs[i] == null && parameterType.isPrimitive()) {
158                throw new SyntaxException("Method argument at position " + i + " of " + m + " is missing");
159              }
160            }
161    
162            // Perform method invocation
163            try {
164              Object ret = m.invoke(command, mArgs);
165              return returnType.cast(ret);
166            }
167            catch (InvocationTargetException e) {
168              Throwable t = e.getTargetException();
169              if (t instanceof Error) {
170                throw (Error)t;
171              } else {
172                throw new InvocationException(t);
173              }
174            }
175            catch (IllegalAccessException t) {
176              throw new InvocationException(t);
177            }
178          }
179        };
180      }
181    }