/*
* Copyright (C) 2012 eXo Platform SAS.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.crsh.cli.impl.lang;
import org.crsh.cli.Named;
import org.crsh.cli.descriptor.Description;
import org.crsh.cli.impl.descriptor.IntrospectionException;
import org.crsh.cli.descriptor.ParameterDescriptor;
import org.crsh.cli.impl.ParameterType;
import org.crsh.cli.Argument;
import org.crsh.cli.Command;
import org.crsh.cli.Option;
import org.crsh.cli.Required;
import org.crsh.cli.type.ValueTypeFactory;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
/** @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a> */
public class CommandFactory {
/** . */
public static final CommandFactory DEFAULT = new CommandFactory();
/** . */
private static final Logger log = Logger.getLogger(CommandFactory.class.getName());
/** . */
protected final ValueTypeFactory valueTypeFactory;
public CommandFactory() {
this.valueTypeFactory = ValueTypeFactory.DEFAULT;
}
public CommandFactory(ClassLoader loader) throws NullPointerException {
this(new ValueTypeFactory(loader));
}
public CommandFactory(ValueTypeFactory valueTypeFactory) throws NullPointerException {
if (valueTypeFactory == null) {
throw new NullPointerException("No null value type factory accepted");
}
//
this.valueTypeFactory = valueTypeFactory;
}
private List<Method> findAllMethods(Class<?> introspected) throws IntrospectionException {
List<Method> methods;
Class<?> superIntrospected = introspected.getSuperclass();
if (superIntrospected == null) {
methods = new ArrayList<Method>();
} else {
methods = findAllMethods(superIntrospected);
for (Method method : introspected.getDeclaredMethods()) {
if (method.getAnnotation(Command.class) != null) {
methods.add(method);
}
}
}
return methods;
}
public <T> ObjectCommandDescriptor<T> create(Class<T> type) throws IntrospectionException {
// Find all command methods
List<Method> methods = findAllMethods(type);
//
String commandName;
if (type.getAnnotation(Named.class) != null) {
commandName = type.getAnnotation(Named.class).value();
} else {
commandName = type.getSimpleName();
}
//
if (methods.size() == 1 && methods.get(0).getName().equals("main")) {
MethodDescriptor<T> methodDescriptor = create(null, commandName, methods.get(0));
for (ParameterDescriptor parameter : parameters(type)) {
methodDescriptor.addParameter(parameter);
}
return methodDescriptor;
} else {
Map<String, MethodDescriptor<T>> methodMap = new LinkedHashMap<String, MethodDescriptor<T>>();
ClassDescriptor<T> classDescriptor = new ClassDescriptor<T>(type, commandName, methodMap, new Description(type));
for (Method method : methods) {
String methodName;
if (method.getAnnotation(Named.class) != null) {
methodName = method.getAnnotation(Named.class).value();
} else {
methodName = method.getName();
}
MethodDescriptor<T> methodDescriptor = create(classDescriptor, methodName, method);
methodMap.put(methodDescriptor.getName(), methodDescriptor);
}
for (ParameterDescriptor parameter : parameters(type)) {
classDescriptor.addParameter(parameter);
}
return classDescriptor;
}
}
private <T> MethodDescriptor<T> create(ClassDescriptor<T> classDescriptor, String name, Method method) throws IntrospectionException {
Description info = new Description(method);
MethodDescriptor<T> methodDescriptor = new MethodDescriptor<T>(
classDescriptor,
method,
name,
info);
Type[] parameterTypes = method.getGenericParameterTypes();
Annotation[][] parameterAnnotationMatrix = method.getParameterAnnotations();
for (int i = 0;i < parameterAnnotationMatrix.length;i++) {
Annotation[] parameterAnnotations = parameterAnnotationMatrix[i];
Type parameterType = parameterTypes[i];
Tuple tuple = get(parameterAnnotations);
MethodArgumentBinding binding = new MethodArgumentBinding(i);
ParameterDescriptor parameter = create(
binding,
parameterType,
tuple.argumentAnn,
tuple.optionAnn,
tuple.required,
tuple.descriptionAnn,
tuple.ann);
if (parameter != null) {
methodDescriptor.addParameter(parameter);
} else {
log.log(Level.FINE, "Method argument with index " + i + " of method " + method + " is not annotated");
}
}
return methodDescriptor;
}
private ParameterDescriptor create(
Binding binding,
Type type,
Argument argumentAnn,
Option optionAnn,
boolean required,
Description info,
Annotation ann) throws IntrospectionException {
//
if (argumentAnn != null) {
if (optionAnn != null) {
throw new IntrospectionException();
}
//
return new BoundArgumentDescriptor(
binding,
argumentAnn.name(),
ParameterType.create(valueTypeFactory, type),
info,
required,
false,
argumentAnn.unquote(),
argumentAnn.completer(),
ann);
} else if (optionAnn != null) {
return new BoundOptionDescriptor(
binding,
ParameterType.create(valueTypeFactory, type),
Collections.unmodifiableList(Arrays.asList(optionAnn.names())),
info,
required,
false,
optionAnn.unquote(),
optionAnn.completer(),
ann);
} else {
return null;
}
}
private static Tuple get(Annotation... ab) {
Argument argumentAnn = null;
Option optionAnn = null;
Boolean required = null;
Description description = new Description(ab);
Annotation info = null;
for (Annotation parameterAnnotation : ab) {
if (parameterAnnotation instanceof Option) {
optionAnn = (Option)parameterAnnotation;
} else if (parameterAnnotation instanceof Argument) {
argumentAnn = (Argument)parameterAnnotation;
} else if (parameterAnnotation instanceof Required) {
required = ((Required)parameterAnnotation).value();
} else if (info == null) {
// Look at annotated annotations
Class<? extends Annotation> a = parameterAnnotation.annotationType();
if (a.getAnnotation(Option.class) != null) {
optionAnn = a.getAnnotation(Option.class);
info = parameterAnnotation;
} else if (a.getAnnotation(Argument.class) != null) {
argumentAnn = a.getAnnotation(Argument.class);
info = parameterAnnotation;
}
//
if (info != null) {
//
description = new Description(description, new Description(a));
//
if (required == null) {
Required metaReq = a.getAnnotation(Required.class);
if (metaReq != null) {
required = metaReq.value();
}
}
}
}
}
//
return new Tuple(argumentAnn, optionAnn, required != null && required,description, info);
}
/**
* Jus grouping some data for conveniency
*/
protected static class Tuple {
final Argument argumentAnn;
final Option optionAnn;
final boolean required;
final Description descriptionAnn;
final Annotation ann;
private Tuple(Argument argumentAnn, Option optionAnn, boolean required, Description info, Annotation ann) {
this.argumentAnn = argumentAnn;
this.optionAnn = optionAnn;
this.required = required;
this.descriptionAnn = info;
this.ann = ann;
}
}
private List<ParameterDescriptor> parameters(Class<?> introspected) throws IntrospectionException {
List<ParameterDescriptor> parameters;
Class<?> superIntrospected = introspected.getSuperclass();
if (superIntrospected == null) {
parameters = new ArrayList<ParameterDescriptor>();
} else {
parameters = parameters(superIntrospected);
for (Field f : introspected.getDeclaredFields()) {
Tuple tuple = get(f.getAnnotations());
ClassFieldBinding binding = new ClassFieldBinding(f);
ParameterDescriptor parameter = create(
binding,
f.getGenericType(),
tuple.argumentAnn,
tuple.optionAnn,
tuple.required,
tuple.descriptionAnn,
tuple.ann);
if (parameter != null) {
parameters.add(parameter);
}
}
}
return parameters;
}
}