package org.apache.velocity.runtime;

/*
 * 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.
 */

import org.apache.velocity.Template;
import org.apache.velocity.app.event.EventCartridge;
import org.apache.velocity.app.event.EventHandler;
import org.apache.velocity.app.event.IncludeEventHandler;
import org.apache.velocity.app.event.InvalidReferenceEventHandler;
import org.apache.velocity.app.event.MethodExceptionEventHandler;
import org.apache.velocity.app.event.ReferenceInsertionEventHandler;
import org.apache.velocity.context.Context;
import org.apache.velocity.context.InternalContextAdapterImpl;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.exception.TemplateInitException;
import org.apache.velocity.exception.VelocityException;
import org.apache.velocity.runtime.directive.Directive;
import org.apache.velocity.runtime.directive.Macro;
import org.apache.velocity.runtime.directive.Scope;
import org.apache.velocity.runtime.directive.StopCommand;
import org.apache.velocity.runtime.parser.LogContext;
import org.apache.velocity.runtime.parser.ParseException;
import org.apache.velocity.runtime.parser.Parser;
import org.apache.velocity.runtime.parser.node.Node;
import org.apache.velocity.runtime.parser.node.SimpleNode;
import org.apache.velocity.runtime.resource.ContentResource;
import org.apache.velocity.runtime.resource.ResourceManager;
import org.apache.velocity.util.ClassUtils;
import org.apache.velocity.util.ExtProperties;
import org.apache.velocity.util.RuntimeServicesAware;
import org.apache.velocity.util.introspection.ChainableUberspector;
import org.apache.velocity.util.introspection.LinkingUberspector;
import org.apache.velocity.util.introspection.Uberspect;

import org.apache.commons.lang3.StringUtils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.Writer;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Properties;
import java.util.Set;

/**
 * <p>This is the Runtime system for Velocity. It is the
 * single access point for all functionality in Velocity.
 * It adheres to the mediator pattern and is the only
 * structure that developers need to be familiar with
 * in order to get Velocity to perform.</p>
 *
 * <p>The Runtime will also cooperate with external
 * systems, which can make all needed setProperty() calls
 * before calling init().</p>
 * <pre>
 * -----------------------------------------------------------------------
 * N O T E S  O N  R U N T I M E  I N I T I A L I Z A T I O N
 * -----------------------------------------------------------------------
 * init()
 *
 * If init() is called by itself the RuntimeInstance will initialize
 * with a set of default values.
 * -----------------------------------------------------------------------
 * init(String/Properties)
 *
 * In this case the default velocity properties are layed down
 * first to provide a solid base, then any properties provided
 * in the given properties object will override the corresponding
 * default property.
 * -----------------------------------------------------------------------
 * </pre>
 *
 * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
 * @author <a href="mailto:jlb@houseofdistraction.com">Jeff Bowden</a>
 * @author <a href="mailto:geirm@optonline.net">Geir Magusson Jr.</a>
 * @version $Id$
 */
public class RuntimeInstance implements RuntimeConstants, RuntimeServices
{
    /**
     *  VelocimacroFactory object to manage VMs
     */
    private  VelocimacroFactory vmFactory = null;

    /**
     * The Runtime logger.  The default instance is the "org.apache.velocity" logger.
     */
    private Logger log = LoggerFactory.getLogger(DEFAULT_RUNTIME_LOG_NAME);

    /**
     * The Runtime parser pool
     */
    private ParserPool parserPool;

    /**
     * Indicate whether the Runtime is in the midst of initialization.
     */
    private boolean initializing = false;

    /**
     * Indicate whether the Runtime has been fully initialized.
     */
    private volatile boolean initialized = false;

    /**
     * These are the properties that are laid down over top
     * of the default properties when requested.
     */
    private ExtProperties overridingProperties = null;

    /**
     * This is a hashtable of initialized directives.
     * The directives that populate this hashtable are
     * taken from the RUNTIME_DEFAULT_DIRECTIVES
     * property file.
     */
    private Map<String, Directive> runtimeDirectives = new Hashtable<>();
    /**
     * Copy of the actual runtimeDirectives that is shared between
     * parsers. Whenever directives are updated, the synchronized
     * runtimeDirectives is first updated and then an unsynchronized
     * copy of it is passed to parsers.
     */
    private Map<String, Directive> runtimeDirectivesShared;

    /**
     * Object that houses the configuration options for
     * the velocity runtime. The ExtProperties object allows
     * the convenient retrieval of a subset of properties.
     * For example all the properties for a resource loader
     * can be retrieved from the main ExtProperties object
     * using something like the following:
     *
     * ExtProperties loaderConfiguration =
     *         configuration.subset(loaderID);
     *
     * And a configuration is a lot more convenient to deal
     * with then conventional properties objects, or Maps.
     */
    private  ExtProperties configuration = new ExtProperties();

    private ResourceManager resourceManager = null;

    /**
     * This stores the engine-wide set of event handlers.  Event handlers for
     * each specific merge are stored in the context.
     */
    private EventCartridge eventCartridge = null;

    /**
     * Whether to use string interning
     */
    private boolean stringInterning = false;

    /**
     * Scope name for evaluate(...) calls.
     */
    private String evaluateScopeName = "evaluate";

    /**
     * Scope names for which to provide scope control objects in the context
     */
    private Set<String> enabledScopeControls = new HashSet<>();

    /**
     *  Opaque reference to something specified by the
     *  application for use in application supplied/specified
     *  pluggable components
     */
    private Map<Object, Object> applicationAttributes = null;

    /**
     *  Uberspector
     */
    private Uberspect uberSpect;

    /**
     * Default encoding
     */
    private String defaultEncoding;

    /**
     * Space gobbling mode
     */
    private SpaceGobbling spaceGobbling;

    /**
     * Whether hyphen is allowed in identifiers
     */
    private boolean hyphenAllowedInIdentifiers;

    /**
     * The LogContext object used to track location in templates
     */
    private LogContext logContext;

    /**
     * Configured parser class
     * @since 2.2
     */
    private Constructor<? extends Parser> parserConstructor;

    /**
     * Configured replacement characters in parser grammar
     * @since 2.2
     */
    private ParserConfiguration parserConfiguration;

    /**
     * Creates a new RuntimeInstance object.
     */
    public RuntimeInstance()
    {
        reset();
    }

