/**
* $Id$
* $Date$
*
*/
package org.xmlsh.sh.module;
import static org.xmlsh.util.UnifiedFileAttributes.MatchFlag.DIRECTORIES;
import static org.xmlsh.util.UnifiedFileAttributes.MatchFlag.EXECUTABLE;
import static org.xmlsh.util.UnifiedFileAttributes.MatchFlag.FILES;
import static org.xmlsh.util.UnifiedFileAttributes.MatchFlag.HIDDEN_NAME;
import static org.xmlsh.util.UnifiedFileAttributes.MatchFlag.HIDDEN_SYS;
import static org.xmlsh.util.UnifiedFileAttributes.MatchFlag.READABLE;
import static org.xmlsh.util.UnifiedFileAttributes.MatchFlag.SYSTEM;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import org.apache.logging.log4j.Logger;
import org.xmlsh.builtin.commands.colon;
import org.xmlsh.builtin.commands.declare;
import org.xmlsh.builtin.commands.echo;
import org.xmlsh.builtin.commands.eval;
import org.xmlsh.builtin.commands.exit;
import org.xmlsh.builtin.commands.jobs;
import org.xmlsh.builtin.commands.log;
import org.xmlsh.builtin.commands.printvar;
import org.xmlsh.builtin.commands.read;
import org.xmlsh.builtin.commands.require;
import org.xmlsh.builtin.commands.set;
import org.xmlsh.builtin.commands.shift;
import org.xmlsh.builtin.commands.source;
import org.xmlsh.builtin.commands.test;
import org.xmlsh.builtin.commands.trap;
import org.xmlsh.builtin.commands.unset;
import org.xmlsh.builtin.commands.wait;
import org.xmlsh.builtin.commands.xbreak;
import org.xmlsh.builtin.commands.xcd;
import org.xmlsh.builtin.commands.xcontinue;
import org.xmlsh.builtin.commands.xecho;
import org.xmlsh.builtin.commands.xfalse;
import org.xmlsh.builtin.commands.ximport;
import org.xmlsh.builtin.commands.xmkpipe;
import org.xmlsh.builtin.commands.xmlsh;
import org.xmlsh.builtin.commands.xmlshui;
import org.xmlsh.builtin.commands.xread;
import org.xmlsh.builtin.commands.xshopt;
import org.xmlsh.builtin.commands.xthrow;
import org.xmlsh.builtin.commands.xtrue;
import org.xmlsh.builtin.commands.xtype;
import org.xmlsh.builtin.commands.xversion;
import org.xmlsh.builtin.commands.xwhich;
import org.xmlsh.core.AbstractCommand;
import org.xmlsh.core.CoreException;
import org.xmlsh.core.ExternalCommand;
import org.xmlsh.core.FunctionCommand;
import org.xmlsh.core.ICommand;
import org.xmlsh.core.IXFunction;
import org.xmlsh.core.ScriptCommand;
import org.xmlsh.core.ScriptCommand.SourceMode;
import org.xmlsh.core.ScriptSource;
import org.xmlsh.core.SearchPath;
import org.xmlsh.internal.commands.readconfig;
import org.xmlsh.sh.core.SourceLocation;
import org.xmlsh.sh.shell.IFunctionDefiniton;
import org.xmlsh.sh.shell.Shell;
import org.xmlsh.sh.shell.ShellConstants;
import org.xmlsh.util.FileUtils;
import org.xmlsh.util.PathMatchOptions;
import org.xmlsh.util.StringPair;
import org.xmlsh.util.Util;
public abstract class CommandFactory {
public static final String kCOMMANDS_HELP_XML = "/org/xmlsh/resources/help/commands.xml";
public static final String kFUNCTIONS_HELP_XML = "/org/xmlsh/resources/help/functions.xml";
public static Logger mLogger = org.apache.logging.log4j.LogManager
.getLogger(CommandFactory.class);
private static CommandFactory _instance = null;
private static HashMap<String, Class<? extends AbstractCommand>> mBuiltinCommands = new HashMap<>();
private static PathMatchOptions sExecutablePath = new PathMatchOptions().withFlagsHidden( DIRECTORIES,HIDDEN_NAME,HIDDEN_SYS, SYSTEM ).
withFlagsMatching(FILES,EXECUTABLE,READABLE);
private static PathMatchOptions sExplicitPath = new PathMatchOptions().withFlagsHidden( DIRECTORIES ).
withFlagsMatching(FILES,READABLE);
private static void addBuiltinCommand(String name,
Class<? extends AbstractCommand> cls) {
mBuiltinCommands.put(name, cls);
}
static {
addBuiltinCommand("cd", xcd.class);
addBuiltinCommand("xecho", xecho.class);
addBuiltinCommand("echo", echo.class);
addBuiltinCommand("false", xfalse.class);
addBuiltinCommand("true", xtrue.class);
addBuiltinCommand("set", set.class);
addBuiltinCommand(".", source.class);
addBuiltinCommand("source", source.class);
addBuiltinCommand("exit", exit.class);
addBuiltinCommand(":", colon.class);
addBuiltinCommand("[", test.class);
addBuiltinCommand("test", test.class);
addBuiltinCommand("shift", shift.class);
addBuiltinCommand("read", read.class);
addBuiltinCommand("xread", xread.class);
addBuiltinCommand("unset", unset.class);
addBuiltinCommand("xwhich", xwhich.class);
addBuiltinCommand("xversion", xversion.class);
addBuiltinCommand("jobs", jobs.class);
addBuiltinCommand("wait", wait.class);
addBuiltinCommand("break", xbreak.class);
addBuiltinCommand("continue", xcontinue.class);
addBuiltinCommand("eval", eval.class);
addBuiltinCommand("declare", declare.class);
addBuiltinCommand("import", ximport.class);
addBuiltinCommand("xmlsh", xmlsh.class);
addBuiltinCommand("throw", xthrow.class);
addBuiltinCommand("log", log.class);
addBuiltinCommand("xtype", xtype.class);
addBuiltinCommand("require", require.class);
addBuiltinCommand("xmlshui", xmlshui.class);
addBuiltinCommand("xmkpipe", xmkpipe.class);
addBuiltinCommand("printvar", printvar.class);
addBuiltinCommand("propread", readconfig.class);
addBuiltinCommand("trap", trap.class);
addBuiltinCommand("xshopt",xshopt.class);
}
public static ICommand getCommand(Shell shell, String name, SourceLocation loc)
throws IOException, CoreException, URISyntaxException {
mLogger.entry(shell, name, loc);
ICommand cmd = getCommandFromFunction(shell, name, loc);
if (cmd == null)
cmd = getBuiltin(shell, name, loc);
if (cmd == null)
cmd = getModuleCommand(shell, name);
if (cmd == null)
cmd = getScript(shell, name, SourceMode.RUN, loc);
if (cmd == null)
cmd = getExternal(shell, name, loc);
return mLogger.exit(cmd);
}
/*
* Gets an External command of given name by looking through the External
* Path
*/
private static ICommand getCommandFromFunction(Shell shell, String name,
SourceLocation loc) {
mLogger.entry(shell, name);
IFunctionDefiniton func = shell.getFunctionDecl(name);
if (func != null)
return mLogger.exit(new FunctionCommand(func.getModule(), func
.getName(), func.getBody(), loc));
return mLogger.exit(null);
}
private static ICommand getExternal(Shell shell, String name, SourceLocation loc)
throws IOException {
mLogger.entry(shell, name);
File cmdFile = null;
String ext = FileUtils.getExt(name).toLowerCase();
if (FileUtils.hasDirectory(name)) {
// dont try to exec non extension files with IACL X perms
if( !Util.isBlank(ext) || ! Util.isWindows() )
cmdFile = shell.getExplicitFile(name, sExecutablePath );
if( cmdFile == null && Util.isEmpty(ext)){
if (cmdFile == null && !ext.equals(".exe") && Util.isWindows())
cmdFile = shell.getExplicitFile(name + ".exe", sExecutablePath );
if (cmdFile == null && !ext.equals(".bat") && Util.isWindows())
cmdFile = shell.getExplicitFile(name + ".bat", sExecutablePath);
if (cmdFile == null && !ext.equals(".cmd") && Util.isWindows())
cmdFile = shell.getExplicitFile(name + ".cmd", sExecutablePath);
}
}
if (cmdFile == null) {
SearchPath path = shell.getExternalPath();
if( !Util.isBlank(ext) || ! Util.isWindows() )
cmdFile = path.getFirstFileInPath(shell, name,sExecutablePath);
if (cmdFile == null && !ext.equals(".exe") && Util.isWindows() )
cmdFile = path.getFirstFileInPath(shell, name + ".exe",sExecutablePath);
if (cmdFile == null && !ext.equals(".bat") && Util.isWindows() )
cmdFile = path.getFirstFileInPath(shell, name + ".bat",sExecutablePath);
if (cmdFile == null && !ext.equals(".cmd") && Util.isWindows() )
cmdFile = path.getFirstFileInPath(shell, name + ".cmd",sExecutablePath);
}
if (cmdFile == null)
return mLogger.exit(null);
return mLogger
.exit(new ExternalCommand(cmdFile, loc, shell.getModule()));
}
private static ICommand getModuleCommand(Shell shell, String name) throws IOException, URISyntaxException {
mLogger.entry(shell, name);
StringPair pair = new StringPair(name, ':');
if (pair.hasLeft()) { // prefix:name , prefix non-empty
IModule m ;
mLogger.trace("found prefix - trying command by prefix: ", pair);
if( Util.isBlank(pair.getLeft()) ){
m = shell.getModule() ;
mLogger.trace("blank prefix - use current module",m);
} else {
m = shell.getModuleByPrefix(pair.getLeft());
mLogger.debug("Preix module : " , m );
}
// Allow C:/xxx/yyy to work
// May look like a namespace but isnt
if (m != null) {
mLogger.trace("Found module - try getting command" , m , pair.getRight());
ICommand cls = m.getCommand(pair.getRight());
if (cls != null) {
mLogger.debug("Command Class found: " , cls );
return cls;
}
return mLogger.exit(null);
}
}
/*
* Try all default modules
*/
mLogger.debug("Try default modules");
for (IModule m : shell.getDefaultModules() ) {
assert( m != null );
ICommand cls = m.getCommand(name);
if (cls != null) {
return mLogger.exit(cls);
}
}
return mLogger.exit(null);
}
private static IXFunction getModuleFunction(Shell shell, String name) throws IOException {
mLogger.entry(shell, name);
StringPair pair = new StringPair(name, ':');
if (pair.hasLeft()) { // prefix:name , prefix non-empty
IModule m ;
mLogger.trace("found prefix - trying command by prefix: ", pair);
if( Util.isBlank(pair.getLeft()) ){
m = shell.getModule() ;
mLogger.trace("blank prefix - use current module",m);
} else {
m = shell.getModuleByPrefix(pair.getLeft());
mLogger.debug("Preix module : " , m );
}
// Allow C:/xxx/yyy to work
// May look like a namespace but isnt
if (m != null) {
mLogger.trace("Found module - try getting command" , m , pair.getRight());
IXFunction cls = m.getFunction(pair.getRight());
if (cls != null) {
mLogger.debug("Command Class found: " , cls );
return cls;
}
}
// Has prefix but cant find -
return mLogger.exit(null);
}
/*
* Try all default modules
*/
mLogger.debug("Try default modules");
for (IModule m : shell.getDefaultModules() ) {
IXFunction cls = m.getFunction(name);
if (cls != null) {
return mLogger.exit(cls);
}
}
return mLogger.exit(null);
}
/*
*
* Try each Path in turn, using each pathExts (or exactly if no extenions)
* paths and exts may be null
* If paths not null
* If exts is not null try each extension in turn (may be "" ) in the in each path
* If paths is null
* Try file in curdir , with each ext (may be "")
* If paths is [] do not try any path
*
*/
public static File findFirstFileInPaths(Shell shell , String name , String[] exts , SearchPath [] paths )
{
mLogger.entry(shell, name, Util.traceArray(exts), Util.traceArray(paths) );
if( exts == null )
exts = Util.toArray( "" );
File file = null ;
if( paths == null ){
for( String ext : exts ){
file = tryScriptFile( shell , name + ext , false );
if( file != null )
return mLogger.exit(file);
}
return mLogger.exit(null );
}
for( SearchPath path : paths ){
for( String ext : exts ){
try {
file = path.getFirstFileInPath(shell, name + ext , sExplicitPath);
if( file != null && isXScriptFile(file.toPath(), shell.getInputTextEncoding() , false ) )
return mLogger.exit(file);
} catch (IOException e) {
mLogger.catching(e);
continue;
}
}
}
return mLogger.exit(null);
}
private static boolean isXScriptFile(Path path, String encoding , boolean anyScripty) {
return FileUtils.isXScript(path, anyScripty , encoding);
}
protected static File tryFile(Shell shell, String name) {
mLogger.entry(shell, name);
File file = null ;
try {
file = shell.getExplicitFile(name, sExplicitPath);
}
catch( IOException e ) {
mLogger.catching(e);
}
return mLogger.exit(file);
}
/*
* Search for script in the following
* If name is a URL try opening the URL as
* If name ends with .xsh and in SOURCE or IMPORT
*
*/
public static ScriptSource getScriptSource(Shell shell, PName pname,
SourceMode sourceMode, List<URL> at) throws IOException, CoreException, URISyntaxException {
mLogger.entry(shell, pname,sourceMode,at);
File scriptFile = null;
/*
* Refuse to serve up known windows non scripts as scripts
*/
if( Util.isWindows()){
String ext = FileUtils.getExt(pname.getName()).toLowerCase();
if(Util.isEqual(ext,".exe")||
Util.isEqual(ext, ".cmd") ||
Util.isEqual(ext, ".bat"))
return null ;
}
// If at - try to use it explicitly ignoring the name for location
URL url = null;
ScriptSource src = null ;
if( at != null ){
Exception caught = null;
for( URL u : at ){
try {
src = getScriptSource(shell, u, pname.getName());
} catch (Exception e) {
caught = e ;
mLogger.catching(e);
continue ;
}
}
if( src != null )
return mLogger.exit(src) ;
mLogger.throwing( caught != null ? caught : new FileNotFoundException(pname.toString()) );
}
// If name has a scheme try that first
boolean hasPrefix = pname.hasPrefix(true);
String psname = pname.toString();
if( hasPrefix ){
url = Util.tryURL(psname);
if (url != null){
mLogger.debug("script has URL {} ", url );
return mLogger.exit(getScriptSource(shell, url, pname.getName() ));
}
// Prefix might be a drive leter -- if so try an absolute file
if( Util.isWindows() && FileUtils.rootPathLength(FileUtils.toJavaPath(psname)) > 0 ){
mLogger.trace("Trying name as windows file: {} " , pname );
scriptFile = shell.getExplicitFile(pname.toString(), true );
if( scriptFile != null && ! scriptFile.isFile() )
scriptFile = null ;
// Explicit rooted path - anything close to text mathces
if( scriptFile != null && ! isXScriptFile(scriptFile.toPath(), shell.getInputTextEncoding(), true ))
scriptFile = null;
}
}
if( scriptFile == null ){
String name = psname;
String ext = FileUtils.getExt( name );
if( FileUtils.hasDirectory(name) ){
scriptFile = tryScriptFile(shell, name, true ) ;
}
if( scriptFile == null && sourceMode == SourceMode.SOURCE || sourceMode == SourceMode.IMPORT ){
scriptFile = findFirstFileInPaths( shell , name , getExtensions( ext , ShellConstants.XSH_EXTENSION ) , null);
}
if (scriptFile == null) {
SearchPath[] paths;
// searh in XPATH for include/source and XMODPATH for modules
SearchPath path = null ;
switch (sourceMode) {
case IMPORT:
paths = new SearchPath[] { shell.getModulePath() } ;
break;
case RUN:
case SOURCE:
case VALIDATE:
default:
paths = new SearchPath[] { shell.getPath(ShellConstants.ENV_XPATH, true) , shell.getPath(ShellConstants.PATH, true) } ;
break ;
}
scriptFile = findFirstFileInPaths( shell , name , getExtensions( ext , ShellConstants.XSH_EXTENSION ) , paths );
}
}
if (scriptFile == null)
return mLogger.exit(null);
mLogger.debug("getting script source from file {}" , scriptFile);
ScriptSource ss = getScriptSource(shell, scriptFile.toURI().toURL(),
scriptFile.getPath());
return mLogger.exit(ss);
}
// tryFile but make sure its text like
private static File tryScriptFile(Shell shell, String name, boolean anyScripty) {
mLogger.entry(shell,name);
File f = tryFile( shell , name );
if( f != null && isXScriptFile( f.toPath() , shell.getInputTextEncoding() , anyScripty))
return mLogger.exit(f);
return mLogger.exit(null) ;
}
/*
* Return a list of extensions to try
* Excluding the current extension (convert to ""
*/
private static String[] getExtensions(String ext, String... exts) {
List<String> list = new ArrayList<>();
list.add("");
if( ! Util.isBlank(ext) && Util.contains( exts , ext ))
list.add("");
for( String e : exts )
if( ! Util.isBlank(e) && ! e.equals(ext) )
list.add(e);
return list.toArray( new String[0] );
}
public static ScriptCommand getScript(Shell shell, String name,
SourceMode sourceMode, SourceLocation loc) throws IOException,
CoreException, URISyntaxException {
mLogger.entry(shell, name,sourceMode);
ScriptSource source = getScriptSource(shell, new PName(name), sourceMode,null);
if (source == null)
return mLogger.exit( null);
return mLogger.exit( new ScriptCommand(source, sourceMode, loc, shell.getModule()));
}
private static ICommand getBuiltin(Shell shell, String name, SourceLocation loc) {
mLogger.entry(shell, name);
Class<?> cls = mBuiltinCommands.get(name);
if (cls != null) {
mLogger.debug("Creating Command from class {} ",cls);
try {
AbstractCommand b = (AbstractCommand) cls.newInstance();
b.setLocation(loc);
return b;
} catch (Exception e) {
mLogger.debug("caught exception" , e );
return mLogger.exit( null );
}
} else
return mLogger.exit( null);
}
public static URL getHelpURL(Shell shell, String name) {
URL url = null;
if (url == null)
url = getBuiltinHelpURL(shell, name);
if (url == null)
url = getNativeHelpURL(shell, name);
return url;
}
private static URL getNativeHelpURL(Shell shell, String name) {
StringPair pair = new StringPair(name, ':');
if (pair.hasLeft()) { // prefix:name , prefix non-empty
IModule m = Util.isBlank(pair.getLeft()) ? shell.getModule()
: shell.getModuleByPrefix(pair.getLeft());
// Allow C:/xxx/yyy to work
// May look like a namespace but isnt
if (m != null && m.hasHelp(pair.getRight()))
return m.getHelpURL();
return null;
}
/*
* Try all default modules
*/
for (IModule m : shell.getDefaultModules()) {
if (m.hasHelp(name))
return m.getHelpURL();
}
return null;
}
private static URL getBuiltinHelpURL(Shell shell, String name) {
if (mBuiltinCommands.containsKey(name))
return shell.getResource(kCOMMANDS_HELP_XML);
else
return null;
}
public static IXFunction getBuiltinFunction(Shell shell, String name) {
mLogger.entry(shell, name);
StringPair pair = new StringPair(name, ':');
if (pair.hasLeft()) { // prefix:name , prefix non-empty
IModule m;
mLogger.trace("found prefix - trying command by prefix: ", pair);
if( Util.isBlank(pair.getLeft()) ){
m = shell.getModule() ;
mLogger.trace("blank prefix - use current module",m);
} else {
m = shell.getModuleByPrefix(pair.getLeft());
mLogger.debug("Preix module : " , m );
}
// Allow C:/xxx/yyy to work
// May look like a namespace but isnt
if (m != null) {
mLogger.debug("Found module",m);
IXFunction cls = m.getFunction(pair.getRight());
if (cls != null) {
return mLogger.exit(cls);
}
}
return mLogger.exit( null);
}
/*
* Try all default modules
*/
mLogger.debug("Try default modules");
for ( IModule m : shell.getDefaultModules() ) {
IXFunction cls = m.getFunction(name);
if (cls != null)
return mLogger.exit(cls);
}
return mLogger.exit(null);
}
public static ScriptSource getScriptSource(Shell shell, URL url, String name)
throws CoreException, IOException, URISyntaxException {
mLogger.entry(shell, name,url);
return new ScriptSource(name, url, shell.getInputTextEncoding());
}
private static ScriptSource getScriptSource(Shell shell, File file, String name)
throws CoreException, IOException, URISyntaxException {
mLogger.entry(shell, name,file);
return new ScriptSource(name, file.toURI().toURL(),
shell.getInputTextEncoding());
}
public static ScriptCommand getScript(Shell shell, URL url, String name,
SourceMode sourceMode, SourceLocation loc) throws CoreException,
IOException, URISyntaxException {
mLogger.entry(shell, url);
return new ScriptCommand(getScriptSource(shell, url, name), sourceMode,
loc, shell.getModule());
}
public static ScriptCommand getScript(Shell shell, File file, String name,
SourceMode sourceMode, SourceLocation loc) throws CoreException,
IOException, URISyntaxException {
mLogger.entry(shell, name , file , sourceMode );
return new ScriptCommand(getScriptSource(shell, file, name),
sourceMode, loc, shell.getModule());
}
public static IXFunction getFunction(Shell shell, String name) throws IOException {
mLogger.entry(shell, name);
// Try builtin functions first
IXFunction func = getBuiltinFunction(shell, name);
// global scope shell functions
if(func == null){
IFunctionDefiniton funcdecl = shell.getFunctionDecl(name);
if( funcdecl != null )
func = funcdecl.getFunction();
}
// IModule scope shell functions
if( func == null ){
func = getModuleFunction(shell,name);
}
return mLogger.exit(func );
}
}
//
//
// Copyright (C) 2008-2014 David A. Lee.
//
// The contents of this file are subject to the "Simplified BSD License" (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.opensource.org/licenses/bsd-license.php
//
// Software distributed under the License is distributed on an "AS IS" basis,
// WITHOUT WARRANTY OF ANY KIND, either express or implied.
// See the License for the specific language governing rights and limitations
// under the License.
//
// The Original Code is: all this file.
//
// The Initial Developer of the Original Code is David A. Lee
//
// Portions created by (your name) are Copyright (C) (your legal entity). All
// Rights Reserved.
//
// Contributor(s): none.
//