Java tutorial
/* * Copyright (c) 2009-present the original author or authors. * * Licensed 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 com.planet57.gshell; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import com.google.common.annotations.VisibleForTesting; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Module; import com.planet57.gossip.Level; import com.planet57.gossip.Log; import com.planet57.gshell.branding.Branding; import com.planet57.gshell.branding.BrandingSupport; import com.planet57.gshell.guice.BeanContainer; import com.planet57.gshell.internal.ShellBuilderImpl; import com.planet57.gshell.shell.ShellBuilder; import com.planet57.gshell.util.io.IO; import com.planet57.gshell.internal.ExitCodeDecoder; import com.planet57.gshell.shell.Shell; import com.planet57.gshell.util.NameValue; import com.planet57.gshell.util.cli2.Argument; import com.planet57.gshell.util.cli2.CliProcessor; import com.planet57.gshell.util.cli2.HelpPrinter; import com.planet57.gshell.util.cli2.Option; import com.planet57.gshell.util.io.StreamSet; import com.planet57.gshell.util.pref.Preference; import com.planet57.gshell.util.pref.PreferenceProcessor; import com.planet57.gshell.util.pref.Preferences; import com.planet57.gshell.util.io.StyledIO; import com.planet57.gshell.variables.VariableNames; import com.planet57.gshell.variables.Variables; import com.planet57.gshell.variables.VariablesSupport; import org.apache.commons.cli.ParseException; import org.apache.felix.gogo.runtime.threadio.ThreadIOImpl; import org.apache.felix.service.threadio.ThreadIO; import org.eclipse.sisu.space.BeanScanning; import org.eclipse.sisu.space.SpaceModule; import org.eclipse.sisu.space.URLClassSpace; import org.eclipse.sisu.wire.WireModule; import org.jline.style.MemoryStyleSource; import org.jline.style.Styler; import org.jline.terminal.Terminal; import org.jline.terminal.TerminalBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.bridge.SLF4JBridgeHandler; import javax.annotation.Nonnull; import javax.annotation.Nullable; import static com.google.common.base.Preconditions.checkNotNull; /** * Support for booting shell applications. * * @since 2.0 */ @Preferences(path = "cli") public abstract class MainSupport { private final Logger log = Log.getLogger(getClass()); private final ThreadIOImpl threadIO = new ThreadIOImpl(); @Option(name = "h", longName = "help", description = "Display usage", override = true) private boolean help; @Option(name = "V", longName = "version", description = "Display program version", override = true) private boolean version; @Preference @Option(name = "e", longName = "errors", description = "Produce detailed exceptions") private boolean showErrorTraces; @Nullable private Level loggingLevel; @Preference(name = "debug") @Option(name = "d", longName = "debug", description = "Enable debug output") private void setDebug(final boolean flag) { log.debug("Debug: {}", flag); if (flag) { loggingLevel = Level.DEBUG; // imply --errors showErrorTraces = true; } } @Preference(name = "trace") @Option(name = "X", longName = "trace", description = "Enable trace output") private void setTrace(final boolean flag) { log.debug("Trace: {}", flag); if (flag) { loggingLevel = Level.TRACE; // imply --errors showErrorTraces = true; } } @Nullable @Option(name = "c", longName = "command", description = "Execute COMMAND", token = "COMMAND") private String command; private final Variables variables = new VariablesSupport(); @Option(name = "D", longName = "define", description = "Define a variable", token = "NAME=VALUE") private void setVariable(final String input) { log.debug("Set variable: {}", input); NameValue nv = NameValue.parse(input); variables.set(nv.name, nv.value); } @Option(name = "P", longName = "property", description = "Define a system-property", token = "NAME=VALUE") private void setSystemProperty(final String input) { log.debug("Set system-property: {}", input); NameValue nv = NameValue.parse(input); System.setProperty(nv.name, nv.value); } @Argument(description = "Command expression to execute", token = "EXPR") @Nullable private List<String> appArgs; // // Boot // public void boot(final String... args) throws Exception { checkNotNull(args); if (log.isDebugEnabled()) { log.debug("Booting w/args: {}", Arrays.toString(args)); } // Register default handler for uncaught exceptions Thread.setDefaultUncaughtExceptionHandler( (thread, cause) -> log.warn("Unhandled exception occurred on thread: {}", thread, cause)); // Prepare branding Branding branding = createBranding(); // Process preferences PreferenceProcessor pp = new PreferenceProcessor(); pp.setBasePath(branding.getPreferencesBasePath()); pp.addBean(this); pp.process(); // Process command line options & arguments CliProcessor clp = new CliProcessor(); clp.addBean(this); clp.setStopAtNonOption(true); // cope with cli exceptions; which are expected try { clp.process(args); } catch (ParseException e) { e.printStackTrace(System.err); exit(2); } // once options are processed setup logging environment setupLogging(loggingLevel); // setup styling Styler.setSource(new MemoryStyleSource()); // prepare terminal and I/O Terminal terminal = createTerminal(branding); IO io = StyledIO.create("shell", createStreamSet(terminal), terminal); if (help) { HelpPrinter printer = new HelpPrinter(clp, terminal.getWidth()); printer.printUsage(io.out, branding.getProgramName()); io.flush(); exit(0); } if (version) { io.format("%s %s%n", branding.getDisplayName(), branding.getVersion()); io.flush(); exit(0); } // install thread-IO handler and attach streams threadIO.start(); threadIO.setStreams(io.streams.in, io.streams.out, io.streams.err); Object result = null; try { variables.set(VariableNames.SHELL_ERRORS, showErrorTraces); Shell shell = createShell(io, variables, branding); shell.start(); try { if (command != null) { result = shell.execute(command); } else if (appArgs != null) { result = shell.execute(String.join(" ", appArgs)); } else { shell.run(); } } finally { shell.stop(); } } finally { io.flush(); threadIO.stop(); terminal.close(); } if (result == null) { result = variables.get(VariableNames.LAST_RESULT); } exit(ExitCodeDecoder.decode(result)); } // // Shell creation // /** * Create a the {@link Branding} instance. * * Branding is needed very early to allow customization of command-line processing. */ protected Branding createBranding() { return new BrandingSupport(); } /** * Setup logging environment. */ protected void setupLogging(@Nullable final Level level) { // install JUL adapter SLF4JBridgeHandler.removeHandlersForRootLogger(); SLF4JBridgeHandler.install(); // conifgure gossip bootstrap loggers with target factory Log.configure(LoggerFactory.getILoggerFactory()); log.debug("Logging setup; level: {}", level); } /** * Create a new {@link Shell}. */ @VisibleForTesting protected Shell createShell(final IO io, final Variables variables, final Branding branding) throws Exception { log.debug("Creating shell instance"); List<Module> modules = new ArrayList<>(); URLClassSpace space = new URLClassSpace(getClass().getClassLoader()); modules.add(new SpaceModule(space, BeanScanning.INDEX)); final BeanContainer container = new BeanContainer(); modules.add(BeanContainer.module(container)); modules.add(binder -> { binder.bind(ThreadIO.class).toInstance(threadIO); binder.bind(Branding.class).toInstance(branding); binder.bind(ShellBuilder.class).to(ShellBuilderImpl.class); }); configure(modules); Injector injector = Guice.createInjector(new WireModule(modules)); // injector is automatically bound to BeanLocator by sisu return injector.getInstance(ShellBuilder.class).branding(branding).io(io).variables(variables).build(); } /** * Allow sub-class to customize container. */ protected void configure(@Nonnull final List<Module> modules) { // empty } // // Helpers // /** * Create the {@link Terminal}. */ @VisibleForTesting protected Terminal createTerminal(final Branding branding) throws Exception { return TerminalBuilder.builder().name(branding.getProgramName()).system(true).nativeSignals(true) .signalHandler(Terminal.SignalHandler.SIG_IGN) // ignore signals by default .build(); } /** * Create the {@link StreamSet} used to register. */ @VisibleForTesting protected StreamSet createStreamSet(final Terminal terminal) { InputStream in = new FilterInputStream(terminal.input()) { @Override public void close() throws IOException { // ignore } }; PrintStream out = new PrintStream(terminal.output(), true) { @Override public void close() { // ignore } }; return new StreamSet(in, out); } /** * Allow control of exit behavior. */ @VisibleForTesting protected void exit(final int code) { log.debug("Existing with code: {}", code); System.exit(code); } }