/**
   * 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;
  }
  /**
   * This method helps to implement the "render literal if null" functionality.
   *
   * <p>VelocimacroProxy saves references to macro arguments (AST nodes) so that if we have a macro
   * #foobar($a $b) then there is key "$a.literal" which points to the literal presentation of the
   * argument provided to variable $a. If the value of $a is null, we render the string that was
   * provided as the argument.
   *
   * @param context
   * @return
   */
  private String getNullString(InternalContextAdapter context) {
    Object callingArgument = context.get(".literal." + nullString);

    if (callingArgument != null) return ((Node) callingArgument).literal();
    else return nullString;
  }