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.cli.impl.lang; 021 022 import org.crsh.cli.Named; 023 import org.crsh.cli.descriptor.Description; 024 import org.crsh.cli.impl.descriptor.IntrospectionException; 025 import org.crsh.cli.descriptor.ParameterDescriptor; 026 import org.crsh.cli.impl.ParameterType; 027 import org.crsh.cli.Argument; 028 import org.crsh.cli.Command; 029 import org.crsh.cli.Option; 030 import org.crsh.cli.Required; 031 import org.crsh.cli.type.ValueTypeFactory; 032 033 import java.lang.annotation.Annotation; 034 import java.lang.reflect.Field; 035 import java.lang.reflect.Method; 036 import java.lang.reflect.Type; 037 import java.util.ArrayList; 038 import java.util.Arrays; 039 import java.util.Collections; 040 import java.util.LinkedHashMap; 041 import java.util.List; 042 import java.util.Map; 043 import java.util.logging.Level; 044 import java.util.logging.Logger; 045 046 /** @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a> */ 047 public class CommandFactory { 048 049 /** . */ 050 public static final CommandFactory DEFAULT = new CommandFactory(); 051 052 /** . */ 053 private static final Logger log = Logger.getLogger(CommandFactory.class.getName()); 054 055 /** . */ 056 protected final ValueTypeFactory valueTypeFactory; 057 058 public CommandFactory() { 059 this.valueTypeFactory = ValueTypeFactory.DEFAULT; 060 } 061 062 public CommandFactory(ClassLoader loader) throws NullPointerException { 063 this(new ValueTypeFactory(loader)); 064 } 065 066 public CommandFactory(ValueTypeFactory valueTypeFactory) throws NullPointerException { 067 if (valueTypeFactory == null) { 068 throw new NullPointerException("No null value type factory accepted"); 069 } 070 071 // 072 this.valueTypeFactory = valueTypeFactory; 073 } 074 075 076 private List<Method> findAllMethods(Class<?> introspected) throws IntrospectionException { 077 List<Method> methods; 078 Class<?> superIntrospected = introspected.getSuperclass(); 079 if (superIntrospected == null) { 080 methods = new ArrayList<Method>(); 081 } else { 082 methods = findAllMethods(superIntrospected); 083 for (Method method : introspected.getDeclaredMethods()) { 084 if (method.getAnnotation(Command.class) != null) { 085 methods.add(method); 086 } 087 } 088 } 089 return methods; 090 } 091 092 public <T> ObjectCommandDescriptor<T> create(Class<T> type) throws IntrospectionException { 093 094 // Find all command methods 095 List<Method> methods = findAllMethods(type); 096 097 // 098 String commandName; 099 if (type.getAnnotation(Named.class) != null) { 100 commandName = type.getAnnotation(Named.class).value(); 101 } else { 102 commandName = type.getSimpleName(); 103 } 104 105 // 106 if (methods.size() == 1 && methods.get(0).getName().equals("main")) { 107 MethodDescriptor<T> methodDescriptor = create(null, commandName, methods.get(0)); 108 for (ParameterDescriptor parameter : parameters(type)) { 109 methodDescriptor.addParameter(parameter); 110 } 111 return methodDescriptor; 112 } else { 113 Map<String, MethodDescriptor<T>> methodMap = new LinkedHashMap<String, MethodDescriptor<T>>(); 114 ClassDescriptor<T> classDescriptor = new ClassDescriptor<T>(type, commandName, methodMap, new Description(type)); 115 for (Method method : methods) { 116 String methodName; 117 if (method.getAnnotation(Named.class) != null) { 118 methodName = method.getAnnotation(Named.class).value(); 119 } else { 120 methodName = method.getName(); 121 } 122 MethodDescriptor<T> methodDescriptor = create(classDescriptor, methodName, method); 123 methodMap.put(methodDescriptor.getName(), methodDescriptor); 124 } 125 for (ParameterDescriptor parameter : parameters(type)) { 126 classDescriptor.addParameter(parameter); 127 } 128 return classDescriptor; 129 } 130 } 131 132 private <T> MethodDescriptor<T> create(ClassDescriptor<T> classDescriptor, String name, Method method) throws IntrospectionException { 133 Description info = new Description(method); 134 MethodDescriptor<T> methodDescriptor = new MethodDescriptor<T>( 135 classDescriptor, 136 method, 137 name, 138 info); 139 140 Type[] parameterTypes = method.getGenericParameterTypes(); 141 Annotation[][] parameterAnnotationMatrix = method.getParameterAnnotations(); 142 for (int i = 0;i < parameterAnnotationMatrix.length;i++) { 143 144 Annotation[] parameterAnnotations = parameterAnnotationMatrix[i]; 145 Type parameterType = parameterTypes[i]; 146 Tuple tuple = get(parameterAnnotations); 147 148 MethodArgumentBinding binding = new MethodArgumentBinding(i); 149 ParameterDescriptor parameter = create( 150 binding, 151 parameterType, 152 tuple.argumentAnn, 153 tuple.optionAnn, 154 tuple.required, 155 tuple.descriptionAnn, 156 tuple.ann); 157 if (parameter != null) { 158 methodDescriptor.addParameter(parameter); 159 } else { 160 log.log(Level.FINE, "Method argument with index " + i + " of method " + method + " is not annotated"); 161 } 162 } 163 return methodDescriptor; 164 } 165 166 private ParameterDescriptor create( 167 Binding binding, 168 Type type, 169 Argument argumentAnn, 170 Option optionAnn, 171 boolean required, 172 Description info, 173 Annotation ann) throws IntrospectionException { 174 175 // 176 if (argumentAnn != null) { 177 if (optionAnn != null) { 178 throw new IntrospectionException(); 179 } 180 181 // 182 return new BoundArgumentDescriptor( 183 binding, 184 argumentAnn.name(), 185 ParameterType.create(valueTypeFactory, type), 186 info, 187 required, 188 false, 189 argumentAnn.unquote(), 190 argumentAnn.completer(), 191 ann); 192 } else if (optionAnn != null) { 193 return new BoundOptionDescriptor( 194 binding, 195 ParameterType.create(valueTypeFactory, type), 196 Collections.unmodifiableList(Arrays.asList(optionAnn.names())), 197 info, 198 required, 199 false, 200 optionAnn.unquote(), 201 optionAnn.completer(), 202 ann); 203 } else { 204 return null; 205 } 206 } 207 208 private static Tuple get(Annotation... ab) { 209 Argument argumentAnn = null; 210 Option optionAnn = null; 211 Boolean required = null; 212 Description description = new Description(ab); 213 Annotation info = null; 214 for (Annotation parameterAnnotation : ab) { 215 if (parameterAnnotation instanceof Option) { 216 optionAnn = (Option)parameterAnnotation; 217 } else if (parameterAnnotation instanceof Argument) { 218 argumentAnn = (Argument)parameterAnnotation; 219 } else if (parameterAnnotation instanceof Required) { 220 required = ((Required)parameterAnnotation).value(); 221 } else if (info == null) { 222 223 // Look at annotated annotations 224 Class<? extends Annotation> a = parameterAnnotation.annotationType(); 225 if (a.getAnnotation(Option.class) != null) { 226 optionAnn = a.getAnnotation(Option.class); 227 info = parameterAnnotation; 228 } else if (a.getAnnotation(Argument.class) != null) { 229 argumentAnn = a.getAnnotation(Argument.class); 230 info = parameterAnnotation; 231 } 232 233 // 234 if (info != null) { 235 236 // 237 description = new Description(description, new Description(a)); 238 239 // 240 if (required == null) { 241 Required metaReq = a.getAnnotation(Required.class); 242 if (metaReq != null) { 243 required = metaReq.value(); 244 } 245 } 246 } 247 } 248 } 249 250 // 251 return new Tuple(argumentAnn, optionAnn, required != null && required,description, info); 252 } 253 254 /** 255 * Jus grouping some data for conveniency 256 */ 257 protected static class Tuple { 258 final Argument argumentAnn; 259 final Option optionAnn; 260 final boolean required; 261 final Description descriptionAnn; 262 final Annotation ann; 263 private Tuple(Argument argumentAnn, Option optionAnn, boolean required, Description info, Annotation ann) { 264 this.argumentAnn = argumentAnn; 265 this.optionAnn = optionAnn; 266 this.required = required; 267 this.descriptionAnn = info; 268 this.ann = ann; 269 } 270 } 271 272 private List<ParameterDescriptor> parameters(Class<?> introspected) throws IntrospectionException { 273 List<ParameterDescriptor> parameters; 274 Class<?> superIntrospected = introspected.getSuperclass(); 275 if (superIntrospected == null) { 276 parameters = new ArrayList<ParameterDescriptor>(); 277 } else { 278 parameters = parameters(superIntrospected); 279 for (Field f : introspected.getDeclaredFields()) { 280 Tuple tuple = get(f.getAnnotations()); 281 ClassFieldBinding binding = new ClassFieldBinding(f); 282 ParameterDescriptor parameter = create( 283 binding, 284 f.getGenericType(), 285 tuple.argumentAnn, 286 tuple.optionAnn, 287 tuple.required, 288 tuple.descriptionAnn, 289 tuple.ann); 290 if (parameter != null) { 291 parameters.add(parameter); 292 } 293 } 294 } 295 return parameters; 296 } 297 }