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