de.tsl2.nano.incubation.terminal.SIShell Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of tsl2.nano.terminal Show documentation
Show all versions of tsl2.nano.terminal Show documentation
TSL2 Framework Terminal (Console Application Framework named SIShell, providing Actions, Options, Commands, Inputs, lots of Selectors, PlatformManagement)
/*
* File: $HeadURL$
* Id : $Id$
*
* created by: Tom
* created on: 13.12.2014
*
* Copyright: (c) Thomas Schneider 2014, all rights reserved
*/
package de.tsl2.nano.incubation.terminal;
import static de.tsl2.nano.incubation.terminal.TextTerminal.BLOCK_BAR;
import static de.tsl2.nano.incubation.terminal.TextTerminal.SCREEN_HEIGHT;
import static de.tsl2.nano.incubation.terminal.TextTerminal.SCREEN_WIDTH;
import static de.tsl2.nano.incubation.terminal.TextTerminal.getTextFrame;
import java.io.File;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Scanner;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.imageio.ImageIO;
import org.apache.commons.logging.Log;
import org.simpleframework.xml.Attribute;
import org.simpleframework.xml.Default;
import org.simpleframework.xml.DefaultType;
import org.simpleframework.xml.Element;
import org.simpleframework.xml.ElementMap;
import org.simpleframework.xml.core.Commit;
import de.tsl2.nano.core.AppLoader;
import de.tsl2.nano.core.ENV;
import de.tsl2.nano.core.Finished;
import de.tsl2.nano.core.ManagedException;
import de.tsl2.nano.core.Messages;
import de.tsl2.nano.core.classloader.NetworkClassLoader;
import de.tsl2.nano.core.cls.PrivateAccessor;
import de.tsl2.nano.core.cls.UnboundAccessor;
import de.tsl2.nano.core.execution.CompatibilityLayer;
import de.tsl2.nano.core.execution.SystemUtil;
import de.tsl2.nano.core.log.LogFactory;
import de.tsl2.nano.core.serialize.XmlUtil;
import de.tsl2.nano.core.util.ConcurrentUtil;
import de.tsl2.nano.core.util.FileUtil;
import de.tsl2.nano.core.util.NetUtil;
import de.tsl2.nano.core.util.StringUtil;
import de.tsl2.nano.core.util.Util;
import de.tsl2.nano.incubation.platform.PlatformManagement;
import de.tsl2.nano.incubation.terminal.IItem.Type;
import de.tsl2.nano.incubation.terminal.item.Container;
import de.tsl2.nano.util.SchedulerUtil;
/**
* Structured Input Shell (SIShell) showing a textual manual. The configuration can be read through an xml file
* (standard name: {@link #DEFAULT_NAME}). For further informations, see {@link IItem}.
*
*
* Features:
* - simple input and output on small screens
* - input constraints check
* - configuration over xml
* - result will be written to a property map
* - tree nodes can be selected through numbers or names (the first unique characters are enough)
* - batch mode possible
* - macro recording and replaying
* - simplified java method calls
* - variable output sizes and styles
* - workflow conditions: items are active if an optional condition is true
* - if an item container (a tree) has only one visible item (perhaps filtered through conditions), it delegates directly to that item
* - actions get the entire environment properties (including system properties) on calling run().
* - sequential mode: if true, all tree items will be asked for in a sequential mode.
* - show ascii-pictures (transformed from pixel-images) for an item (description must point to an image file)
* - extends itself downloading required jars from network (if {@link #useNetworkExtension} ist true)
* - schedule mode: starts a scheduler for an action
* - selectors for files, content of files, class members, properties, csv-files etc.
* - administration modus to create/change/remove items, change terminal properties
*
*
* @author Tom
* @version $Revision$
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
@Default(value = DefaultType.FIELD, required = false)
public class SIShell implements IItemHandler, Serializable {
/** serialVersionUID */
private static final long serialVersionUID = -5767124822662015899L;
transient private static final Log LOG;
static {
LogFactory.setPrintToConsole(false);
LOG = LogFactory.getLog(SIShell.class);
}
/** used as file name */
@Attribute
String name = DEFAULT_NAME;
/** utility to read user input */
transient Scanner input;
transient InputStream in;
transient PrintStream out;
@Attribute
int width = SCREEN_WIDTH;
/** if height < 1, the height will be dynamic to the items height */
@Attribute
int height = SCREEN_HEIGHT;
@Attribute
int style = BLOCK_BAR;
@Attribute
boolean bars = true;
/** item properties */
transient Properties env;
/** default: false. if true, on each terminal save, the terminals xml serialization file will be stored. */
@Attribute(required = false)
boolean refreshConfig = false;
transient Exception lastException;
/**
* predefined variables (not changable through user input) copied to the {@link #env} but not saved in property
* file. mostly technical definitions.
*/
@ElementMap(entry = "definition", attribute = true, inline = true, keyType = String.class, key = "name", required = false, value = "value", valueType = Object.class)
Map definitions;
/** batch file name. the batch file contains input instructions (numbers or names) separated by '\n'. */
@Element(required = false)
String batch;
/** base item - should be a Selection */
@Element
IItem root;
/** useNetworkExtension */
@Attribute
boolean useNetworkExtension = true;
/**
* true, if macro recording was started through {@link #KEY_MACRO_RECORD} and not yet stoped with
* {@link #KEY_MACRO_STOP}
*/
transient boolean isRecording;
/** if true, all tree items will be accessed directly and sequentially */
@Attribute(required = false)
boolean sequential = false;
@Element(required = false)
String clearScreenCmd = AppLoader.isUnix() ? "clear" : "cls";
/** command identifier */
static final String KEY_COMMAND = ":";
/*
* available commands
*/
static final String KEY_HELP = "help";
/** prints system informations */
static final String KEY_INFO = "info";
/** prints content of all platform MBeans (JMX) */
static final String KEY_PLATFORMINFO = "platform";
/** prints all system properties */
static final String KEY_PROPERTIES = "properties";
/** starts macro recording. user input will be stored to {@link #batch} and saved on terminal end. */
static final String KEY_MACRO_RECORD = "record";
/** stops macro recording */
static final String KEY_MACRO_STOP = "stop";
/** starts a scheduler for the given action */
static final String KEY_SCHEDULE = "schedule";
/** starts the given reflection expression on a given property */
static final String KEY_REFLECT = "reflect";
static final String KEY_ADMIN = "admin";
public static final String KEY_SEQUENTIAL0 = "sequential";
static final String KEY_USENETWORKEXTENSION = "network";
public static final String KEY_LASTEXCEPTION = "exception";
/** saves the current state to xml and property files */
static final String KEY_SAVE = "save";
/** quits the terminal */
static final String KEY_QUIT = "quit";
public static final String PREFIX = "sishell.";
public static final String KEY_NAME = PREFIX + "name";
public static final String KEY_WIDTH = PREFIX + "width";
public static final String KEY_HEIGHT = PREFIX + "height";
public static final String KEY_SEQUENTIAL = PREFIX + "sequential";
/** default script file name */
public static final String DEFAULT_NAME = PREFIX + "xml";
static final String ASK_ENTER = ENV.translate("ask.enter", false);
private static final String LOGO = "tsl2nano.logo.png";
public SIShell() {
initDeserialization();
}
public SIShell(IItem root) {
this(root, System.in, System.out);
}
public SIShell(IItem root, InputStream in, PrintStream out) {
this(root, in, out, SCREEN_WIDTH, SCREEN_HEIGHT, BLOCK_BAR, null);
}
public SIShell(IItem root, InputStream in, PrintStream out, int width, int height, int style) {
this(root, in, out, width, height, style, null);
}
/**
* constructor
*
* @param root
* @param input
* @param in
* @param out
*/
public SIShell(IItem root,
InputStream in,
PrintStream out,
int width,
int height,
int style,
Map defintions) {
super();
this.root = root;
this.in = in;
this.out = out;
this.width = width;
this.height = height;
this.style = style;
this.definitions = defintions;
this.env = createEnvironment(name, defintions);
}
static Properties createEnvironment(String name, Map definitions) {
String p = name + ".properties";
Properties env = new File(p).getAbsoluteFile().canRead() ? FileUtil.loadPropertiesFromFile(p) : new Properties();
if (definitions != null) {
env.putAll(definitions);
}
return env;
}
public static SIShell create(String file) {
File ffile = new File(file);
//do a pre-check on the toolbox configuration file which needs ant to be available
if (file.contains(DEFAULT_NAME) && ffile.getAbsoluteFile().exists() && !NetUtil.isOnline()
&& !new CompatibilityLayer().isAvailable("org.apache.tools.ant.Task"))
throw new ManagedException("error.ant.missing", file);
SIShell t =
ffile.exists() ? XmlUtil.loadXml(file, SIShell.class) : new SIShell(new Container(file, null));
t.name = file;
return t;
}
/**
* {@inheritDoc}
*/
@Override
public void run() {
try {
LOG.info("starting SI-Shell " + name);
input = new Scanner(in);
prepareEnvironment(env, root);
// //welcome screen
if (!isInBatchMode()) {
printAsciiImage(LOGO, new PrintWriter(out), width, height > 0 ? height : 22, true, bars);
nextLine(in);
}
//if only one tree-item available, go to that item
if (root instanceof Container) {
root = ((Container) root).delegateToUniqueChild(root, in, out, env);
}
serve(root, in, out, env);
shutdown();
} catch (Finished ex) {
shutdown();
} catch (Exception ex) {
ManagedException.forward(ex);
} finally {
if (input != null) {
input.close();
}
}
}
public static void printAsciiImage(String name,
PrintWriter out,
int width,
int height,
boolean resource,
boolean bars) {
try {
if (resource) {
new AsciiImage(bars ? AsciiImage.BARS : AsciiImage.CHARS, AsciiImage.RGB).convertToAscii(
ImageIO.read(FileUtil.getResource(name)), out, width, height).flush();
} else {
new AsciiImage().convertToAscii(name, out, width, height).flush();
}
} catch (Exception e) {
//it's only a logo, no problem (perhaps on android)
LOG.error(e.toString());
}
}
/**
* see {@link #definitions}
*
* @return Returns the definitions.
*/
public Map getDefinitions() {
return definitions;
}
/**
* see {@link #definitions}
*
* @param definitions The definitions to set.
*/
public void setDefinitions(Map definitions) {
this.definitions = definitions;
}
protected void shutdown() {
save(refreshConfig || !new File(name).getAbsoluteFile().exists());
String shutdownInfo =
ENV.translate("message.shutdown", false, name);
printScreen(shutdownInfo, in, out, null, width, height, style, true, isInBatchMode());
LOG.info("si-shell " + name + " ended");
}
protected void save(boolean saveConfiguration) {
if (saveConfiguration) {
XmlUtil.saveXml(name, this);
}
Set