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 }