docs: @Override
      public void end(String namespace, String name) throws Exception {
  
          // Determine the target object for the method call: the parent object
          Object parent = getDigester().peek(1);
          Object child = getDigester().peek(0);
  
          // Retrieve or construct the parameter values array
          Object[] parameters = null;
          if (m_paramCount > 0) {
              parameters = getDigester().popParams();
              if (LOG.isTraceEnabled()) {
                  for (int i = 0, size = parameters.length; i < size; i++) {
                      LOG.trace("[SetNextRuleWithParams](" + i + ")" + parameters[i]);
                  }
              }
  
              // In the case where the target method takes a single parameter
              // and that parameter does not exist (the CallParamRule never
              // executed or the CallParamRule was intended to set the parameter
              // from an attribute but the attribute wasn't present etc) then
              // skip the method call.
              //
              // This is useful when a class has a "default" value that should
              // only be overridden if data is present in the XML. I don't
              // know why this should only apply to methods taking *one*
              // parameter, but it always has been so we can't change it now.
              if ((m_paramCount == 1) && (parameters[0] == null)) {
                  return;
              }
  
          } else if (m_paramTypes.length != 0) {
              // Having paramCount == 0 and paramTypes.length == 1 indicates
              // that we have the special case where the target method has one
              // parameter being the body text of the current element.
  
              // There is no body text included in the source XML file,
              // so skip the method call
              if (m_bodyText == null) {
                  return;
              }
  
              parameters = new Object[1];
              parameters[0] = m_bodyText;
              if (m_paramTypes.length == 0) {
                  m_paramTypes = new Class[1];
                  m_paramTypes[0] = String.class;
              }
  
          } else {
              // When paramCount is zero and paramTypes.length is zero it
              // means that we truly are calling a method with no parameters.
              // Nothing special needs to be done here.
              parameters = new Object[0];
          }
  
          // Construct the parameter values array we will need
          // We only do the conversion if the param value is a String and
          // the specified paramType is not String.
          Object[] paramValues = new Object[m_paramTypes.length];
  
          Class<?> propertyClass = child.getClass();
          for (int i = 0; i < m_paramTypes.length; i++) {
              if (m_paramTypes[i] == propertyClass) {
                  // implant the original child to set if Class matches:
                  paramValues[i] = child;
              } else if ((parameters[i] == null)
                  || ((parameters[i] instanceof String) && !String.class.isAssignableFrom(m_paramTypes[i]))) {
                  // convert nulls and convert stringy parameters
                  // for non-stringy param types
                  if (parameters[i] == null) {
                      paramValues[i] = null;
                  } else {
                      paramValues[i] = ConvertUtils.convert((String)parameters[i], m_paramTypes[i]);
                  }
  
              } else {
                  paramValues[i] = parameters[i];
              }
          }
  
          if (parent == null) {
              StringBuffer sb = new StringBuffer();
              sb.append("[SetNextRuleWithParams]{");
              sb.append(getDigester().getMatch());
              sb.append("} Call target is null (");
              sb.append("targetOffset=");
              sb.append(m_targetOffset);
              sb.append(",stackdepth=");
              sb.append(getDigester().getCount());
              sb.append(")");
              throw new org.xml.sax.SAXException(sb.toString());
          }
  
          // Invoke the required method on the top object
          if (LOG.isDebugEnabled()) {
              StringBuffer sb = new StringBuffer("[SetNextRuleWithParams]{");
              sb.append(getDigester().getMatch());
              sb.append("} Call ");
              sb.append(parent.getClass().getName());
              sb.append(".");
              sb.append(m_methodName);
              sb.append("(");
              for (int i = 0; i < paramValues.length; i++) {
                  if (i > 0) {
                      sb.append(",");
                  }
                  if (paramValues[i] == null) {
                      sb.append("null");
                  } else {
                      sb.append(paramValues[i].toString());
                  }
                  sb.append("/");
                  if (m_paramTypes[i] == null) {
                      sb.append("null");
                  } else {
                      sb.append(m_paramTypes[i].getName());
                  }
              }
              sb.append(")");
              LOG.debug(sb.toString());
          }
  
          Object result = null;
          if (m_useExactMatch) {
              // invoke using exact match
              result = MethodUtils.invokeExactMethod(parent, m_methodName, paramValues, m_paramTypes);
  
          } else {
              // invoke using fuzzier match
              result = MethodUtils.invokeMethod(parent, m_methodName, paramValues, m_paramTypes);
          }
  
          processMethodCallResult(result);
      }