001    /*
002     * Copyright (C) 2010 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.binding.ClassFieldBinding;
023    import static org.crsh.cmdline.Util.indent;
024    
025    import org.slf4j.Logger;
026    import org.slf4j.LoggerFactory;
027    
028    import java.io.IOException;
029    import java.lang.reflect.Method;
030    import java.util.ArrayList;
031    import java.util.Collections;
032    import java.util.Formatter;
033    import java.util.HashSet;
034    import java.util.LinkedHashMap;
035    import java.util.List;
036    import java.util.Map;
037    import java.util.Set;
038    
039    /**
040     * A command backed by a class.
041     *
042     * @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a>
043     * @version $Revision$
044     */
045    public class ClassDescriptor<T> extends CommandDescriptor<T, ClassFieldBinding> {
046    
047      /** . */
048      private static final Logger log = LoggerFactory.getLogger(ClassDescriptor.class);
049    
050      /** . */
051      private final Class<T> type;
052    
053      /** . */
054      private final Map<String, MethodDescriptor<T>> methodMap;
055    
056      public ClassDescriptor(Class<T> type, Description info) throws IntrospectionException {
057        super(type.getSimpleName().toLowerCase(), info);
058    
059        // Make sure we can add it
060        Map<String, MethodDescriptor<T>> methodMap = new LinkedHashMap<String, MethodDescriptor<T>>();
061        for (MethodDescriptor<T> method : commands(type)) {
062          //
063          methodMap.put(method.getName(), method);
064        }
065    
066        //
067        this.methodMap = methodMap;
068        this.type = type;
069      }
070    
071      @Override
072      void addParameter(ParameterDescriptor<ClassFieldBinding> parameter) throws IntrospectionException {
073    
074        // Check we can add the option
075        if (parameter instanceof OptionDescriptor<?>) {
076          OptionDescriptor<ClassFieldBinding> option = (OptionDescriptor<ClassFieldBinding>)parameter;
077          Set<String> blah = new HashSet<String>();
078          for (String optionName : option.getNames()) {
079            blah.add((optionName.length() == 1 ? "-" : "--") + optionName);
080          }
081          for (MethodDescriptor<T> method : methodMap.values()) {
082            Set<String> diff = new HashSet<String>(method.getOptionNames());
083            diff.retainAll(blah);
084            if (diff.size() > 0) {
085              throw new IntrospectionException("Cannot add method " + method.getName() + " because it has common "
086              + " options with its class: " + diff);
087            }
088          }
089        }
090    
091        //
092        super.addParameter(parameter);
093      }
094    
095      @Override
096      public Class<T> getType() {
097        return type;
098      }
099    
100      @Override
101      public Map<String, ? extends CommandDescriptor<T, ?>> getSubordinates() {
102        return methodMap;
103      }
104    
105      @Override
106      public OptionDescriptor<?> findOption(String name) {
107        return getOption(name);
108      }
109    
110      @Override
111      public void printUsage(Appendable writer) throws IOException {
112        if (methodMap.size() == 1) {
113          methodMap.values().iterator().next().printUsage(writer);
114        } else {
115          writer.append("usage: ").append(getName());
116          for (OptionDescriptor<?> option : getOptions()) {
117            option.printUsage(writer);
118          }
119          writer.append(" COMMAND [ARGS]\n\n");
120          writer.append("The most commonly used ").append(getName()).append(" commands are:\n");
121          String format = "   %1$-16s %2$s\n";
122          for (MethodDescriptor<T> method : getMethods()) {
123            Formatter formatter = new Formatter(writer);
124            formatter.format(format, method.getName(), method.getUsage());
125          }
126        }
127      }
128    
129      public void printMan(Appendable writer) throws IOException {
130        if (methodMap.size() == 1) {
131          methodMap.values().iterator().next().printMan(writer);
132        } else {
133    
134          // Name
135          writer.append("NAME\n");
136          writer.append(Util.MAN_TAB).append(getName());
137          if (getUsage().length() > 0) {
138            writer.append(" - ").append(getUsage());
139          }
140          writer.append("\n\n");
141    
142          // Synopsis
143          writer.append("SYNOPSIS\n");
144          writer.append(Util.MAN_TAB).append(getName());
145          for (OptionDescriptor<?> option : getOptions()) {
146            writer.append(" ");
147            option.printUsage(writer);
148          }
149          writer.append(" COMMAND [ARGS]\n\n");
150    
151          //
152          String man = getDescription().getMan();
153          if (man.length() > 0) {
154            writer.append("DESCRIPTION\n");
155            indent(Util.MAN_TAB, man, writer);
156            writer.append("\n\n");
157          }
158    
159          // Common options
160          if (getOptions().size() > 0) {
161            writer.append("PARAMETERS\n");
162            for (OptionDescriptor<?> option : getOptions()) {
163              writer.append(Util.MAN_TAB);
164              option.printUsage(writer);
165              String optionText = option.getDescription().getBestEffortMan();
166              if (optionText.length() > 0) {
167                writer.append("\n");
168                indent(Util.MAN_TAB_EXTRA, optionText, writer);
169              }
170              writer.append("\n\n");
171            }
172          }
173    
174          //
175          writer.append("COMMANDS\n");
176          for (MethodDescriptor<T> method : getMethods()) {
177            writer.append(Util.MAN_TAB).append(method.getName());
178            String methodText = method.getDescription().getBestEffortMan();
179            if (methodText.length() > 0) {
180              writer.append("\n");
181              indent(Util.MAN_TAB_EXTRA, methodText, writer);
182            }
183            writer.append("\n\n");
184          }
185        }
186      }
187    
188      public Iterable<MethodDescriptor<T>> getMethods() {
189        return methodMap.values();
190      }
191    
192      public MethodDescriptor<T> getMethod(String name) {
193        return methodMap.get(name);
194      }
195    
196      private List<MethodDescriptor<T>> commands(Class<?> introspected) throws IntrospectionException {
197        List<MethodDescriptor<T>> commands;
198        Class<?> superIntrospected = introspected.getSuperclass();
199        if (superIntrospected == null) {
200          commands = new ArrayList<MethodDescriptor<T>>();
201        } else {
202          commands = commands(superIntrospected);
203          for (Method m : introspected.getDeclaredMethods()) {
204            MethodDescriptor<T> mDesc = CommandFactory.create(this, m);
205            if (mDesc != null) {
206              commands.add(mDesc);
207            }
208          }
209        }
210        return commands;
211      }
212    }