    /**
     * This is the primary initialization method in the Velocity
     * Runtime. The systems that are setup/initialized here are
     * as follows:
     *
     * <ul>
     *   <li>Logging System</li>
     *   <li>ResourceManager</li>
     *   <li>EventHandler</li>
     *   <li>Parser Pool</li>
     *   <li>Global Cache</li>
     *   <li>Static Content Include System</li>
     *   <li>Velocimacro System</li>
     * </ul>
     */
    @Override
    public synchronized void init()
    {
        if (!initialized && !initializing)
        {
            try
            {
                log.debug("Initializing Velocity, Calling init()...");
                initializing = true;

                log.trace("*****************************");
                log.debug("Starting Apache Velocity " + VelocityEngineVersion.VERSION);
                log.trace("RuntimeInstance initializing.");

                initializeProperties();
                initializeSelfProperties();
                initializeLog();
                initializeResourceManager();
                initializeDirectives();
                initializeEventHandlers();
                initializeParserPool();

                initializeIntrospection();
                initializeScopeSettings();
                /*
                 *  initialize the VM Factory.  It will use the properties
                 * accessible from Runtime, so keep this here at the end.
                 */
                vmFactory.initVelocimacro();

                log.trace("RuntimeInstance successfully initialized.");

                initialized = true;
                initializing = false;
            }
            catch(RuntimeException re)
            {
                // initialization failed at some point... try to reset everything
                try
                {
                    reset();
                }
                catch(RuntimeException re2) {} // prefer throwing the original exception
                throw re;
            }
            finally
            {
                initializing = false;
            }
        }
    }

    /**
     * Resets the instance, so Velocity can be re-initialized again.
     *
     * @since 2.0.0
     */
    public synchronized void reset()
    {
        this.configuration = new ExtProperties();
        this.defaultEncoding = null;
        this.evaluateScopeName = "evaluate";
        this.eventCartridge = null;
        this.initialized = false;
        this.initializing = false;
        this.overridingProperties = null;
        this.parserPool = null;
        this.enabledScopeControls.clear();
        this.resourceManager = null;
        this.runtimeDirectives = new Hashtable<>();
        this.runtimeDirectivesShared = null;
        this.uberSpect = null;
        this.stringInterning = false;
        this.parserConfiguration = new ParserConfiguration();

        /*
         *  create a VM factory, introspector, and application attributes
         */
        vmFactory = new VelocimacroFactory( this );

        /*
         * and a store for the application attributes
         */
        applicationAttributes = new HashMap<>();
    }

    /**
     * Returns true if the RuntimeInstance has been successfully initialized.
     * @return True if the RuntimeInstance has been successfully initialized.
     * @since 1.5
     */
    @Override
    public boolean isInitialized()
    {
        return initialized;
    }

    /**
     * Init or die! (with some log help, of course)
     */
    private void requireInitialization()
    {
        if (!initialized)
        {
            try
            {
                init();
            }
            catch (Exception e)
            {
                log.error("Could not auto-initialize Velocity", e);
                throw new RuntimeException("Velocity could not be initialized!", e);
            }
        }
    }

    /**
     *  Initialize runtime internal properties
     */
    private void initializeSelfProperties()
    {
        /* initialize string interning (defaults to false) */
        stringInterning = getBoolean(RUNTIME_STRING_INTERNING, true);

        /* initialize indentation mode (defaults to 'lines') */
        String im = getString(SPACE_GOBBLING, "lines");
        try
        {
            spaceGobbling = SpaceGobbling.valueOf(im.toUpperCase(Locale.ROOT));
        }
        catch (NoSuchElementException nse)
        {
            spaceGobbling = SpaceGobbling.LINES;
        }

        /* init parser behavior */
        hyphenAllowedInIdentifiers = getBoolean(PARSER_HYPHEN_ALLOWED, false);
    }

    private char getConfiguredCharacter(String configKey, char defaultChar)
    {
        String configuredChar = getString(configKey);
        if (configuredChar != null)
        {
            if (configuredChar.length() != 1)
            {
                throw new IllegalArgumentException(String.format("value of '%s' must be a single character string, but is '%s'", configKey, configuredChar));
            }
            return configuredChar.charAt(0);
        }
        return defaultChar;
    }

    /**
     *  Gets the classname for the Uberspect introspection package and
     *  instantiates an instance.
     */
    private void initializeIntrospection()
    {
        String[] uberspectors = configuration.getStringArray(RuntimeConstants.UBERSPECT_CLASSNAME);
        for (String rm : uberspectors)
        {
            Object o = null;

            try
            {
                o = ClassUtils.getNewInstance(rm);
            }
            catch (ClassNotFoundException cnfe)
            {
                String err = "The specified class for Uberspect (" + rm
                    + ") does not exist or is not accessible to the current classloader.";
                log.error(err);
                throw new VelocityException(err, cnfe);
            }
            catch (InstantiationException ie)
            {
                throw new VelocityException("Could not instantiate class '" + rm + "'", ie);
            }
            catch (IllegalAccessException ae)
            {
                throw new VelocityException("Cannot access class '" + rm + "'", ae);
            }

            if (!(o instanceof Uberspect))
            {
                String err = "The specified class for Uberspect ("
                    + rm + ") does not implement " + Uberspect.class.getName()
                    + "; Velocity is not initialized correctly.";

                log.error(err);
                throw new VelocityException(err);
            }

            Uberspect u = (Uberspect) o;

            if (u instanceof RuntimeServicesAware)
            {
                ((RuntimeServicesAware) u).setRuntimeServices(this);
            }

            if (uberSpect == null)
            {
                uberSpect = u;
            } else
            {
                if (u instanceof ChainableUberspector)
                {
                    ((ChainableUberspector) u).wrap(uberSpect);
                    uberSpect = u;
                } else
                {
                    uberSpect = new LinkingUberspector(uberSpect, u);
                }
            }
        }

        if(uberSpect != null)
        {
            uberSpect.init();
        }
        else
        {
           /*
            *  someone screwed up.  Lets not fool around...
            */

           String err = "It appears that no class was specified as the"
           + " Uberspect.  Please ensure that all configuration"
           + " information is correct.";

           log.error(err);
           throw new VelocityException(err);
        }
    }

    /**
     * Initializes the Velocity Runtime with properties file.
     * The properties file may be in the file system proper,
     * or the properties file may be in the classpath.
     */
    private void setDefaultProperties()
    {
        InputStream inputStream = null;
        try
        {
            inputStream = getClass().getClassLoader()
                .getResourceAsStream(DEFAULT_RUNTIME_PROPERTIES);

            if (inputStream == null)
                throw new IOException("Resource not found: " + DEFAULT_RUNTIME_PROPERTIES);

            configuration.load( inputStream );

            /* populate 'defaultEncoding' member */
            defaultEncoding = getString(INPUT_ENCODING, ENCODING_DEFAULT);

            log.debug("Default Properties resource: {}", DEFAULT_RUNTIME_PROPERTIES);
        }
        catch (IOException ioe)
        {
            String msg = "Cannot get Velocity Runtime default properties!";
            log.error(msg, ioe);
            throw new RuntimeException(msg, ioe);
        }
        finally
        {
            try
            {
                if (inputStream != null)
                {
                    inputStream.close();
                }
            }
            catch (IOException ioe)
            {
                String msg = "Cannot close Velocity Runtime default properties!";
                log.error(msg, ioe);
                throw new RuntimeException(msg, ioe);
            }
        }
    }

