/** * $Id$ * $Date$ * */ package org.xmlsh.core; import static org.xmlsh.core.XVariable.XVarFlag.EXPORT; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; import java.net.URISyntaxException; import java.nio.file.Path; import java.util.Collection; import java.util.EnumSet; import java.util.Stack; import javax.xml.transform.Source; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.xml.sax.InputSource; import org.xmlsh.core.XVariable.XVarFlag; import org.xmlsh.core.io.AbstractPort; import org.xmlsh.core.io.FileOutputPort; import org.xmlsh.core.io.OutputPort; import org.xmlsh.core.io.ShellIO; import org.xmlsh.core.io.StreamInputPort; import org.xmlsh.core.io.StreamOutputPort; import org.xmlsh.core.io.VariableInputPort; import org.xmlsh.core.io.VariableOutputPort; import org.xmlsh.core.io.XValueInputPort; import org.xmlsh.core.io.XValueOutputPort; import org.xmlsh.sh.module.IModule; import org.xmlsh.sh.shell.FunctionDefinitions; import org.xmlsh.sh.shell.Modules; import org.xmlsh.sh.shell.SerializeOpts; import org.xmlsh.sh.shell.Shell; import org.xmlsh.sh.shell.StaticContext; import org.xmlsh.types.TypeFamily; import org.xmlsh.types.xtypes.XValueMap; import org.xmlsh.util.Util; import lombok.extern.log4j.Log4j2; @Log4j2 public class XEnvironment { private Shell mShell; private volatile XIOEnvironment mIO; private Variables mVars; private Stack<StaticContext> mStaticContextStack; private Stack<XIOEnvironment> mSavedIO; private Stack<IModule> mModuleStack; private boolean bClosed = false; /* * (non-Javadoc) * * @see java.lang.Object#finalize() */ @Override protected void finalize() throws Throwable { if(!bClosed) close(); } public XEnvironment(Shell shell, StaticContext ctx, IModule mod, ShellIO io) throws IOException { mLogger.entry(shell, ctx, io); mStaticContextStack = new Stack<>(); mShell = shell; mVars = new Variables(); mIO = new XIOEnvironment(); mModuleStack = new Stack<>(); pushModule(mod, ctx); if(io != null) getIO().initFromIO(io); mLogger.traceExit(); } public void addAutoRelease(AbstractPort obj) { getIO().addAutoRelease(obj); } private XIOEnvironment getIO() { if(mIO == null) { synchronized(this) { mIO = new XIOEnvironment(); } } return mIO; } /* * Standard Varibles */ public XVariable getVar(String name) { mLogger.entry(name); /* * Special variables */ if(name == null) { // $* return XVariable.anonymousInstance(XValue.newXValue(mShell.getArgs())); } switch(name){ case "*": return XVariable.anonymousInstance(XValue.newXValue(mShell.getArgs())); case "@": return mShell.getArgs().size() == 0 ? null : XVariable.anonymousInstance(XValue.newXValue(mShell.getArgs())); case "#": return XVariable.newInstance(name, XValue.newXValue(mShell.getArgs().size())); case "$": return XVariable.newInstance(name, XValue.newXValue(Thread.currentThread().getId())); case "?": return XVariable.newInstance(name, XValue.newXValue(mShell.getStatus())); case "!": return XVariable.newInstance(name, XValue.newXValue(mShell.getLastThreadId())); } if(Util.isInt(name, false)) { int n = Util.parseInt(name, -1); if(n == 0) return XVariable.newInstance(name, XValue.newXValue(mShell.getArg0())); else if(n > 0 && n <= mShell.getArgs().size()) return XVariable.newInstance(name, mShell.getArgs().get(n - 1)); else return null; } return mVars.get(name); } protected XVariable setVar(XVariable var) { mVars.put(var); return var; } public void setIndexedVar(String name, XValue value, String ind) throws CoreException { XVariable var = mVars.get(name); if(var == null) var = XVariable.newInstance(name, XValue.newXValue(TypeFamily.XTYPE, new XValueMap()), effectiveFlags(null)); else var = var.clone(); var.setIndexedValue(value, ind); setVar(var); } /* * Append to a variable as a sequence */ public void appendVar(String name, XValue xvalue) throws InvalidArgumentException { XVariable var = mVars.get(name); if(var == null) { // If no existing variable then dont touch setVar(XVariable.newInstance(name, xvalue)); return; } var = var.clone(); xvalue = var.getValue().append(xvalue); var.setValue(xvalue); setVar(var); } public XVariable declareVar(String name, EnumSet<XVarFlag> flags) throws InvalidArgumentException { XVariable var = mVars.get(name); if(var == null) var = XVariable.newInstance(name, effectiveFlags(flags)); else var = var.clone(effectiveFlags(effectiveFlags(flags))); mVars.put(var); return var; } public XVariable declareVar(String name, EnumSet<XVarFlag> flags, XValue value) throws InvalidArgumentException { XVariable var = mVars.get(name); if(var == null) var = XVariable.newInstance(name, value, effectiveFlags(flags)); else { var = var.clone(effectiveFlags(flags)); var.setValue(value); } ; mVars.put(var); return var; } // TODO: Hook here for variable specific flag settings private EnumSet<XVarFlag> effectiveFlags(EnumSet<XVarFlag> explicitFlags) { EnumSet<XVarFlag> flags = getStaticContext().getVarFlags(); if(mShell.getOpts().isAllLocal() && mShell.isInFunction()) flags = Util.withEnumsAdded(flags, XVariable.localFlags()); if(explicitFlags != null) flags = Util.withEnumsAdded(flags, explicitFlags); return flags; } public XVariable exportVar(String name) throws InvalidArgumentException { XVariable var = mVars.get(name); if(var == null) { var = XVariable.newInstance(name); var.unset(); } var.setFlag(EXPORT); mVars.put(var); return var; } public XVariable setVar(String name, XValue value) throws InvalidArgumentException { return setVar(name, value, null); } public XVariable setVar(String name, XValue value, EnumSet<XVarFlag> explicitFlags) throws InvalidArgumentException { XVariable var = mVars.get(name); if(var == null) { var = XVariable.newInstance(name, value, effectiveFlags(explicitFlags)); } else var = var.newValue(value, effectiveFlags(explicitFlags)); return setVar(var); } @Override public XEnvironment clone() { // TODO When cloning, only export marked for export vars // Add typeset command try { return clone(mShell); } catch (IOException e) { mShell.printErr("Exception cloning shell", e); return null; } } /* * Clone an environment for use in a new thread * * @see java.lang.Object#clone() */ public XEnvironment clone(Shell shell) throws IOException { mLogger.entry(shell); XEnvironment that = new XEnvironment(shell, getStaticContext().clone(), getModule(), null); that.mVars = new Variables(mVars); that.mIO = new XIOEnvironment(getIO()); return mLogger.exit(that); } private void close() { mLogger.entry(bClosed); if(bClosed) { mLogger.warn("Multiple close"); mLogger.exit(); } assert (!(mSavedIO != null && !mSavedIO.isEmpty())); getIO().release(); // close modulesF if(mModuleStack != null) mModuleStack.clear(); // get rid of cycles mModuleStack = null; if(mStaticContextStack != null) mStaticContextStack.clear(); mStaticContextStack = null; bClosed = true; } public Shell getShell() { return mShell; } public Variables getVars() { return mVars; } public Collection<String> getVarNames() { return mVars.getVarNames(); } public String getVarString(String key) { XVariable var = getVar(key); if(var == null) return null; return var.getValue().toString(); } /* * Save the environment by cloning it and pushing it to this * and return the OLD environment */ public void saveIO() throws CoreException { if(mSavedIO == null) mSavedIO = new Stack<XIOEnvironment>(); mSavedIO.push(getIO()); mIO = new XIOEnvironment(getIO()); } public void restoreIO() { getIO().release(); mIO = mSavedIO.pop(); } /** * @param file * @param append * @return * @throws FileNotFoundException * @throws IOException * @throws CoreException * @see org.xmlsh.sh.shell.Shell#getOutputStream(java.lang.String, boolean) */ public OutputStream getOutputStream(String file, boolean append, SerializeOpts opts) throws FileNotFoundException, IOException, CoreException { return mShell.getOutputStream(file, append, opts); } public OutputStream getOutputStream(File file, boolean append) throws FileNotFoundException { return mShell.getOutputStream(file, append); } /** * @param s * @param e * @see org.xmlsh.sh.shell.Shell#printErr(java.lang.String, * java.lang.Exception) */ public void printErr(String s, Exception e) { mShell.printErr(s, e); } /** * @param s * @see org.xmlsh.sh.shell.Shell#printErr(java.lang.String) */ public void printErr(String s) { mShell.printErr(s); } /** * @return * @see org.xmlsh.sh.shell.Shell#getCurdir() */ public File getCurdir() { return Shell.getCurdir(); } public Path getCurPath() { return Shell.getCurPath(); } /** * @param cd * @throws IOException * @see org.xmlsh.sh.shell.Shell#setCurdir(java.io.File) */ public void setCurdir(File cd) throws IOException { mShell.setCurdir(cd); } public XValue getVarValue(String name) { XVariable var = getVar(name); if(var == null) return null; else return var.getValue(); } public void unsetVar(String name) throws InvalidArgumentException { mVars.unset(name); } public boolean isStdinSystem() { return getStdin().isSystem(); } public boolean isStdoutSystem() { return getStdout().isSystem(); } public boolean isStderrSystem() { return getStderr().isSystem(); } /** * @return * @throws IOException * @see org.xmlsh.core.XIOEnvironment#getStderr() */ public OutputPort getStderr() { return getIO().getStderr(); } /** * @return * @throws IOException * @see org.xmlsh.core.XIOEnvironment#getStdin() */ public InputPort getStdin() { return getIO().getStdin(); } /** * @return * @throws IOException * @see org.xmlsh.core.XIOEnvironment#getStdout() */ public OutputPort getStdout() { return getIO().getStdout(); } /* * Create or return an output port - managed by the autorelease pool */ public OutputPort getOutput(XValue port, boolean append) throws IOException, InvalidArgumentException { mLogger.entry(port,append); if(port == null) return getStdout(); if(port.isAtomic()) { String name = port.toString().trim(); if(name.equals("-")) return getStdout(); mLogger.trace("creating new output port {}",name); OutputPort p = mShell.newOutputPort(name, append); addAutoRelease(p); return p; } else { OutputPort p = new XValueOutputPort(port); addAutoRelease(p); return p; } } public OutputPort getOutput(String port, boolean append) throws IOException, InvalidArgumentException { return getOutput(XValue.newXValue(port), append); } public OutputPort getOutput(File file, boolean append) throws IOException { return new FileOutputPort(file, append); } /** * @param stderr * @throws IOException * @throws InvalidArgumentException * @see org.xmlsh.core.XIOEnvironment#setStderr(java.io.OutputStream) */ public void setStderr(OutputStream stderr) throws CoreException { getIO().setStderr(stderr); } public void setStderr(OutputPort stderr) throws CoreException { getIO().setStderr(stderr); } /** * @param stdin * @throws IOException * @see org.xmlsh.core.XIOEnvironment#setStdin(java.io.InputStream) */ public void setStdin(InputStream in) throws IOException { setInput(null, in); } public void setStdin(XVariable variable) throws IOException, InvalidArgumentException { setInput(null, variable); } public void setStdin(InputPort in) throws IOException { setInput(null, in); } public InputPort setInput(String name, InputStream in) throws IOException { return getIO().setInput(name, new StreamInputPort(in, null)); } public InputPort setInput(String name, XVariable variable) throws IOException, InvalidArgumentException { assert (variable != null && !variable.isNull()); return getIO().setInput(name, new VariableInputPort(variable)); } public InputPort setInput(String name, InputPort in) throws IOException { return getIO().setInput(name, in); } public void setStdout(OutputStream out) throws CoreException { setOutput(null, new StreamOutputPort(out)); } public void setStdout(OutputPort port) throws CoreException { setOutput(null, port); } public void setStdout(XVariable xvar) throws CoreException { setOutput(null, new VariableOutputPort(xvar)); } public void setOutput(String name, OutputStream out) throws CoreException { setOutput(name, new StreamOutputPort(out)); } public void setOutput(String name, XVariable xvar) throws CoreException { mLogger.entry( name , xvar ); assert (xvar != null); setOutput(name, new VariableOutputPort(xvar)); } public void setOutput(String name, OutputPort out) throws CoreException { mLogger.entry(name,out); getIO().setOutput(name, out); } public void declareNamespace(String ns) { getNamespaces().declare(ns); } public void declareNamespace(String prefix, String uri) { getNamespaces().declare(prefix, uri); } public Namespaces getNamespaces() { return getStaticContext().getNamespaces(); } public InputStream getInputStream(XValue file, SerializeOpts opts) throws IOException, InvalidArgumentException, CoreException { return getInput(file).asInputStream(opts); } public Source getSource(XValue value, SerializeOpts opts) throws IOException, InvalidArgumentException, CoreException { return getInput(value).asSource(opts); } /* * Get an input by name or value * * If port is null return stdin * If port is a string * If port is a string * if equals to "-" return stdin * if looks like "scheme://path" return a port based on an input stream from * UI * if looks like "name" return a port based on an input stream by filename * if port is a node return an anonymous port based on a value * */ public InputPort getInput(XValue port) throws IOException, InvalidArgumentException { if(port == null) return getStdin(); if(port.isAtomic()) { String name = port.toString().trim(); if(name.equals("-")) return getStdin(); InputPort p = mShell.newInputPort(name); // Port is not managed, add to autorelease addAutoRelease(p); return p; } else { XValueInputPort p = new XValueInputPort(port); // Port is not managed, add to autorelease addAutoRelease(p); return p; } } /* * Get an input port explicitly by its name */ public InputPort getInputPort(String name) { return getIO().getInputPort(name); } public InputPort getInput(String name) throws CoreException, IOException { return getInput(XValue.newXValue(name)); } public OutputPort getOutputPort(String name) { return getIO().getOutputPort(name); } public String getAbsoluteURI(String sysid) throws URISyntaxException { URI uri = new URI(sysid); if(uri.isAbsolute()) return sysid; URI absolute = getBaseURI().resolve(sysid); return absolute.toString(); } public URI getBaseURI() { return getCurdir().toURI(); } public OutputPort getOutputPort(String portname, boolean append) { // TODO: Add append mode to output ports return getIO().getOutputPort(portname); } public InputSource getInputSource(XValue value, SerializeOpts opts) throws CoreException, FileNotFoundException, IOException { InputPort in = getInput(value); return in.asInputSource(opts); } public boolean isDefined(String name) { return mVars.containsKey(name); } public Variables pushLocalVars() { Variables current = mVars; mVars = mVars.pushLocals(this.getStaticContext().getStaticLocals()); return current; } public void popLocalVars(Variables vars) { mVars = vars; } /** * @return the savedIO */ public XIOEnvironment getSavedIO() { return mSavedIO.peek(); } // "1>&2" public void dupOutput(String portLeft, String portRight) throws IOException { getIO().dupOutput(portLeft, portRight); } public void dupInput(String portLeft, String portRight) throws IOException { getIO().dupInput(portLeft, portRight); } public static boolean isSpecialVarname(String name) { switch(name){ case "#": case "$": case "?": case "!": case "*": case "@": return true; default: return false; } } public StaticContext getStaticContext() { mLogger.entry(); return mStaticContextStack.peek(); } public Modules getModules() { return getStaticContext().getModules(); } public IModule getModuleByPrefix(String prefix) { mLogger.entry(prefix); return mLogger.exit( getStaticContext().getModules().getExistingModuleByPrefix(prefix)); } public FunctionDefinitions getFunctions() { return getStaticContext().getFunctions(); } public IModule pushModule(IModule module, StaticContext ctx) { mLogger.entry(module, mModuleStack.size()); mModuleStack.push(module); if(ctx == null) ctx = mStaticContextStack.peek(); mStaticContextStack.push(ctx); /* * // Push context locals onto the stack * NameValueMap<XVariable> locals = ctx.getStaticLocals(); * if( locals != null ) * Push locals when the shell pushes locals */ // ctx = module.get().getStaticContext(); // mLogger.error("WHAT TO DO WITH PUSHED STATIC CTX: {}",ctx ); return mLogger.exit(module); } public IModule popModule() throws IOException { mLogger.entry(); assert (!mModuleStack.isEmpty()); IModule module = mModuleStack.pop(); assert (!mStaticContextStack.isEmpty()); mStaticContextStack.pop(); /// Leak here ? return mLogger.exit(module); } public StaticContext exportStaticContext() { StaticContext ctx = getStaticContext().export(mVars.getLocalVars()); return ctx; } public Collection<String> getPrefixesForModule(IModule hm) { return mLogger .exit(getStaticContext().getModules().getPrefixesForModule(hm)); } public void initVariable(XVariable var) { if(mVars.containsKey(var.getName())) { mLogger.debug("Attempt to initialize a pre-existing variable: {}", var.getName()); } mVars.put(var); } public IModule getModule() { assert (mModuleStack.size() > 0); return mModuleStack.peek(); } // like close - but not a closeable public void clear() { if(!bClosed) close(); } public XVariable declareVar(String var) throws InvalidArgumentException { return declareVar(var, null); } public void setVar(String name, String value) throws InvalidArgumentException { setVar(name, XValue.newXValue(value)); } } // // // 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. //