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

org.tomitribe.crest.Main Maven / Gradle / Ivy

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * 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.tomitribe.crest;

import org.tomitribe.crest.api.Exit;
import org.tomitribe.crest.api.PrintOutput;
import org.tomitribe.crest.api.StreamingOutput;
import org.tomitribe.crest.api.interceptor.CrestInterceptor;
import org.tomitribe.crest.cmds.Cmd;
import org.tomitribe.crest.cmds.CommandFailedException;
import org.tomitribe.crest.cmds.Completer;
import org.tomitribe.crest.cmds.HelpPrintedException;
import org.tomitribe.crest.cmds.processors.Commands;
import org.tomitribe.crest.cmds.processors.Help;
import org.tomitribe.crest.contexts.DefaultsContext;
import org.tomitribe.crest.contexts.SystemPropertiesDefaultsContext;
import org.tomitribe.crest.environments.Environment;
import org.tomitribe.crest.environments.SystemEnvironment;
import org.tomitribe.crest.interceptor.internal.InternalInterceptor;
import org.tomitribe.crest.table.Formatting;
import org.tomitribe.crest.table.TableInterceptor;

import java.io.PrintStream;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.stream.Stream;

public class Main implements Completer {

    protected final Map commands = new ConcurrentHashMap<>();
    protected final Map, InternalInterceptor> interceptors = new HashMap<>();

    public Main() {
        this(new SystemPropertiesDefaultsContext(), Commands.load());
    }

    public Main(final Class... classes) {
        this(Arrays.asList(classes));
    }

    public Main(final DefaultsContext defaultsContext, final Class... classes) {
        this(defaultsContext, Arrays.asList(classes));
    }

    public Main(final DefaultsContext defaultsContext, final Iterable> classes) {
        for (final Class clazz : classes) {
            processClass(defaultsContext, clazz);
        }

        // Built-in formatters
        processClass(defaultsContext, TableInterceptor.class);

        // Built-in commands
        installHelp(defaultsContext);
    }

    public void processClass(final DefaultsContext defaultsContext, final Class clazz) {
        final Map m = Commands.get(clazz, defaultsContext);
        if (!m.isEmpty()) {
            this.commands.putAll(m);
        } else {

            final InternalInterceptor internalInterceptor = InternalInterceptor.from(clazz);
            if (interceptors.put(clazz, internalInterceptor) != null) {
                throw new IllegalArgumentException(clazz + " interceptor is conflicting");
            }

            for (final Annotation annotation : clazz.getDeclaredAnnotations()) {
                if (isCustomInterceptorAnnotation(annotation)) {
                    if (interceptors.put(annotation.annotationType(), internalInterceptor) != null) {
                        throw new IllegalArgumentException(clazz + " interceptor is conflicting");
                    }
                }
            }
        }
    }

    private static boolean isCustomInterceptorAnnotation(final Annotation annotation) {
        for (final Annotation declaredAnnotation : annotation.annotationType().getDeclaredAnnotations()) {
            if (declaredAnnotation instanceof CrestInterceptor) {
                return true;
            }
        }
        return false;
    }

    public Main(final Iterable> classes) {
        this(new SystemPropertiesDefaultsContext(), classes);
    }

    public void add(final Cmd cmd) {
        commands.put(cmd.getName(), cmd);
    }

    public void remove(final Cmd cmd) {
        commands.remove(cmd.getName());
    }

    private void installHelp(final DefaultsContext dc) {
        final Map stringCmdMap = Commands.get(new Help(Main.this.commands), dc);
        for (final Cmd cmd : stringCmdMap.values()) {
            add(cmd);
        }
    }

    public static void main(final String... args) throws Exception {
        final Environment env = new SystemEnvironment();
        final Consumer onExit = System::exit;

        main(env, onExit, args);
    }

    /**
     * Added additional method for greater testability, including knowing if exit
     * is properly called with the correct value.
     */
    public static void main(final Environment env, final Consumer onExit, final String... args) {
        try {
            final Main main = new Main();
            main.main(env, args);
        } catch (final CommandFailedException e) {

            final Throwable cause = e.getCause();

            handle(env, onExit, cause);

        } catch (final Throwable throwable) {

            handle(env, onExit, throwable);
        }
    }

