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 }