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.Field;
037import java.lang.reflect.Type;
038import java.util.HashSet;
039import java.util.Map;
040import java.util.Set;
041
042class ClassDescriptor<T> extends CommandDescriptorImpl<T> {
043
044  /** . */
045  private final Class<T> type;
046
047  /** . */
048  private final Map<String, MethodDescriptor<T>> methods;
049
050  ClassDescriptor(Class<T> type, Map<String, MethodDescriptor<T>> methods, Description info) throws IntrospectionException {
051    super(type.getSimpleName().toLowerCase(), info);
052
053    //
054    this.methods = methods;
055    this.type = type;
056  }
057
058  @Override
059  protected void addParameter(ParameterDescriptor parameter) throws IntrospectionException {
060
061    // Check we can add the option
062    if (parameter instanceof OptionDescriptor) {
063      OptionDescriptor option = (OptionDescriptor)parameter;
064      Set<String> blah = new HashSet<String>();
065      for (String optionName : option.getNames()) {
066        blah.add((optionName.length() == 1 ? "-" : "--") + optionName);
067      }
068      for (MethodDescriptor<T> method : methods.values()) {
069        Set<String> diff = new HashSet<String>(method.getOptionNames());
070        diff.retainAll(blah);
071        if (diff.size() > 0) {
072          throw new IntrospectionException("Cannot add method " + method.getName() + " because it has common "
073          + " options with its class: " + diff);
074        }
075      }
076    }
077
078    //
079    super.addParameter(parameter);
080  }
081
082  @Override
083  public CommandInvoker<T, ?> getInvoker(final InvocationMatch<T> match) {
084
085    if (Runnable.class.isAssignableFrom(type)) {
086      return new CommandInvoker<T, Void>() {
087        @Override
088        public InvocationMatch<T> getMatch() {
089          return match;
090        }
091        @Override
092        public Class<Void> getReturnType() {
093          return Void.class;
094        }
095        @Override
096        public Type getGenericReturnType() {
097          return Void.class;
098        }
099        @Override
100        public Class<?>[] getParameterTypes() {
101          return new Class<?>[0];
102        }
103        @Override
104        public Type[] getGenericParameterTypes() {
105          return new Type[0];
106        }
107        @Override
108        public Void invoke(Resolver resolver, T command) throws InvocationException, SyntaxException {
109          configure(match, command);
110          Runnable runnable = Runnable.class.cast(command);
111          try {
112            runnable.run();
113          }
114          catch (Exception e) {
115            throw new InvocationException(e);
116          }
117          return null;
118        }
119      };
120    } else {
121      return null;
122    }
123  }
124
125  void configure(InvocationMatch<T> classMatch, T command) throws InvocationException, SyntaxException {
126    for (ParameterDescriptor parameter : getParameters()) {
127      ParameterMatch match = classMatch.getParameter(parameter);
128      if (match == null) {
129        if (parameter.isRequired()) {
130          if (parameter instanceof ArgumentDescriptor) {
131            ArgumentDescriptor argument = (ArgumentDescriptor)parameter;
132            throw new SyntaxException("Missing argument " + argument.getName());
133          } else {
134            OptionDescriptor option = (OptionDescriptor)parameter;
135            throw new SyntaxException("Missing option " + option.getNames());
136          }
137        }
138      } else {
139        Object value = match.computeValue();
140        Field f = ((ClassFieldBinding)parameter.getBinding()).getField();
141        try {
142          f.setAccessible(true);
143          f.set(command, value);
144        }
145        catch (Exception e) {
146          throw new InvocationException(e.getMessage(), e);
147        }
148      }
149    }
150  }
151
152  @Override
153  public CommandDescriptor<T> getOwner() {
154    return null;
155  }
156
157  @Override
158  public Class<T> getType() {
159    return type;
160  }
161
162  @Override
163  public Map<String, ? extends MethodDescriptor<T>> getSubordinates() {
164    return methods;
165  }
166
167  public MethodDescriptor<T> getSubordinate(String name) {
168    return methods.get(name);
169  }
170}