    private static void handle(final Environment env, final Consumer onExit, final Throwable cause) {
        final Exit exit = cause.getClass().getAnnotation(Exit.class);
        final int code = (exit != null) ? exit.value() : -1;

        if (cause instanceof HelpPrintedException) {

            // these are already handled via message + help
            onExit.accept(code);

        } else if (exit != null) {

            env.getError().println(cause.getMessage());
            onExit.accept(exit.value());

        } else {

            cause.printStackTrace(env.getError());
            onExit.accept(-1);

        }
    }

    public void main(final Environment env, final String... args) throws Exception {
        final Environment old = Environment.ENVIRONMENT_THREAD_LOCAL.get();
        Environment.ENVIRONMENT_THREAD_LOCAL.set(env);

        try {
            final Object result = exec(args);

            if (result == null) return;

            final PrintStream out = env.getOutput();

            if (result instanceof StreamingOutput) {

                ((StreamingOutput) result).write(out);

            } else if (result instanceof PrintOutput) {

                ((PrintOutput) result).write(out);

            } else if (result instanceof Stream) {

                ((Stream) result)
                        .map(o -> o == null ? "" : o)
                        .map(Object::toString)
                        .forEach(out::println);

            } else if (result instanceof Iterable) {

                final Iterable iterable = (Iterable) result;

                for (final Object o : iterable) {
                    if (o != null) out.println(o.toString());
                }

            } else if (result instanceof String) {

                final String string = (String) result;

                out.print(string);

                if (!string.endsWith("\n")) out.println();

            } else if (result instanceof String[][]) {

                final String[][] data = (String[][]) result;

                Formatting.asPrintStream(data).write(out);

            } else {

                out.println(result);

            }
        } finally {
            Environment.ENVIRONMENT_THREAD_LOCAL.set(old);
        }
    }

    public Object exec(String... args) throws Exception {
        final List list = processSystemProperties(args);

        final String command = (list.isEmpty()) ? "help" : list.remove(0);
        args = list.toArray(new String[list.size()]);

        if (command.equals("_completion")) {
            return BashCompletion.generate(this, args);
        }

        final Cmd cmd = commands.get(command);

        if (cmd == null) {

            final PrintStream err = Environment.ENVIRONMENT_THREAD_LOCAL.get().getError();
            err.println("Unknown command: " + command);
            err.println();
            commands.get("help").exec(interceptors);
            throw new IllegalArgumentException();
        }

        return cmd.exec(interceptors, args);
    }

    public static List processSystemProperties(final String[] args) {
        final List list = new ArrayList<>();

        // Read in and apply the properties specified on the command line
        for (final String arg : args) {
            if (arg.startsWith("-D")) {

                final String name = arg.substring(arg.indexOf("-D") + 2, arg.indexOf('='));
                final String value = arg.substring(arg.indexOf('=') + 1);

                final Properties properties = Environment.ENVIRONMENT_THREAD_LOCAL.get().getProperties();
                properties.setProperty(name, value);
            } else {
                list.add(arg);
            }
        }

        return list;
    }

    @Override
    public Collection complete(final String buffer, final int cursorPosition) {
        final List cmds = new ArrayList<>();

        if (buffer == null || buffer.isEmpty()) {
            final Set cmd = commands.keySet();
            for (final String s : cmd) {
                cmds.add(s + " ");
            }
        } else {

            if (buffer.substring(0, cursorPosition).contains(" ")) {
                final Cmd cmd = getCmd(buffer);

                if (cmd != null) {
                    return cmd.complete(buffer, cursorPosition);
                }
            }

            final String prefix = buffer.substring(0, cursorPosition);
            Iterator iterator = commands.keySet().iterator();
            while (iterator.hasNext()) {
                final String command = iterator.next();
                if (command.startsWith(prefix)) {
                    cmds.add(command + " ");
                }
            }
        }

        Collections.sort(cmds);
        return cmds;
    }

    private Cmd getCmd(String buffer) {
        final String commandName = buffer.replaceAll("^(\\w*).*?$", "$1");
        final Iterator iterator = this.commands.keySet().iterator();

        while (iterator.hasNext()) {
            String cmd = iterator.next();
            if (cmd.equals(commandName)) {
                return this.commands.get(cmd);
            }
        }

        return null;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy