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