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.IntrospectionException;
026import org.crsh.cli.descriptor.OptionDescriptor;
027import org.crsh.cli.descriptor.ParameterDescriptor;
028import org.crsh.cli.impl.SyntaxException;
029import org.crsh.cli.impl.invocation.CommandInvoker;
030import org.crsh.cli.impl.invocation.InvocationException;
031import org.crsh.cli.impl.invocation.InvocationMatch;
032import org.crsh.cli.impl.invocation.ParameterMatch;
033
034import java.lang.reflect.InvocationTargetException;
035import java.lang.reflect.Method;
036import java.lang.reflect.Type;
037import java.util.Collections;
038import java.util.Map;
039
040class 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}