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