    /**
     * Allows an external system to set a property in
     * the Velocity Runtime.
     *
     * @param key property key
     * @param  value property value
     */
    @Override
    public void setProperty(String key, Object value)
    {
        if (overridingProperties == null)
        {
            overridingProperties = new ExtProperties();
        }

        overridingProperties.setProperty(key, value);
    }


    /**
     * Add all properties contained in the file fileName to the RuntimeInstance properties
     * @param fileName
     */
    public void setProperties(String fileName)
    {
        ExtProperties props = null;
        try
        {
              props = new ExtProperties(fileName);
        }
        catch (IOException e)
        {
              throw new VelocityException("Error reading properties from '"
                + fileName + "'", e);
        }

        Enumeration<String> en = props.keys();
        while (en.hasMoreElements())
        {
            String key = en.nextElement();
            setProperty(key, props.get(key));
        }
    }


    /**
     * Add all the properties in props to the RuntimeInstance properties
     * @param props
     */
    public void setProperties(Properties props)
    {
        Enumeration en = props.keys();
        while (en.hasMoreElements())
        {
            String key = en.nextElement().toString();
            setProperty(key, props.get(key));
        }
    }

    /**
     * Allow an external system to set an ExtProperties
     * object to use.
     *
     * @param  configuration
     * @since 2.0
     */
    @Override
    public void setConfiguration(ExtProperties configuration)
    {
        if (overridingProperties == null)
        {
            overridingProperties = configuration;
        }
        else
        {
            // Avoid possible ConcurrentModificationException
            if (overridingProperties != configuration)
            {
                overridingProperties.combine(configuration);
            }
        }
    }

    /**
     * Add a property to the configuration. If it already
     * exists then the value stated here will be added
     * to the configuration entry. For example, if
     *
     * resource.loader = file
     *
     * is already present in the configuration and you
     *
     * addProperty("resource.loader", "classpath")
     *
     * Then you will end up with a Vector like the
     * following:
     *
     * ["file", "classpath"]
     *
     * @param  key
     * @param  value
     */
    @Override
    public void addProperty(String key, Object value)
    {
        if (overridingProperties == null)
        {
            overridingProperties = new ExtProperties();
        }

        overridingProperties.addProperty(key, value);
    }

    /**
     * Clear the values pertaining to a particular
     * property.
     *
     * @param key of property to clear
     */
    @Override
    public void clearProperty(String key)
    {
        if (overridingProperties != null)
        {
            overridingProperties.clearProperty(key);
        }
    }

    /**
     *  Allows an external caller to get a property.  The calling
     *  routine is required to know the type, as this routine
     *  will return an Object, as that is what properties can be.
     *
     *  @param key property to return
     *  @return Value of the property or null if it does not exist.
     */
    @Override
    public Object getProperty(String key)
    {
        Object o = null;

        /*
         * Before initialization, check the user-entered properties first.
         */
        if (!initialized && overridingProperties != null)
        {
            o = overridingProperties.get(key);
        }

        /*
         * After initialization, configuration will hold all properties.
         */
        if (o == null)
        {
            o = configuration.getProperty(key);
        }
        if (o instanceof String)
        {
            return StringUtils.trim((String) o);
        }
        else
        {
            return o;
        }
    }

    /**
     * Initialize Velocity properties, if the default
     * properties have not been laid down first then
     * do so. Then proceed to process any overriding
     * properties. Laying down the default properties
     * gives a much greater chance of having a
     * working system.
     */
    private void initializeProperties()
    {
        /*
         * Always lay down the default properties first as
         * to provide a solid base.
         */
        if ( !configuration.isInitialized() )
        {
            setDefaultProperties();
        }

        if( overridingProperties != null )
        {
            configuration.combine(overridingProperties);

            /* reinitialize defaultEncoding in case it is overridden */
            defaultEncoding = getString(INPUT_ENCODING, ENCODING_DEFAULT);
        }
    }

    /**
     * Initialize the Velocity Runtime with a Properties
     * object.
     *
     * @param p Velocity properties for initialization
     */
    @Override
    public void init(Properties p)
    {
        setConfiguration(ExtProperties.convertProperties(p));
        init();
    }

    /**
     * Initialize the Velocity Runtime with a
     * properties file path.
     *
     * @param configurationFile
     */
    @Override
    public void init(String configurationFile)
    {
        setProperties(configurationFile);
        init();
    }

    private void initializeResourceManager()
    {
        /*
         * Which resource manager?
         */
        Object inst = getProperty(RuntimeConstants.RESOURCE_MANAGER_INSTANCE);
        String rm = getString(RuntimeConstants.RESOURCE_MANAGER_CLASS);

        if (inst != null)
        {
            if (ResourceManager.class.isAssignableFrom(inst.getClass()))
            {
                resourceManager = (ResourceManager)inst;
                resourceManager.initialize(this);
            }
            else
            {
                String msg = inst.getClass().getName() + " object set as resource.manager.instance is not a valid org.apache.velocity.runtime.resource.ResourceManager.";
                log.error(msg);
                throw new VelocityException(msg);
            }
        }
        else if (rm != null && rm.length() > 0)
        {
            /*
             *  if something was specified, then make one.
             *  if that isn't a ResourceManager, consider
             *  this a huge error and throw
             */

            Object o = null;

            try
            {
               o = ClassUtils.getNewInstance( rm );
            }
            catch (ClassNotFoundException cnfe )
            {
                String err = "The specified class for ResourceManager (" + rm
                    + ") does not exist or is not accessible to the current classloader.";
                log.error(err);
                throw new VelocityException(err, cnfe);
            }
            catch (InstantiationException ie)
            {
              throw new VelocityException("Could not instantiate class '" + rm + "'", ie);
            }
            catch (IllegalAccessException ae)
            {
              throw new VelocityException("Cannot access class '" + rm + "'", ae);
            }

            if (!(o instanceof ResourceManager))
            {
                String err = "The specified class for ResourceManager (" + rm
                    + ") does not implement " + ResourceManager.class.getName()
                    + "; Velocity is not initialized correctly.";

                log.error(err);
                throw new VelocityException(err);
            }

            resourceManager = (ResourceManager) o;
            resourceManager.initialize(this);
            setProperty(RESOURCE_MANAGER_INSTANCE, resourceManager);
         }
         else
         {
            /*
             *  someone screwed up.  Lets not fool around...
             */

            String err = "It appears that no class or instance was specified as the"
            + " ResourceManager.  Please ensure that all configuration"
            + " information is correct.";

            log.error(err);
            throw new VelocityException( err );
        }
    }

