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
020package org.crsh.cli.impl.lang;
021
022import org.crsh.cli.descriptor.ArgumentDescriptor;
023import org.crsh.cli.descriptor.CommandDescriptor;
024import org.crsh.cli.descriptor.Description;
025import org.crsh.cli.impl.descriptor.CommandDescriptorImpl;
026import org.crsh.cli.impl.descriptor.IntrospectionException;
027import org.crsh.cli.descriptor.OptionDescriptor;
028import org.crsh.cli.descriptor.ParameterDescriptor;
029import org.crsh.cli.SyntaxException;
030import org.crsh.cli.impl.invocation.CommandInvoker;
031import org.crsh.cli.impl.invocation.InvocationException;
032import org.crsh.cli.impl.invocation.InvocationMatch;
033import org.crsh.cli.impl.invocation.ParameterMatch;
034import org.crsh.cli.impl.invocation.Resolver;
035
036import java.lang.reflect.InvocationTargetException;
037import java.lang.reflect.Method;
038import java.lang.reflect.Type;
039import java.util.Collections;
040import java.util.Map;
041
042class MethodDescriptor<T> extends CommandDescriptorImpl<T> {
043
044  /** . */
045  private final ClassDescriptor<T> owner;
046
047  /** . */
048  private final Method method;
049
050  /** . */
051  private final int size;
052
053  public MethodDescriptor(
054    ClassDescriptor<T> owner,
055    Method method,
056    String name,
057    Description info) throws IntrospectionException {
058    super(name, info);
059
060    //
061    this.owner = owner;
062    this.method = method;
063    this.size = method.getParameterTypes().length;
064  }
065
066  /**
067   * Returns the parameter descriptor for the specified method parameter index.
068   *
069   * @param index the parameter index
070   * @return the parameter descriptor or null if none can be bound
071   * @throws IndexOutOfBoundsException if the index is not valid
072   */
073  ParameterDescriptor getParameter(int index) throws IndexOutOfBoundsException {
074    if (index < 0 || index >= size) {
075      throw new IndexOutOfBoundsException("Bad index value " + index);
076    }
077    for (ParameterDescriptor argument : getParameters()) {
078      if (((MethodArgumentBinding)argument.getBinding()).getIndex() == index) {
079        return argument;
080      }
081    }
082    return null;
083  }
084
085  @Override
086  protected void addParameter(ParameterDescriptor parameter) throws IntrospectionException, NullPointerException, IllegalArgumentException {
087    super.addParameter(parameter);
088  }
089
090  @Override
091  public CommandDescriptor<T> getOwner() {
092    return owner;
093  }
094
095  @Override
096  public Map<String, ? extends CommandDescriptorImpl<T>> getSubordinates() {
097    return Collections.emptyMap();
098  }
099
100  @Override
101  public CommandDescriptorImpl<T> getSubordinate(String name) {
102    return null;
103  }
104
105  public Method getMethod() {
106    return method;
107  }
108
109  @Override
110  public Class<T> getType() {
111    return owner.getType();
112  }
113
114  public CommandInvoker<T, ?> getInvoker(final InvocationMatch<T> match) {
115    Class<?> type = method.getReturnType();
116    return getInvoker2(match, type);
117  }
118
119  private <V> CommandInvoker<T, V> getInvoker2(final InvocationMatch<T> _match, final Class<V> returnType) {
120    return new CommandInvoker<T, V>() {
121      @Override
122      public InvocationMatch<T> getMatch() {
123        return _match;
124      }
125      @Override
126      public Class<V> getReturnType() {
127        return returnType;
128      }
129      @Override
130      public Type getGenericReturnType() {
131        return getMethod().getGenericReturnType();
132      }
133      @Override
134      public Class<?>[] getParameterTypes() {
135        return getMethod().getParameterTypes();
136      }
137      @Override
138      public Type[] getGenericParameterTypes() {
139        return getMethod().getGenericParameterTypes();
140      }
141      @Override
142      public V invoke(Resolver resolver, T command) throws InvocationException, SyntaxException {
143
144        //
145        owner.configure(_match.owner(), command);
146
147        // Prepare invocation
148        Method m = getMethod();
149        Class<?>[] parameterTypes = m.getParameterTypes();
150        Object[] mArgs = new Object[parameterTypes.length];
151        for (int i = 0;i < mArgs.length;i++) {
152          ParameterDescriptor parameter = getParameter(i);
153
154          //
155          Class<?> parameterType = parameterTypes[i];
156
157          Object v;
158          if (parameter == null) {
159            // Attempt to obtain from resolver
160            v = resolver.resolve(parameterType);
161          } else {
162            ParameterMatch match = _match.getParameter(parameter);
163            if (match != null) {
164              v = match.computeValue();
165            } else {
166              v = null;
167            }
168          }
169
170          //
171          if (v == null) {
172            if (parameterType.isPrimitive() || parameter.isRequired()) {
173              if (parameter instanceof ArgumentDescriptor) {
174                ArgumentDescriptor argument = (ArgumentDescriptor)parameter;
175                throw new SyntaxException("Missing argument " + argument.getName());
176              } else {
177                OptionDescriptor option = (OptionDescriptor)parameter;
178                throw new SyntaxException("Missing option " + option.getNames());
179              }
180            }
181          }
182
183          //
184          mArgs[i] = v;
185        }
186
187        // Perform method invocation
188        try {
189          Object ret = m.invoke(command, mArgs);
190          return returnType.cast(ret);
191        }
192        catch (InvocationTargetException e) {
193          Throwable t = e.getTargetException();
194          if (t instanceof Error) {
195            throw (Error)t;
196          } else {
197            throw new InvocationException(t);
198          }
199        }
200        catch (IllegalAccessException t) {
201          throw new InvocationException(t);
202        }
203      }
204    };
205  }
206}