package org.apache.commons.digester;

import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.MethodUtils;
import org.xml.sax.Attributes;

public class CallMethodRule extends Rule {
  protected String bodyText;
  
  protected String methodName;
  
  protected int paramCount;
  
  protected Class[] paramTypes;
  
  private String[] paramClassNames;
  
  protected boolean useExactMatch;
  
  public CallMethodRule(Digester digester, String methodName, int paramCount) {
    this(methodName, paramCount);
  }
  
  public CallMethodRule(Digester digester, String methodName, int paramCount, String[] paramTypes) {
    this(methodName, paramCount, paramTypes);
  }
  
  public CallMethodRule(Digester digester, String methodName, int paramCount, Class[] paramTypes) {
    this(methodName, paramCount, paramTypes);
  }
  
  public CallMethodRule(String methodName, int paramCount) {
    this.bodyText = null;
    this.methodName = null;
    this.paramCount = 0;
    this.paramTypes = null;
    this.paramClassNames = null;
    this.useExactMatch = false;
    this.methodName = methodName;
    this.paramCount = paramCount;
    if (paramCount == 0) {
      this.paramTypes = new Class[] { String.class };
    } else {
      this.paramTypes = new Class[paramCount];
      for (int i = 0; i < this.paramTypes.length; i++)
        this.paramTypes[i] = String.class; 
    } 
  }
  
  public CallMethodRule(String methodName) {
    this(methodName, 0, (Class[])null);
  }
  
  public CallMethodRule(String methodName, int paramCount, String[] paramTypes) {
    this.bodyText = null;
    this.methodName = null;
    this.paramCount = 0;
    this.paramTypes = null;
    this.paramClassNames = null;
    this.useExactMatch = false;
    this.methodName = methodName;
    this.paramCount = paramCount;
    if (paramTypes == null) {
      this.paramTypes = new Class[paramCount];
      for (int i = 0; i < this.paramTypes.length; i++)
        this.paramTypes[i] = "abc".getClass(); 
    } else {
      this.paramClassNames = new String[paramTypes.length];
      for (int i = 0; i < this.paramClassNames.length; i++)
        this.paramClassNames[i] = paramTypes[i]; 
    } 
  }
  
  public CallMethodRule(String methodName, int paramCount, Class[] paramTypes) {
    this.bodyText = null;
    this.methodName = null;
    this.paramCount = 0;
    this.paramTypes = null;
    this.paramClassNames = null;
    this.useExactMatch = false;
    this.methodName = methodName;
    this.paramCount = paramCount;
    if (paramTypes == null) {
      this.paramTypes = new Class[paramCount];
      for (int i = 0; i < this.paramTypes.length; i++)
        this.paramTypes[i] = "abc".getClass(); 
    } else {
      this.paramTypes = new Class[paramTypes.length];
      for (int i = 0; i < this.paramTypes.length; i++)
        this.paramTypes[i] = paramTypes[i]; 
    } 
  }
  
  public boolean getUseExactMatch() {
    return this.useExactMatch;
  }
  
  public void setUseExactMatch(boolean useExactMatch) {
    this.useExactMatch = useExactMatch;
  }
  
  public void setDigester(Digester digester) {
    super.setDigester(digester);
    if (this.paramClassNames != null) {
      this.paramTypes = new Class[this.paramClassNames.length];
      for (int i = 0; i < this.paramClassNames.length; i++) {
        try {
          this.paramTypes[i] = digester.getClassLoader().loadClass(this.paramClassNames[i]);
        } catch (ClassNotFoundException e) {
          digester.getLogger().error("(CallMethodRule) Cannot load class " + this.paramClassNames[i], e);
          this.paramTypes[i] = null;
        } 
      } 
    } 
  }
  
  public void begin(Attributes attributes) throws Exception {
    if (this.paramCount > 0) {
      Object[] parameters = new Object[this.paramCount];
      for (int i = 0; i < parameters.length; i++)
        parameters[i] = null; 
      this.digester.pushParams(parameters);
    } 
  }
  
  public void body(String bodyText) throws Exception {
    if (this.paramCount == 0)
      this.bodyText = bodyText.trim(); 
  }
  
  public void end() throws Exception {
    Object[] parameters = null;
    if (this.paramCount > 0) {
      parameters = (Object[])this.digester.popParams();
      if (this.digester.log.isTraceEnabled())
        for (int j = 0, size = parameters.length; j < size; j++)
          this.digester.log.trace("[CallMethodRule](" + j + ")" + parameters[j]);  
      if (this.paramCount == 1 && parameters[0] == null)
        return; 
    } else if (this.paramTypes != null && this.paramTypes.length != 0) {
      if (this.bodyText == null)
        return; 
      parameters = new Object[1];
      parameters[0] = this.bodyText;
      if (this.paramTypes.length == 0) {
        this.paramTypes = new Class[1];
        this.paramTypes[0] = "abc".getClass();
      } 
    } 
    Object[] paramValues = new Object[this.paramTypes.length];
    for (int i = 0; i < this.paramTypes.length; i++) {
      if (parameters[i] == null || (parameters[i] instanceof String && !String.class.isAssignableFrom(this.paramTypes[i]))) {
        paramValues[i] = ConvertUtils.convert((String)parameters[i], this.paramTypes[i]);
      } else {
        paramValues[i] = parameters[i];
      } 
    } 
    Object top = this.digester.peek();
    if (this.digester.log.isDebugEnabled()) {
      StringBuffer sb = new StringBuffer("[CallMethodRule]{");
      sb.append(this.digester.match);
      sb.append("} Call ");
      if (top == null) {
        sb.append("[NULL TOP]");
      } else {
        sb.append(top.getClass().getName());
      } 
      sb.append(".");
      sb.append(this.methodName);
      sb.append("(");
      for (int j = 0; j < paramValues.length; j++) {
        if (j > 0)
          sb.append(","); 
        if (paramValues[j] == null) {
          sb.append("null");
        } else {
          sb.append(paramValues[j].toString());
        } 
        sb.append("/");
        if (this.paramTypes[j] == null) {
          sb.append("null");
        } else {
          sb.append(this.paramTypes[j].getName());
        } 
      } 
      sb.append(")");
      this.digester.log.debug(sb.toString());
    } 
    if (this.useExactMatch) {
      MethodUtils.invokeExactMethod(top, this.methodName, paramValues, this.paramTypes);
    } else {
      MethodUtils.invokeMethod(top, this.methodName, paramValues, this.paramTypes);
    } 
  }
  
  public void finish() throws Exception {
    this.bodyText = null;
  }
  
  public String toString() {
    StringBuffer sb = new StringBuffer("CallMethodRule[");
    sb.append("methodName=");
    sb.append(this.methodName);
    sb.append(", paramCount=");
    sb.append(this.paramCount);
    sb.append(", paramTypes={");
    if (this.paramTypes != null)
      for (int i = 0; i < this.paramTypes.length; i++) {
        if (i > 0)
          sb.append(", "); 
        sb.append(this.paramTypes[i].getName());
      }  
    sb.append("}");
    sb.append("]");
    return sb.toString();
  }
}
