All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openstreetmap.atlas.utilities.runtime.FlexibleCommand Maven / Gradle / Ivy

package org.openstreetmap.atlas.utilities.runtime;

import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.openstreetmap.atlas.exception.CoreException;

import io.github.classgraph.ClassGraph;
import io.github.classgraph.ClassInfoList;
import io.github.classgraph.ScanResult;

/**
 * Shell for running subcommands. Reflections is used to find commands and their switchlists are
 * automatically added to the command at runtime. This implementation will search the entire
 * classloader for all FlexibleSubCommand implementations. If you want to restrict the set of
 * commands to support, override the getSupportedCommands method
 *
 * @author cstaylor
 */
public class FlexibleCommand extends Command
{
    private FlexibleSubCommand subcommand;

    private Map commandMap;

    public static void main(final String... args)
    {
        if (args.length == 0)
        {
            new FlexibleCommand("FAILED").printUsageAndExit();
        }
        new FlexibleCommand(args[0]).run(args);
    }

    protected static boolean noAbstract(final Class klass)
    {
        return !Modifier.isAbstract(klass.getModifiers());
    }

    private static FlexibleSubCommand create(final Class klass)
    {
        try
        {
            return klass.newInstance();
        }
        catch (final Exception oops)
        {
            throw new CoreException("Error when creating new instance of {}",
                    klass.getCanonicalName(), oops);
        }
    }

    public FlexibleCommand(final String... args)
    {
        initializeCommands();
        if (args.length == 0)
        {
            printUsageAndExit();
        }
    }

    @Override
    public void run(final String... arguments)
    {
        final String[] commandArgs = prepare(arguments);
        super.run(commandArgs);
    }

    @Override
    public void runWithoutQuitting(final String... arguments)
    {
        final String[] commandArgs = prepare(arguments);
        super.runWithoutQuitting(commandArgs);
    }

    /**
     * Default behavior finds all FlexibleSubCommands in the classpath. Override to restrict the set
     * of classes returned
     *
     * @return a stream containing all of the subcommands we want to support
     */
    @SuppressWarnings("unchecked")
    protected Stream> getSupportedCommands()
    {
        final List> returnValue = new ArrayList<>();
        try (ScanResult scanResult = new ClassGraph().enableAllInfo().scan())
        {
            final ClassInfoList classInfoList = scanResult
                    .getClassesImplementing(FlexibleSubCommand.class.getName());
            classInfoList.loadClasses()
                    .forEach(klass -> returnValue.add((Class) klass));
        }
        return returnValue.stream();
    }

    @Override
    protected int onRun(final CommandMap command)
    {
        return this.subcommand.execute(command);
    }

    protected void printUsageAndExit()
    {
        printUsageAndExit(-1);
    }

    protected void printUsageAndExit(final int errorCode)
    {
        this.commandMap.entrySet().stream().forEach(item ->
        {
            System.err.printf("%s - %s\n", item.getKey(), item.getValue().getDescription());
            item.getValue().usage(System.err);
            System.err.printf("\n");
        });
        System.exit(errorCode);
    }

    @Override
    protected SwitchList switches()
    {
        return this.subcommand.switches();
    }

    private void initializeCommands()
    {
        this.commandMap = getSupportedCommands().filter(FlexibleCommand::noAbstract)
                .map(FlexibleCommand::create)
                .collect(Collectors.toMap(FlexibleSubCommand::getName, x -> x));
    }

    private String[] prepare(final String... arguments)
    {
        this.subcommand = this.commandMap.get(arguments[0]);
        if (this.subcommand == null)
        {
            printUsageAndExit();
        }
        /**
         * With FlexibleCommands, the first argument is the name of the subcommand. We're peeling
         * that argument off the args array and passing that value to the FlexibleCommand
         * constructor. If no subcommand matches that name, we immediately terminate with a usage
         * error, otherwise we continue to run using the remaining arguments copied heres
         */
        final String[] commandArgs = new String[arguments.length - 1];
        System.arraycopy(arguments, 1, commandArgs, 0, commandArgs.length);
        return commandArgs;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy