org.nuunframework.cli.NuunCliPlugin.java Source code

Java tutorial

Introduction

Here is the source code for org.nuunframework.cli.NuunCliPlugin.java

Source

/**
 * Copyright (C) 2013 Kametic <epo.jemba@kametic.com>
 *
 * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE, Version 3, 29 June 2007;
 * or any later version
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.gnu.org/licenses/lgpl-3.0.txt
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.nuunframework.cli;

import com.google.common.base.Strings;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;
import org.apache.commons.lang.ArrayUtils;
import org.nuunframework.cli.api.NuunCliHandler;
import org.nuunframework.kernel.commons.AssertUtils;
import org.nuunframework.kernel.commons.specification.AbstractSpecification;
import org.nuunframework.kernel.commons.specification.Specification;
import org.nuunframework.kernel.context.InitContext;
import org.nuunframework.kernel.plugin.AbstractPlugin;
import org.nuunframework.kernel.plugin.InitState;
import org.nuunframework.kernel.plugin.PluginException;
import org.nuunframework.kernel.plugin.request.ClasspathScanRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

public class NuunCliPlugin extends AbstractPlugin {

    private Logger logger = LoggerFactory.getLogger(NuunCliPlugin.class);
    private Specification<Class<?>> optionDefsSpecification;
    private Options optionsAggregated;
    private String[] lineArguments;
    private CommandLine commandLine;
    private Map<Class<?>, CommandLine> contextualCommandLineMap;
    private CommandLineParser providedParser;

    Specification<Class<?>> cliHandlerSpec = null;
    private Map<Class, Class> bindings;
    private Map<Class<?>, Options> byClassOptions;

    /**
     * 
     */
    public NuunCliPlugin() {
        bindings = Maps.newHashMap();
        contextualCommandLineMap = Maps.newHashMap();
        optionsAggregated = new Options();
    }

    @Override
    public String name() {
        return "nuun-cli-plugin";
    }

    /*
     * (non-Javadoc)
     * @see org.nuunframework.kernel.plugin.AbstractPlugin#init(org.nuunframework.kernel.context.InitContext)
     */
    @Override
    public InitState init(InitContext initContext) {
        // Parser initialization
        CommandLineParser parser = null;
        if (providedParser == null) {
            parser = new PosixParser();
        } else {
            parser = providedParser;
        }

        /////////// handler CliHandler
        {
            Collection<Class<?>> collection = initContext.scannedTypesBySpecification().get(cliHandlerSpec);

            for (Class<?> class1 : collection) {
                bindings.put(NuunCliHandler.class, class1);
                // for now only one CommandLineHandler
                break;
            }
        }

        //////////// Define options and command lines

        Collection<Class<?>> collection = initContext.scannedTypesBySpecification().get(optionDefsSpecification);

        byClassOptions = createOptions(collection);

        for (Entry<Class<?>, Options> entry : byClassOptions.entrySet()) {
            try {
                commandLine = parser.parse(entry.getValue(), lineArguments);
                contextualCommandLineMap.put(entry.getKey(), commandLine);
            } catch (ParseException e) {
                logger.debug("Error during option parse for class " + entry.getKey());
            }
        }

        return InitState.INITIALIZED;
    }

    /**
     * @param lineArguments2
     * @return
     */
    private String treat(String[] lineArguments2) {
        StringBuilder bulder = new StringBuilder();

        for (String string : lineArguments2) {
            bulder.append(string).append(' ');
        }
        return bulder.toString();
    }

    private Map<Class<?>, Options> createOptions(Collection<Class<?>> collection) {
        Set<String> shortOptions = Sets.newHashSet();
        Set<String> longOptions = Sets.newHashSet();

        Map<Class<?>, Options> optionsMap = Maps.newHashMap();

        for (Class<?> class1 : collection) {
            logger.info("CLASS " + class1.getSimpleName());
            Set<Field> fields = annotatedFields(class1, NuunOption.class);
            Options internalOptions = new Options();
            for (Field field : fields) {
                logger.info("Class : " + field.getDeclaringClass().getSimpleName() + " / " + field.getName());
                Option option = createOptionFromField(field);

                if (!Strings.isNullOrEmpty(option.getOpt()) && shortOptions.contains(option.getOpt())) {
                    exception("Short option " + option.getOpt() + " already exists!");
                }

                if (!Strings.isNullOrEmpty(option.getLongOpt()) && longOptions.contains(option.getLongOpt())) {
                    exception("Long option " + option.getLongOpt() + " already exists!");
                }

                if (Strings.isNullOrEmpty(option.getOpt()) && Strings.isNullOrEmpty(option.getLongOpt())) {
                    exception("NuunOption defined on " + field + " has no opt nor longOpt.");
                }

                internalOptions.addOption(option);
                // global one
                optionsAggregated.addOption(option);

                shortOptions.add(option.getOpt());
                longOptions.add(option.getLongOpt());
            }

            optionsMap.put(class1, internalOptions);

        }

        return optionsMap;
    }

    public void provideParser(CommandLineParser parser) {
        this.providedParser = parser;

    }

    /* (non-Javadoc)
     * @see org.nuunframework.kernel.plugin.AbstractPlugin#provideContainerContext(java.lang.Object)
     */
    @Override
    public void provideContainerContext(Object containerContext) {
        if (containerContext instanceof String[]) {
            lineArguments = (String[]) containerContext;
        } else {
            lineArguments = new String[0];
        }
    }

    /* (non-Javadoc)
     * @see org.nuunframework.kernel.plugin.AbstractPlugin#dependencyInjectionDef()
     */
    @Override
    public Object dependencyInjectionDef() {
        return new NuunCliModule(commandLine, this.optionsAggregated, contextualCommandLineMap, byClassOptions,
                bindings);
    }

    private void exception(String message, Object... objects) {
        logger.error(message, objects);
        if (objects != null && objects.length == 1 && (Throwable.class.isAssignableFrom(objects[0].getClass()))) {
            throw new PluginException(message, (Throwable) objects[0]);
        } else {
            throw new PluginException(message, objects);

        }
    }

    private void logException(String message, Object... objects) {
        logger.error(message, objects);
        PluginException pluginException = null;
        if (objects != null && objects.length == 1 && (Throwable.class.isAssignableFrom(objects[0].getClass()))) {
            pluginException = new PluginException(message, (Throwable) objects[0]);
        } else {
            pluginException = new PluginException(message, objects);

        }
        logger.error("Nuun Cli Plugin Exception : ", pluginException);
    }

    private Option createOptionFromField(Field field) {
        Option option = null;
        // Cli Option Builder is completly static :-/
        // so we synchronized it ...
        synchronized (OptionBuilder.class) {
            // reset the builder creating a dummy option
            OptionBuilder.withLongOpt("dummy");
            OptionBuilder.create();

            // 
            NuunOption nuunOption = field.getAnnotation(NuunOption.class);

            if (nuunOption == null) {
                for (Annotation anno : field.getAnnotations()) {
                    if (AssertUtils.hasAnnotationDeep(anno.annotationType(), NuunOption.class)) {
                        nuunOption = AssertUtils.annotationProxyOf(NuunOption.class, anno);
                        break;
                    }
                }
            }

            // longopt
            if (!Strings.isNullOrEmpty(nuunOption.longOpt())) {
                OptionBuilder.withLongOpt(nuunOption.longOpt());
            }
            // description
            if (!Strings.isNullOrEmpty(nuunOption.description())) {
                OptionBuilder.withDescription(nuunOption.description());
            }
            // required
            OptionBuilder.isRequired((nuunOption.required()));

            // arg
            OptionBuilder.hasArg((nuunOption.arg()));

            // args
            if (nuunOption.args()) {
                if (nuunOption.numArgs() > 0) {
                    OptionBuilder.hasArgs(nuunOption.numArgs());
                } else {
                    OptionBuilder.hasArgs();
                }
            }
            // is optional 
            if (nuunOption.optionalArg()) {
                OptionBuilder.hasOptionalArg();
            }

            // nuun 
            OptionBuilder.withValueSeparator(nuunOption.valueSeparator());

            // opt 
            if (!Strings.isNullOrEmpty(nuunOption.opt())) {
                option = OptionBuilder.create(nuunOption.opt());
            } else {
                option = OptionBuilder.create();
            }
        }

        return option;

    }

    private Set<Field> annotatedFields(Class<?> class_, Class<? extends Annotation> annoClass) {
        Set<Field> fields = new HashSet<Field>();
        for (Field field : class_.getDeclaredFields()) {
            if (field.isAnnotationPresent(annoClass)) {
                fields.add(field);
            } else {
                for (Annotation annotation : field.getAnnotations()) {
                    if (hasAnnotationDeep(annotation.annotationType(), annoClass)) {
                        //                        return true;
                        fields.add(field);
                    }
                }
            }
        }

        return fields;
    }

    /*
     * (non-Javadoc)
     * @see org.nuunframework.kernel.plugin.AbstractPlugin#classpathScanRequests()
     */
    @SuppressWarnings("unchecked")
    @Override
    public Collection<ClasspathScanRequest> classpathScanRequests() {
        optionDefsSpecification = fieldAnnotatedWith(NuunOption.class);

        cliHandlerSpec = and(ancestorImplements(NuunCliHandler.class), //
                not(classIsInterface()), //
                not(classIsAbstract()) // 
        );

        return classpathScanRequestBuilder() //
                .specification(optionDefsSpecification) //
                .specification(cliHandlerSpec).build();
    }

    protected Specification<Class<?>> fieldAnnotatedWith(final Class<? extends Annotation> annotationClass) {
        return new AbstractSpecification<Class<?>>() {
            @Override
            public boolean isSatisfiedBy(Class<?> candidate) {

                if (candidate != null) {
                    try {
                        for (Field field : candidate.getDeclaredFields()) {
                            if (field.isAnnotationPresent(annotationClass)) {
                                return true;
                            }
                            for (Annotation annotation : field.getAnnotations()) {
                                if (hasAnnotationDeep(annotation.annotationType(), annotationClass)) {
                                    return true;
                                }
                            }
                        }
                    } catch (Throwable throwable) {
                        logger.trace("fieldAnnotatedWith : " + candidate + " missing " + throwable);
                    }
                }

                return false;
            }
        };
    }

    boolean hasAnnotationDeep(Class<? extends Annotation> from, Class<? extends Annotation> toFind) {

        if (from.equals(toFind)) {
            return true;
        }

        for (Annotation anno : from.getAnnotations()) {
            Class<? extends Annotation> annoClass = anno.annotationType();
            if (!annoClass.getPackage().getName().startsWith("java.lang") && hasAnnotationDeep(annoClass, toFind)) {
                return true;
            }
        }

        return false;
    }

    /*******************************************************************/
    /************* SPECIFICATIONS                     ******************/
    /*******************************************************************/

    protected Specification<Class<?>> classIsInterface() {
        return new AbstractSpecification<Class<?>>() {
            @Override
            public boolean isSatisfiedBy(Class<?> candidate) {

                return candidate != null && candidate.isInterface();
            }
        };
    }

    protected Specification<Class<?>> classIsAbstract() {
        return new AbstractSpecification<Class<?>>() {
            @Override
            public boolean isSatisfiedBy(Class<?> candidate) {

                return candidate != null && Modifier.isAbstract(candidate.getModifiers());
            }
        };
    }

    protected Specification<Class<?>> ancestorImplements(final Class<?> interfaceClass) {
        return new AbstractSpecification<Class<?>>() {

            @Override
            public boolean isSatisfiedBy(Class<?> candidate) {
                if (candidate == null)
                    return false;

                boolean result = false;

                Class<?>[] allInterfacesAndClasses = getAllInterfacesAndClasses(candidate);

                for (Class<?> clazz : allInterfacesAndClasses) {
                    if (!clazz.isInterface()) {
                        for (Class<?> i : clazz.getInterfaces()) {
                            if (i.equals(interfaceClass)) {
                                result = true;
                                break;
                            }
                        }
                    }
                }

                return result;
            }

        };
    }

    Class<?>[] getAllInterfacesAndClasses(Class<?> clazz) {
        return getAllInterfacesAndClasses(new Class[] { clazz });
    }

    //This method walks up the inheritance hierarchy to make sure we get every class/interface that could
    //possibly contain the declaration of the annotated method we're looking for.
    @SuppressWarnings("unchecked")
    Class<?>[] getAllInterfacesAndClasses(Class<?>[] classes) {
        if (0 == classes.length) {
            return classes;
        } else {
            List<Class<?>> extendedClasses = new ArrayList<Class<?>>();
            // all interfaces hierarchy
            for (Class<?> clazz : classes) {
                if (clazz != null) {
                    Class<?>[] interfaces = clazz.getInterfaces();
                    if (interfaces != null) {
                        extendedClasses.addAll((List<? extends Class<?>>) Arrays.asList(interfaces));
                    }
                    Class<?> superclass = clazz.getSuperclass();
                    if (superclass != null && superclass != Object.class) {
                        extendedClasses.addAll((List<? extends Class<?>>) Arrays.asList(superclass));
                    }
                }
            }

            // Class::getInterfaces() gets only interfaces/classes
            // implemented/extended directly by a given class.
            // We need to walk the whole way up the tree.
            return (Class[]) ArrayUtils.addAll(classes,
                    getAllInterfacesAndClasses(extendedClasses.toArray(new Class[extendedClasses.size()])));
        }
    }
    //    class OptionsDefSpecification extends AbstractSpecification<Class<?>>
    //    {
    //        public OptionsDefSpecification()
    //        {
    //        }
    //
    //        @Override
    //        public boolean isSatisfiedBy(Class<?> candidate)
    //        {
    //            return (candidate != null && candidate.isAnnotation() &&     
    //
    //                    ( candidate.isAnnotationPresent(NuunOption.class))  /*||  annotationAnnotatedWith(candidate, MetaNuunOption.class) */ );
    //        }
    //        
    //        
    //        private boolean annotationAnnotatedWith (Class<?> candidate, Class<? extends Annotation> anno) 
    //        {
    //            for (Annotation a : candidate.getAnnotations())
    //            {
    //                if (a.annotationType().getAnnotation(anno) != null)
    //                {
    //                    return true;
    //                }
    //            }
    //            
    //            return false;
    //        }
    //    }

}