    private void initializeEventHandlers()
    {

        eventCartridge = new EventCartridge();
        eventCartridge.setRuntimeServices(this);

        /*
         * For each type of event handler, get the class name, instantiate it, and store it.
         */

        String[] referenceinsertion = configuration.getStringArray(RuntimeConstants.EVENTHANDLER_REFERENCEINSERTION);
        if ( referenceinsertion != null )
        {
            for (String aReferenceinsertion : referenceinsertion)
            {
                EventHandler ev = initializeSpecificEventHandler(aReferenceinsertion, RuntimeConstants.EVENTHANDLER_REFERENCEINSERTION, ReferenceInsertionEventHandler.class);
                if (ev != null)
                    eventCartridge.addReferenceInsertionEventHandler((ReferenceInsertionEventHandler) ev);
            }
        }

        String[] methodexception = configuration.getStringArray(RuntimeConstants.EVENTHANDLER_METHODEXCEPTION);
        if ( methodexception != null )
        {
            for (String aMethodexception : methodexception)
            {
                EventHandler ev = initializeSpecificEventHandler(aMethodexception, RuntimeConstants.EVENTHANDLER_METHODEXCEPTION, MethodExceptionEventHandler.class);
                if (ev != null)
                    eventCartridge.addMethodExceptionHandler((MethodExceptionEventHandler) ev);
            }
        }

        String[] includeHandler = configuration.getStringArray(RuntimeConstants.EVENTHANDLER_INCLUDE);
        if ( includeHandler != null )
        {
            for (String anIncludeHandler : includeHandler)
            {
                EventHandler ev = initializeSpecificEventHandler(anIncludeHandler, RuntimeConstants.EVENTHANDLER_INCLUDE, IncludeEventHandler.class);
                if (ev != null)
                    eventCartridge.addIncludeEventHandler((IncludeEventHandler) ev);
            }
        }

        String[] invalidReferenceSet = configuration.getStringArray(RuntimeConstants.EVENTHANDLER_INVALIDREFERENCES);
        if ( invalidReferenceSet != null )
        {
            for (String anInvalidReferenceSet : invalidReferenceSet)
            {
                EventHandler ev = initializeSpecificEventHandler(anInvalidReferenceSet, RuntimeConstants.EVENTHANDLER_INVALIDREFERENCES, InvalidReferenceEventHandler.class);
                if (ev != null)
                {
                    eventCartridge.addInvalidReferenceEventHandler((InvalidReferenceEventHandler) ev);
                }
            }
        }


    }

    private EventHandler initializeSpecificEventHandler(String classname, String paramName, Class<?> EventHandlerInterface)
    {
        if ( classname != null && classname.length() > 0)
        {
            Object o = null;
            try
            {
                o = ClassUtils.getNewInstance(classname);
            }
            catch (ClassNotFoundException cnfe )
            {
                String err = "The specified class for "
                    + paramName + " (" + classname
                    + ") does not exist or is not accessible to the current classloader.";
                log.error(err);
                throw new VelocityException(err, cnfe);
            }
            catch (InstantiationException ie)
            {
              throw new VelocityException("Could not instantiate class '" + classname + "'", ie);
            }
            catch (IllegalAccessException ae)
            {
              throw new VelocityException("Cannot access class '" + classname + "'", ae);
            }

            if (!EventHandlerInterface.isAssignableFrom(EventHandlerInterface))
            {
                String err = "The specified class for " + paramName + " ("
                    + classname + ") does not implement "
                    + EventHandlerInterface.getName()
                    + "; Velocity is not initialized correctly.";

                log.error(err);
                throw new VelocityException(err);
            }

            EventHandler ev = (EventHandler) o;
            if ( ev instanceof RuntimeServicesAware )
                ((RuntimeServicesAware) ev).setRuntimeServices(this);
            return ev;

        } else
            return null;
    }

    /**
     * Initialize the Velocity logging system.
     */
    private void initializeLog()
    {
        // if we were provided a specific logger or logger name, let's use it
        try
        {
            /* If a Logger instance was set as a configuration
             * value, use that.  This is any class the user specifies.
             */
            Object o = getProperty(RuntimeConstants.RUNTIME_LOG_INSTANCE);
            if (o != null)
            {
                // check for a Logger
                if (Logger.class.isAssignableFrom(o.getClass()))
                {
                    //looks ok
                    log = (Logger)o;
                }
                else
                {
                    String msg = o.getClass().getName() + " object set as runtime.log.instance is not a valid org.slf4j.Logger implementation.";
                    log.error(msg);
                    throw new VelocityException(msg);
                }
            }
            else
            {
                /* otherwise, see if a logger name was specified.
                 */
                o = getProperty(RuntimeConstants.RUNTIME_LOG_NAME);
                if (o != null)
                {
                    if (o instanceof String)
                    {
                        log = LoggerFactory.getLogger((String)o);
                    }
                    else
                    {
                        String msg = o.getClass().getName() + " object set as runtime.log.name is not a valid string.";
                        log.error(msg);
                        throw new VelocityException(msg);
                    }
                }
            }
            /* else keep our default Velocity logger
             */

            /* Initialize LogContext */
            boolean trackLocation = getBoolean(RUNTIME_LOG_TRACK_LOCATION, false);
            logContext = new LogContext(trackLocation);
        }
        catch (Exception e)
        {
            throw new VelocityException("Error initializing log: " + e.getMessage(), e);
        }
    }


