/**
   * @param context
   * @param variable
   * @return The evaluated value of the variable.
   * @throws MethodInvocationException
   */
  public Object getVariableValue(Context context, String variable)
      throws MethodInvocationException {
    Object obj = null;
    try {
      obj = context.get(variable);
    } catch (RuntimeException e) {
      log.error(
          "Exception calling reference $" + variable + " at " + Log.formatFileString(uberInfo));
      throw e;
    }

    if (strictRef && obj == null) {
      if (!context.containsKey(variable)) {
        log.error(
            "Variable $" + variable + " has not been set at " + Log.formatFileString(uberInfo));
        throw new MethodInvocationException(
            "Variable $" + variable + " has not been set",
            null,
            identifier,
            uberInfo.getTemplateName(),
            uberInfo.getLine(),
            uberInfo.getColumn());
      }
    }
    return obj;
  }
  /**
   * Computes boolean value of this reference Returns the actual value of reference return type
   * boolean, and 'true' if value is not null
   *
   * @param context context to compute value with
   * @return True if evaluation was ok.
   * @throws MethodInvocationException
   */
  public boolean evaluate(InternalContextAdapter context) throws MethodInvocationException {
    Object value = execute(null, context);

    if (value == null) {
      return false;
    } else if (value instanceof Boolean) {
      if (((Boolean) value).booleanValue()) return true;
      else return false;
    } else if (toStringNullCheck) {
      try {
        return value.toString() != null;
      } catch (Exception e) {
        throw new VelocityException(
            "Reference evaluation threw an exception at " + Log.formatFileString(this), e);
      }
    } else {
      return true;
    }
  }
  /**
   * Sets the value of a complex reference (something like $foo.bar) Currently used by
   * ASTSetReference()
   *
   * @see ASTSetDirective
   * @param context context object containing this reference
   * @param value Object to set as value
   * @return true if successful, false otherwise
   * @throws MethodInvocationException
   */
  public boolean setValue(InternalContextAdapter context, Object value)
      throws MethodInvocationException {
    if (jjtGetNumChildren() == 0) {
      context.put(rootString, value);
      return true;
    }

    /*
     *  The rootOfIntrospection is the object we will
     *  retrieve from the Context. This is the base
     *  object we will apply reflection to.
     */

    Object result = getVariableValue(context, rootString);

    if (result == null) {
      String msg = "reference set is not a valid reference at " + Log.formatFileString(uberInfo);
      log.error(msg);
      return false;
    }

    /*
     * How many child nodes do we have?
     */

    for (int i = 0; i < numChildren - 1; i++) {
      result = jjtGetChild(i).execute(result, context);

      if (result == null) {
        if (strictRef) {
          String name = jjtGetChild(i + 1).getFirstToken().image;
          throw new MethodInvocationException(
              "Attempted to access '" + name + "' on a null value",
              null,
              name,
              uberInfo.getTemplateName(),
              jjtGetChild(i + 1).getLine(),
              jjtGetChild(i + 1).getColumn());
        }

        String msg = "reference set is not a valid reference at " + Log.formatFileString(uberInfo);
        log.error(msg);

        return false;
      }
    }

    if (astIndex != null) {
      // If astIndex is not null then we are actually setting an index reference,
      // something of the form $foo[1] =, or in general any reference that ends with
      // the brackets.  This means that we need to call a more general method
      // of the form set(Integer, <something>), or put(Object, <something), where
      // the first parameter is the index value and the second is the LHS of the set.

      Object argument = astIndex.jjtGetChild(0).value(context);
      // If negative, turn -1 into (size - 1)
      argument = ASTIndex.adjMinusIndexArg(argument, result, context, astIndex);
      Object[] params = {argument, value};
      Class[] paramClasses = {
        params[0] == null ? null : params[0].getClass(),
        params[1] == null ? null : params[1].getClass()
      };

      String methodName = "set";
      VelMethod method =
          ClassUtils.getMethod(methodName, params, paramClasses, result, context, astIndex, false);

      if (method == null) {
        // If we can't find a 'set' method, lets try 'put',  This warrents a little
        // investigation performance wise... if the user is using the hash
        // form $foo["blaa"], then it may be expensive to first try and fail on 'set'
        // then go to 'put'?  The problem is that getMethod will try the cache, then
        // perform introspection on 'result' for 'set'
        methodName = "put";
        method =
            ClassUtils.getMethod(
                methodName, params, paramClasses, result, context, astIndex, false);
      }

      if (method == null) {
        // couldn't find set or put method, so bail
        if (strictRef) {
          throw new VelocityException(
              "Found neither a 'set' or 'put' method with param types '("
                  + printClass(paramClasses[0])
                  + ","
                  + printClass(paramClasses[1])
                  + ")' on class '"
                  + result.getClass().getName()
                  + "' at "
                  + Log.formatFileString(astIndex));
        }
        return false;
      }

      try {
        method.invoke(result, params);
      } catch (RuntimeException e) {
        // Kludge since invoke throws Exception, pass up Runtimes
        throw e;
      } catch (Exception e) {
        throw new MethodInvocationException(
            "Exception calling method '"
                + methodName
                + "("
                + printClass(paramClasses[0])
                + ","
                + printClass(paramClasses[1])
                + ")' in  "
                + result.getClass(),
            e.getCause(),
            identifier,
            astIndex.getTemplateName(),
            astIndex.getLine(),
            astIndex.getColumn());
      }

      return true;
    }

    /*
     *  We support two ways of setting the value in a #set($ref.foo = $value ) :
     *  1) ref.setFoo( value )
     *  2) ref,put("foo", value ) to parallel the get() map introspection
     */

    try {
      VelPropertySet vs = rsvc.getUberspect().getPropertySet(result, identifier, value, uberInfo);

      if (vs == null) {
        if (strictRef) {
          throw new MethodInvocationException(
              "Object '"
                  + result.getClass().getName()
                  + "' does not contain property '"
                  + identifier
                  + "'",
              null,
              identifier,
              uberInfo.getTemplateName(),
              uberInfo.getLine(),
              uberInfo.getColumn());
        } else {
          return false;
        }
      }

      vs.invoke(result, value);
    } catch (InvocationTargetException ite) {
      /*
       *  this is possible
       */

      throw new MethodInvocationException(
          "ASTReference : Invocation of method '"
              + identifier
              + "' in  "
              + result.getClass()
              + " threw exception "
              + ite.getTargetException().toString(),
          ite.getTargetException(),
          identifier,
          getTemplateName(),
          this.getLine(),
          this.getColumn());
    }
    /** pass through application level runtime exceptions */
    catch (RuntimeException e) {
      throw e;
    } catch (Exception e) {
      /*
       *  maybe a security exception?
       */
      String msg =
          "ASTReference setValue() : exception : "
              + e
              + " template at "
              + Log.formatFileString(uberInfo);
      log.error(msg, e);
      throw new VelocityException(msg, e);
    }

    return true;
  }
  /**
   * gets the value of the reference and outputs it to the writer.
   *
   * @param context context of data to use in getting value
   * @param writer writer to render to
   * @return True if rendering was successful.
   * @throws IOException
   * @throws MethodInvocationException
   */
  public boolean render(InternalContextAdapter context, Writer writer)
      throws IOException, MethodInvocationException {
    if (referenceType == RUNT) {
      writer.write(rootString);
      return true;
    }

    Object value = null;
    if (escaped && strictEscape) {
      /**
       * If we are in strict mode and the variable is escaped, then don't bother to retreive the
       * value since we won't use it. And if the var is not defined it will throw an exception. Set
       * value to TRUE to fall through below with simply printing $foo, and not \$foo
       */
      value = Boolean.TRUE;
    } else {
      value = execute(null, context);
    }

    String localNullString = null;

    /*
     * if this reference is escaped (\$foo) then we want to do one of two things : 1) if this is
     * a reference in the context, then we want to print $foo 2) if not, then \$foo (its
     * considered schmoo, not VTL)
     */

    if (escaped) {
      localNullString = getNullString(context);

      if (value == null) {
        writer.write(escPrefix);
        writer.write("\\");
        writer.write(localNullString);
      } else {
        writer.write(escPrefix);
        writer.write(localNullString);
      }
      return true;
    }

    /*
     * the normal processing
     *
     * if we have an event cartridge, get a new value object
     */

    //        value = EventHandlerUtil.referenceInsert(rsvc, context, literal, value);

    String toString = null;
    if (value != null) {
      if (value instanceof Renderable) {
        Renderable renderable = (Renderable) value;
        try {
          if (renderable.render(context, writer)) return true;
        } catch (RuntimeException e) {
          // We commonly get here when an error occurs within a block reference.
          // We want to log where the reference is at so that a developer can easily
          // know where the offending call is located.  This can be seen
          // as another element of the error stack we report to log.
          log.error(
              "Exception rendering "
                  + ((renderable instanceof Reference) ? "block " : "Renderable ")
                  + rootString
                  + " at "
                  + Log.formatFileString(this));
          throw e;
        }
      }

      toString = value.toString();
    }

    if (value == null || toString == null) {
      if (strictRef) {
        if (referenceType != QUIET_REFERENCE) {
          log.error(
              "Prepend the reference with '$!' e.g., $!"
                  + literal().substring(1)
                  + " if you want Velocity to ignore the reference when it evaluates to null");
          if (value == null) {
            throw new VelocityException(
                "Reference "
                    + literal()
                    + " evaluated to null when attempting to render at "
                    + Log.formatFileString(this));
          } else // toString == null
          {
            // This will probably rarely happen, but when it does we want to
            // inform the user that toString == null so they don't pull there
            // hair out wondering why Velocity thinks the value is null.
            throw new VelocityException(
                "Reference "
                    + literal()
                    + " evaluated to object "
                    + value.getClass().getName()
                    + " whose toString() method returned null at "
                    + Log.formatFileString(this));
          }
        }
        return true;
      }

      /*
       * write prefix twice, because it's schmoo, so the \ don't escape each
       * other...
       */
      localNullString = getNullString(context);
      if (!strictEscape) {
        // If in strict escape mode then we only print escape once.
        // Yea, I know.. brittle stuff
        writer.write(escPrefix);
      }
      writer.write(escPrefix);
      writer.write(morePrefix);
      writer.write(localNullString);

      if (logOnNull && referenceType != QUIET_REFERENCE && log.isDebugEnabled()) {
        log.debug(
            "Null reference [template '"
                + getTemplateName()
                + "', line "
                + this.getLine()
                + ", column "
                + this.getColumn()
                + "] : "
                + this.literal()
                + " cannot be resolved.");
      }
      return true;
    } else {
      /*
       * non-null processing
       */
      writer.write(escPrefix);
      writer.write(morePrefix);
      writer.write(toString);

      return true;
    }
  }
  /**
   * gets an Object that 'is' the value of the reference
   *
   * @param o unused Object parameter
   * @param context context used to generate value
   * @return The execution result.
   * @throws MethodInvocationException
   */
  public Object execute(Object o, InternalContextAdapter context) throws MethodInvocationException {

    if (referenceType == RUNT) return null;

    /*
     *  get the root object from the context
     */

    Object result = getVariableValue(context, rootString);

    if (result == null && !strictRef) {
      //            return EventHandlerUtil.invalidGetMethod(rsvc, context,
      //                    getDollarBang() + rootString, null, null, uberInfo);
      return result;
    }

    /*
     * Iteratively work 'down' (it's flat...) the reference
     * to get the value, but check to make sure that
     * every result along the path is valid. For example:
     *
     * $hashtable.Customer.Name
     *
     * The $hashtable may be valid, but there is no key
     * 'Customer' in the hashtable so we want to stop
     * when we find a null value and return the null
     * so the error gets logged.
     */

    try {
      Object previousResult = result;
      int failedChild = -1;
      for (int i = 0; i < numChildren; i++) {
        if (strictRef && result == null) {
          /**
           * At this point we know that an attempt is about to be made to call a method or property
           * on a null value.
           */
          String name = jjtGetChild(i).getFirstToken().image;
          throw new VelocityException(
              "Attempted to access '"
                  + name
                  + "' on a null value at "
                  + Log.formatFileString(
                      uberInfo.getTemplateName(),
                      +jjtGetChild(i).getLine(),
                      jjtGetChild(i).getColumn()));
        }
        previousResult = result;
        result = jjtGetChild(i).execute(result, context);
        if (result == null && !strictRef) // If strict and null then well catch this
        // next time through the loop
        {
          failedChild = i;
          break;
        }
      }

      if (result == null) {
        if (failedChild == -1) {
          //                    result = EventHandlerUtil.invalidGetMethod(rsvc, context,
          //                            getDollarBang() + rootString, previousResult, null,
          // uberInfo);
        } else {
          StringBuffer name = new StringBuffer(getDollarBang()).append(rootString);
          for (int i = 0; i <= failedChild; i++) {
            Node node = jjtGetChild(i);
            if (node instanceof ASTMethod) {
              name.append(".").append(((ASTMethod) node).getMethodName()).append("()");
            } else {
              name.append(".").append(node.getFirstToken().image);
            }
          }

          if (jjtGetChild(failedChild) instanceof ASTMethod) {
            String methodName = ((ASTMethod) jjtGetChild(failedChild)).getMethodName();
            //                        result = EventHandlerUtil.invalidMethod(rsvc, context,
            //                                name.toString(), previousResult, methodName,
            // uberInfo);
          } else {
            String property = jjtGetChild(failedChild).getFirstToken().image;
            //                        result = EventHandlerUtil.invalidGetMethod(rsvc, context,
            //                                name.toString(), previousResult, property, uberInfo);
          }
        }
      }

      return result;
    } catch (MethodInvocationException mie) {
      mie.setReferenceName(rootString);
      throw mie;
    }
  }