    /**
     * This methods initializes all the directives
     * that are used by the Velocity Runtime. The
     * directives to be initialized are listed in
     * the RUNTIME_DEFAULT_DIRECTIVES properties
     * file.
     */
    private void initializeDirectives()
    {
        Properties directiveProperties = new Properties();

        /*
         * Grab the properties file with the list of directives
         * that we should initialize.
         */

        InputStream inputStream = null;

        try
        {
            inputStream = getClass().getResourceAsStream('/' + DEFAULT_RUNTIME_DIRECTIVES);

            if (inputStream == null)
            {
                throw new VelocityException("Error loading directive.properties! " +
                                    "Something is very wrong if these properties " +
                                    "aren't being located. Either your Velocity " +
                                    "distribution is incomplete or your Velocity " +
                                    "jar file is corrupted!");
            }

            directiveProperties.load(inputStream);

        }
        catch (IOException ioe)
        {
            String msg = "Error while loading directive properties!";
            log.error(msg, ioe);
            throw new RuntimeException(msg, ioe);
        }
        finally
        {
            try
            {
                if (inputStream != null)
                {
                    inputStream.close();
                }
            }
            catch (IOException ioe)
            {
                String msg = "Cannot close directive properties!";
                log.error(msg, ioe);
                throw new RuntimeException(msg, ioe);
            }
        }


        /*
         * Grab all the values of the properties. These
         * are all class names for example:
         *
         * org.apache.velocity.runtime.directive.Foreach
         */
        Enumeration directiveClasses = directiveProperties.elements();

        while (directiveClasses.hasMoreElements())
        {
            String directiveClass = (String) directiveClasses.nextElement();
            loadDirective(directiveClass);
            log.debug("Loaded System Directive: {}", directiveClass);
        }

        /*
         *  now the user's directives
         */

        String[] userdirective = configuration.getStringArray(CUSTOM_DIRECTIVES);

        for (String anUserdirective : userdirective)
        {
            loadDirective(anUserdirective);
            log.debug("Loaded User Directive: {}", anUserdirective);
        }

    }

    /**
     * Programatically add a directive.
     * @param directive
     */
    public synchronized void addDirective(Directive directive)
    {
        runtimeDirectives.put(directive.getName(), directive);
        updateSharedDirectivesMap();
    }

    /**
     * Retrieve a previously instantiated directive.
     * @param name name of the directive
     * @return the {@link Directive} for that name
     */
    @Override
    public Directive getDirective(String name)
    {
        return runtimeDirectivesShared.get(name);
    }

    /**
     * Remove a directive.
     * @param name name of the directive.
     */
    public synchronized void removeDirective(String name)
    {
        runtimeDirectives.remove(name);
        updateSharedDirectivesMap();
    }

    /**
     * Makes an unsynchronized copy of the directives map
     * that is used for Directive lookups by all parsers.
     *
     * This follows Copy-on-Write pattern. The cost of creating
     * a new map is acceptable since directives are typically
     * set and modified only during Velocity setup phase.
     */
    private void updateSharedDirectivesMap()
    {
        runtimeDirectivesShared = new HashMap<>(runtimeDirectives);
    }

    /**
     *  instantiates and loads the directive with some basic checks
     *
     *  @param directiveClass classname of directive to load
     */
    public void loadDirective(String directiveClass)
    {
        try
        {
            Object o = ClassUtils.getNewInstance( directiveClass );

            if (o instanceof Directive)
            {
                Directive directive = (Directive) o;
                addDirective(directive);
            }
            else
            {
                String msg = directiveClass + " does not implement "
                    + Directive.class.getName() + "; it cannot be loaded.";
                log.error(msg);
                throw new VelocityException(msg);
            }
        }
        // The ugly threesome:  ClassNotFoundException,
        // IllegalAccessException, InstantiationException.
        // Ignore Findbugs complaint for now.
        catch (Exception e)
        {
            String msg = "Failed to load Directive: " + directiveClass;
            log.error(msg, e);
            throw new VelocityException(msg, e);
        }
    }


    /**
     * Initializes the Velocity parser pool.
     */
    private void initializeParserPool()
    {
        /*
         * First initialize parser class. If it's not valid or not found, it will generate an error
         * later on in this method when parser creation is tester.
         */
        String parserClassName = getString(PARSER_CLASS, DEFAULT_PARSER_CLASS);
        Class<? extends Parser> parserClass;
        try
        {
            parserClass = (Class<? extends Parser>)ClassUtils.getClass(parserClassName);
        }
        catch (ClassNotFoundException cnfe)
        {
            throw new VelocityException("parser class not found: " + parserClassName, cnfe);
        }
        try
        {
            parserConstructor = parserClass.getConstructor(RuntimeServices.class);
        }
        catch (NoSuchMethodException nsme)
        {
            throw new VelocityException("parser class must provide a constructor taking a RuntimeServices argument", nsme);
        }

        /*
         * Which parser pool?
         */
        String pp = getString(RuntimeConstants.PARSER_POOL_CLASS);

        if (pp != null && pp.length() > 0)
        {
            /*
             *  if something was specified, then make one.
             *  if that isn't a ParserPool, consider
             *  this a huge error and throw
             */

            Object o = null;

            try
            {
                o = ClassUtils.getNewInstance( pp );
            }
            catch (ClassNotFoundException cnfe )
            {
                String err = "The specified class for ParserPool ("
                    + pp
                    + ") does not exist (or is not accessible to the current classloader.";
                log.error(err);
                throw new VelocityException(err, cnfe);
            }
            catch (InstantiationException ie)
            {
              throw new VelocityException("Could not instantiate class '" + pp + "'", ie);
            }
            catch (IllegalAccessException ae)
            {
              throw new VelocityException("Cannot access class '" + pp + "'", ae);
            }

            if (!(o instanceof ParserPool))
            {
                String err = "The specified class for ParserPool ("
                    + pp + ") does not implement " + ParserPool.class
                    + " Velocity not initialized correctly.";

                log.error(err);
                throw new VelocityException(err);
            }

            parserPool = (ParserPool) o;

            parserPool.initialize(this);

            /*
             * test parser creation and use generated parser to fill up customized characters
             */
            Parser parser = parserPool.get();
            parserConfiguration = new ParserConfiguration();
            parserConfiguration.setDollarChar(parser.dollar());
            parserConfiguration.setHashChar(parser.hash());
            parserConfiguration.setAtChar(parser.at());
            parserConfiguration.setAsteriskChar(parser.asterisk());
            parserPool.put(parser);
        }
        else
        {
            /*
             *  someone screwed up.  Lets not fool around...
             */

            String err = "It appears that no class was specified as the"
                + " ParserPool.  Please ensure that all configuration"
                + " information is correct.";

            log.error(err);
            throw new VelocityException( err );
        }

    }

    /**
     * Returns a JavaCC generated Parser.
     *
     * @return Parser javacc generated parser
     */
    @Override
    public Parser createNewParser()
    {
        requireInitialization();
        try
        {
            return parserConstructor.newInstance(this);
        }
        catch (IllegalAccessException | InstantiationException | InvocationTargetException e)
        {
            throw new VelocityException("could not build new parser class", e);
        }
    }

    /**
     * Parse the input and return the root of
     * AST node structure.
     * <br><br>
     *  In the event that it runs out of parsers in the
     *  pool, it will create and let them be GC'd
     *  dynamically, logging that it has to do that.  This
     *  is considered an exceptional condition.  It is
     *  expected that the user will set the
     *  PARSER_POOL_SIZE property appropriately for their
     *  application.  We will revisit this.
     *
     * @param reader Reader retrieved by a resource loader
     * @param template template being parsed
     * @return A root node representing the template as an AST tree.
     * @throws ParseException When the template could not be parsed.
     */
    @Override
    public SimpleNode parse(Reader reader, Template template)
        throws ParseException
    {
        requireInitialization();

        Parser parser = parserPool.get();
        boolean keepParser = true;
        if (parser == null)
        {
            /*
             *  if we couldn't get a parser from the pool make one and log it.
             */
            log.info("Runtime: ran out of parsers. Creating a new one. "
                     + " Please increment the parser.pool.size property."
                     + " The current value is too small.");
            parser = createNewParser();
            keepParser = false;
        }

        try
        {
            return parser.parse(reader, template);
        }
        finally
        {
            if (keepParser)
            {
                /* drop the parser Template reference to allow garbage collection */
                parser.resetCurrentTemplate();
                parserPool.put(parser);
            }

        }
    }

    private void initializeScopeSettings()
    {
        ExtProperties scopes = configuration.subset(CONTEXT_SCOPE_CONTROL);
        if (scopes != null)
        {
            Iterator<String> scopeIterator = scopes.getKeys();
            while (scopeIterator.hasNext())
            {
                String scope = scopeIterator.next();
                boolean enabled = scopes.getBoolean(scope);
                if (enabled) enabledScopeControls.add(scope);
            }
        }
    }

    /**
     * Renders the input string using the context into the output writer.
     * To be used when a template is dynamically constructed, or want to use
     * Velocity as a token replacer.
     * <br>
     * Note! Macros defined in evaluate() calls are not persisted in memory so next evaluate() call
     * does not know about macros defined during previous calls.
     *
     * @param context context to use in rendering input string
     * @param out  Writer in which to render the output
     * @param logTag  string to be used as the template name for log
     *                messages in case of error
     * @param instring input string containing the VTL to be rendered
     *
     * @return true if successful, false otherwise.  If false, see
     *              Velocity runtime log
     * @throws ParseErrorException The template could not be parsed.
     * @throws MethodInvocationException A method on a context object could not be invoked.
     * @throws ResourceNotFoundException A referenced resource could not be loaded.
     * @since Velocity 1.6
     */
    @Override
    public boolean evaluate(Context context, Writer out,
                            String logTag, String instring)
    {
        return evaluate(context, out, logTag, new StringReader(instring));
    }

    /**
     * Renders the input reader using the context into the output writer.
     * To be used when a template is dynamically constructed, or want to
     * use Velocity as a token replacer.
     * <br>
     * Note! Macros defined in evaluate() calls are not persisted in memory so next evaluate() call
     * does not know about macros defined during previous calls.
     *
     * @param context context to use in rendering input string
     * @param writer  Writer in which to render the output
     * @param logTag  string to be used as the template name for log messages
     *                in case of error
     * @param reader Reader containing the VTL to be rendered
     *
     * @return true if successful, false otherwise.  If false, see
     *              Velocity runtime log
     * @throws ParseErrorException The template could not be parsed.
     * @throws MethodInvocationException A method on a context object could not be invoked.
     * @throws ResourceNotFoundException A referenced resource could not be loaded.
     * @since Velocity 1.6
     */
    @Override
    public boolean evaluate(Context context, Writer writer,
                            String logTag, Reader reader)
    {
        if (logTag == null)
        {
            throw new NullPointerException("logTag (i.e. template name) cannot be null, you must provide an identifier for the content being evaluated");
        }

        SimpleNode nodeTree = null;
        Template t = new Template();
        t.setName(logTag);
        try
        {
            nodeTree = parse(reader, t);
        }
        catch (ParseException pex)
        {
            throw new ParseErrorException(pex, null);
        }
        catch (TemplateInitException pex)
        {
            throw new ParseErrorException(pex, null);
        }

        if (nodeTree == null)
        {
            return false;
        }
        else
        {
            return render(context, writer, logTag, nodeTree);
        }
    }


    /**
     * <p>Initializes and renders the AST {@link SimpleNode} using the context
     * into the output writer.</p>
     * <p>Note that the <code>nodeTree</code> argument should be a fresh AST tree obtained by the
     * {@link #parse(Reader, Template)} method.</p>
     * <p>If you wish to reuse AST trees but bypass the resource loading mechanism, you should rely on the Template API,
     * as follow:</p>
     * <pre><code>
     *      RuntimeInstance runtimeInstance = new RuntimeInstance();
     *      Template template = new Template();
     *      template.setName("my_template");
     *      template.setRuntimeServices(runtimeInstance);
     *      SimpleNode astTree = runtimeInstance.parse(new StringReader("Some template..."), template);
     *      template.setData(astTree);
     *      template.initDocument();
     * </code></pre>
     * <p>You will then be able to render several times the template with <code>template.render(context, writer)</code>.</p>
     *
     * @param context context to use in rendering input string
     * @param writer  Writer in which to render the output
     * @param logTag  string to be used as the template name for log messages
     *                in case of error
     * @param nodeTree SimpleNode which is the root of the AST to be rendered
     *
     * @return true if successful, false otherwise.  If false, see
     *              Velocity runtime log for errors
     * @throws ParseErrorException The template could not be parsed.
     * @throws MethodInvocationException A method on a context object could not be invoked.
     * @throws ResourceNotFoundException A referenced resource could not be loaded.
     * @since Velocity 1.6
     */
    public boolean render(Context context, Writer writer,
                          String logTag, SimpleNode nodeTree)
    {
        /*
         * we want to init then render
         */
        InternalContextAdapterImpl ica =
            new InternalContextAdapterImpl(context);

        ica.pushCurrentTemplateName(logTag);

        try
        {
            ica.setCurrentResource(nodeTree.getTemplate());
            try
            {
                nodeTree.init(ica, this);
            }
            catch (TemplateInitException pex)
            {
                throw new ParseErrorException(pex, null);
            }
            /*
             * pass through application level runtime exceptions
             */
            catch(RuntimeException e)
            {
                throw e;
            }
            catch(Exception e)
            {
                String msg = "RuntimeInstance.render(): init exception for tag '"+logTag + "'";
                log.error(msg, e);
                throw new VelocityException(msg, e, getLogContext().getStackTrace());
            }

            try
            {
                if (isScopeControlEnabled(evaluateScopeName))
                {
                    Object previous = ica.get(evaluateScopeName);
                    context.put(evaluateScopeName, new Scope(this, previous));
                }
                /*
                 * optionally put the context in itself if asked so
                 */
                String self = getString(CONTEXT_AUTOREFERENCE_KEY);
                if (self != null) context.put(self, context);
                nodeTree.render(ica, writer);
            }
            catch (StopCommand stop)
            {
                if (!stop.isFor(this))
                {
                    throw stop;
                }
                else
                {
                    log.debug(stop.getMessage());
                }
            }
            catch (IOException e)
            {
                throw new VelocityException("IO Error in writer: " + e.getMessage(), e, getLogContext().getStackTrace());
            }
        }
        finally
        {
            ica.popCurrentTemplateName();
            ica.setCurrentResource(null);
            if (isScopeControlEnabled(evaluateScopeName))
            {
                Object obj = ica.get(evaluateScopeName);
                if (obj instanceof Scope)
                {
                    Scope scope = (Scope)obj;
                    if (scope.getParent() != null)
                    {
                        ica.put(evaluateScopeName, scope.getParent());
                    }
                    else if (scope.getReplaced() != null)
                    {
                        ica.put(evaluateScopeName, scope.getReplaced());
                    }
                    else
                    {
                        ica.remove(evaluateScopeName);
                    }
                }
            }
        }

        return true;
    }

    /**
     * Invokes a currently registered Velocimacro with the params provided
     * and places the rendered stream into the writer.
     * <br>
     * Note: currently only accepts args to the VM if they are in the context.
     * <br>
     * Note: only macros in the global context can be called. This method doesn't find macros defined by
     * templates during previous mergeTemplate calls if Velocity.VM_PERM_INLINE_LOCAL has been enabled.
     *
     * @param vmName name of Velocimacro to call
     * @param logTag string to be used for template name in case of error. if null,
     *               the vmName will be used
     * @param params keys for args used to invoke Velocimacro, in java format
     *               rather than VTL (eg  "foo" or "bar" rather than "$foo" or "$bar")
     * @param context Context object containing data/objects used for rendering.
     * @param writer  Writer for output stream
     * @return true if Velocimacro exists and successfully invoked, false otherwise.
     * @since 1.6
     */
    @Override
    public boolean invokeVelocimacro(final String vmName, String logTag,
                                     String[] params, final Context context,
                                     final Writer writer)
     {
        /* check necessary parameters */
        if (vmName == null || context == null || writer == null)
        {
            String msg = "RuntimeInstance.invokeVelocimacro(): invalid call: vmName, context, and writer must not be null";
            log.error(msg);
            throw new NullPointerException(msg);
        }

        /* handle easily corrected parameters */
        if (logTag == null)
        {
            logTag = vmName;
        }
        if (params == null)
        {
            params = new String[0];
        }

        /* does the VM exist? (only global scope is scanned so this doesn't find inline macros in templates) */
        if (!isVelocimacro(vmName, null))
        {
            String msg = "RuntimeInstance.invokeVelocimacro(): VM '" + vmName
                         + "' is not registered.";
            log.error(msg);
            throw new VelocityException(msg, null, getLogContext().getStackTrace());
        }

        /* now just create the VM call, and use evaluate */
        StringBuilder template = new StringBuilder(String.valueOf(parserConfiguration.getHashChar()));
        template.append(vmName);
        template.append("(");
         for (String param : params)
         {
             template.append(" $");
             template.append(param);
         }
        template.append(" )");

        return evaluate(context, writer, logTag, template.toString());
    }

    /**
     * Retrieves and caches the configured default encoding
     * for better performance. (VELOCITY-606)
     */
    private String getDefaultEncoding()
    {
        return defaultEncoding;
    }

    /**
     * Returns a <code>Template</code> from the resource manager.
     * This method assumes that the character encoding of the
     * template is set by the <code>resource.default_encoding</code>
     * property. The default is UTF-8.
     *
     * @param name The file name of the desired template.
     * @return     The template.
     * @throws ResourceNotFoundException if template not found
     *          from any available source.
     * @throws ParseErrorException if template cannot be parsed due
     *          to syntax (or other) error.
     */
    @Override
    public Template getTemplate(String name)
        throws ResourceNotFoundException, ParseErrorException
    {
        return getTemplate(name, null);
    }

    /**
     * Returns a <code>Template</code> from the resource manager
     *
     * @param name The  name of the desired template.
     * @param encoding Character encoding of the template
     * @return     The template.
     * @throws ResourceNotFoundException if template not found
     *          from any available source.
     * @throws ParseErrorException if template cannot be parsed due
     *          to syntax (or other) error.
     */
    @Override
    public Template getTemplate(String name, String  encoding)
        throws ResourceNotFoundException, ParseErrorException
    {
        requireInitialization();
        if (encoding == null) encoding = getDefaultEncoding();
        return (Template)
                resourceManager.getResource(name,
                    ResourceManager.RESOURCE_TEMPLATE, encoding);
    }

    /**
     * Returns a static content resource from the
     * resource manager.  Uses the current value
     * if INPUT_ENCODING as the character encoding.
     *
     * @param name Name of content resource to get
     * @return parsed ContentResource object ready for use
     * @throws ResourceNotFoundException if template not found
     *          from any available source.
     * @throws ParseErrorException When the template could not be parsed.
     */
    @Override
    public ContentResource getContent(String name)
        throws ResourceNotFoundException, ParseErrorException
    {
        /*
         *  the encoding is irrelvant as we don't do any converstion
         *  the bytestream should be dumped to the output stream
         */

        return getContent(name, getDefaultEncoding());
    }

    /**
     * Returns a static content resource from the
     * resource manager.
     *
     * @param name Name of content resource to get
     * @param encoding Character encoding to use
     * @return parsed ContentResource object ready for use
     * @throws ResourceNotFoundException if template not found
     *          from any available source.
     * @throws ParseErrorException When the template could not be parsed.
     */
    @Override
    public ContentResource getContent(String name, String encoding)
        throws ResourceNotFoundException, ParseErrorException
    {
        requireInitialization();

        return (ContentResource)
                resourceManager.getResource(name,
                        ResourceManager.RESOURCE_CONTENT, encoding);
    }


    /**
     *  Determines if a template exists and returns name of the loader that
     *  provides it.  This is a slightly less hokey way to support
     *  the Velocity.resourceExists() utility method, which was broken
     *  when per-template encoding was introduced.  We can revisit this.
     *
     *  @param resourceName Name of template or content resource
     *  @return class name of loader than can provide it
     */
    @Override
    public String getLoaderNameForResource(String resourceName)
    {
        requireInitialization();

        return resourceManager.getLoaderNameForResource(resourceName);
    }

    /**
     * Returns the configured logger.
     *
     * @return A Logger instance
     * @since 1.5
     */
    @Override
    public Logger getLog()
    {
        return log;
    }

    /**
     * Get a logger for the specified child namespace.
     * If a logger was configured using the runtime.log.instance configuration property, returns this instance.
     * Otherwise, uses SLF4J LoggerFactory on baseNamespace '.' childNamespace.
     * @param childNamespace
     * @return child namespace logger
     */
    @Override
    public Logger getLog(String childNamespace)
    {
        Logger log = (Logger)getProperty(RuntimeConstants.RUNTIME_LOG_INSTANCE);
        if (log == null)
        {
            String loggerName = getString(RUNTIME_LOG_NAME, DEFAULT_RUNTIME_LOG_NAME) + "." + childNamespace;
            log = LoggerFactory.getLogger(loggerName);
        }
        return log;
    }

    /**
     * Get the LogContext object used to tack locations in templates.
     * @return LogContext object
     * @since 2.2
     */
    @Override
    public LogContext getLogContext()
    {
        return logContext;
    }

    /**
     * String property accessor method with default to hide the
     * configuration implementation.
     *
     * @param key property key
     * @param defaultValue  default value to return if key not
     *               found in resource manager.
     * @return value of key or default
     */
    @Override
    public String getString(String key, String defaultValue)
    {
        return configuration.getString(key, defaultValue);
    }

    /**
     * Returns the appropriate VelocimacroProxy object if vmName
     * is a valid current Velocimacro.
     *
     * @param vmName  Name of velocimacro requested
     * @param renderingTemplate Template we are currently rendering. This
     *    information is needed when VM_PERM_ALLOW_INLINE_REPLACE_GLOBAL setting is true
     *    and template contains a macro with the same name as the global macro library.
     * @param template Template which acts as the host for the macro
     *
     * @return VelocimacroProxy
     */
    @Override
    public Directive getVelocimacro(String vmName, Template renderingTemplate, Template template)
    {
        return vmFactory.getVelocimacro(vmName, renderingTemplate, template);
    }

    /**
     * Adds a new Velocimacro. Usually called by Macro only while parsing.
     *
     * @param name  Name of velocimacro
     * @param macro  root AST node of the parsed macro
     * @param macroArgs  Array of macro arguments, containing the
     *        #macro() arguments and default values.  the 0th is the name.
     * @param definingTemplate Template containing the source of the macro
     *
     * @return boolean  True if added, false if rejected for some
     *                  reason (either parameters or permission settings)
     */
    @Override
    public boolean addVelocimacro(String name,
                                  Node macro,
                                  List<Macro.MacroArg> macroArgs,
                                  Template definingTemplate)
    {
        return vmFactory.addVelocimacro(stringInterning ? name.intern() : name, macro, macroArgs, definingTemplate);
    }

    /**
     *  Checks to see if a VM exists
     *
     * @param vmName Name of the Velocimacro.
     * @param template Template on which to look for the Macro.
     * @return True if VM by that name exists, false if not
     */
    @Override
    public boolean isVelocimacro(String vmName, Template template)
    {
        return vmFactory.isVelocimacro(stringInterning ? vmName.intern() : vmName, template);
    }

    /* --------------------------------------------------------------------
     * R U N T I M E  A C C E S S O R  M E T H O D S
     * --------------------------------------------------------------------
     * These are the getXXX() methods that are a simple wrapper
     * around the configuration object. This is an attempt
     * to make a the Velocity Runtime the single access point
     * for all things Velocity, and allow the Runtime to
     * adhere as closely as possible the the Mediator pattern
     * which is the ultimate goal.
     * --------------------------------------------------------------------
     */

    /**
     * String property accessor method to hide the configuration implementation
     * @param key  property key
     * @return   value of key or null
     */
    @Override
    public String getString(String key)
    {
        return StringUtils.trim(configuration.getString(key));
    }

    /**
     * Int property accessor method to hide the configuration implementation.
     *
     * @param key Property key
     * @return value
     */
    @Override
    public int getInt(String key)
    {
        return configuration.getInt(key);
    }

    /**
     * Int property accessor method to hide the configuration implementation.
     *
     * @param key  property key
     * @param defaultValue The default value.
     * @return value
     */
    @Override
    public int getInt(String key, int defaultValue)
    {
        return configuration.getInt(key, defaultValue);
    }

    /**
     * Boolean property accessor method to hide the configuration implementation.
     *
     * @param key property key
     * @param def The default value if property not found.
     * @return value of key or default value
     */
    @Override
    public boolean getBoolean(String key, boolean def)
    {
        return configuration.getBoolean(key, def);
    }

    /**
     * Return the velocity runtime configuration object.
     *
     * @return Configuration object which houses the Velocity runtime
     * properties.
     */
    @Override
    public ExtProperties getConfiguration()
    {
        return configuration;
    }

    /**
     * Returns the event handlers for the application.
     * @return The event handlers for the application.
     * @since 1.5
     */
    @Override
    public EventCartridge getApplicationEventCartridge()
    {
        return eventCartridge;
    }


    /**
     *  Gets the application attribute for the given key
     *
     * @param key
     * @return The application attribute for the given key.
     */
    @Override
    public Object getApplicationAttribute(Object key)
    {
        return applicationAttributes.get(key);
    }

    /**
     *   Sets the application attribute for the given key
     *
     * @param key
     * @param o The new application attribute.
     * @return The old value of this attribute or null if it hasn't been set before.
     */
    @Override
    public Object setApplicationAttribute(Object key, Object o)
    {
        return applicationAttributes.put(key, o);
    }

    /**
     * Returns the Uberspect object for this Instance.
     *
     * @return The Uberspect object for this Instance.
     */
    @Override
    public Uberspect getUberspect()
    {
        return uberSpect;
    }

    /**
     * Whether to use string interning
     *
     * @return boolean
     */
    @Override
    public boolean useStringInterning()
    {
        return stringInterning;
    }

    /**
     * get space gobbling mode
     * @return indentation mode
     */
    @Override
    public SpaceGobbling getSpaceGobbling()
    {
        return spaceGobbling;
    }

    /**
     * get whether hyphens are allowed in identifiers
     * @return configured boolean flag
     */
    @Override
    public boolean isHyphenAllowedInIdentifiers()
    {
        return hyphenAllowedInIdentifiers;
    }

    /**
     * Get whether to provide a scope control object for this scope
     * @param scopeName
     * @return scope control enabled
     * @since 2.1
     */
    @Override
    public boolean isScopeControlEnabled(String scopeName)
    {
        return enabledScopeControls.contains(scopeName);
    }

    @Override
    public ParserConfiguration getParserConfiguration()
    {
        return parserConfiguration;
    }
}
