/*
 * $Header: /home/cvsroot/main/com/hm/util/taglib/LinkTag.java,v 1.1 2001/06/08 08:10:57 efe Exp $
 * $Revision: 1.1 $
 * $Date: 2001/06/08 08:10:57 $
 *
 * ====================================================================
 */

package com.hm.util.taglib;

import javax.servlet.http.HttpServletResponse;
import java.net.URLEncoder;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.Iterator;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.BodyTagSupport;
import javax.servlet.jsp.tagext.BodyContent;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionForwards;
import org.apache.struts.util.MessageResources;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.struts.util.ResponseUtils;

import org.apache.log4j.Category;

/**
 * Generates an HTML link. This class adds the parameter feature to the Struts
 * html link tag.<br/>
 * It should be use as follow:
 * <pre>
 *  <hm:link href="http://java.sun.com">
 *    <hm:linkparam name="action" value="submit" />
 *    <hm:linkparam name="ref" value="144532" />
 *  </hm:linl>
 *  </pre>
 *  This will produce the equivalent of
 *  <pre><href="http://java.sun.com?action=submit&ref=144532"></pre>
 *
 * Title:        BSquare
 * Description:  Bsquare Projects
 * Copyright:    Copyright (c) 2001
 * Company:      HubMethods
 * @author $Author: efe $
 * @version $Revision: 1.1 $
 */

public class LinkTag extends BodyTagSupport {

    // ------------------------------------------------------ Logging
    Category cat = Category.getInstance(LinkTag.class);

    // ------------------------------------------------------ Instance Vartables
    /**
     * The full HREF URL
     */
    private StringBuffer hrefURL = new StringBuffer();

    /**
     * The tag body
     */
    String body = null;

    /**
     * The logical forward name from which to retrieve the hyperlink URI.
     */
    protected String forward = null;


    /**
     * The hyperlink URI.
     */
    protected String href = null;


    /**
     * The message resources for this package.
     */
    protected static MessageResources messages =
	MessageResources.getMessageResources
	("org.apache.struts.taglib.LocalStrings");


    /**
     * The JSP bean name for query parameters.
     */
    protected String name = null;


    /**
     * The JSP bean property name for query parameters.
     */
    protected String property = null;


    /**
     * The window target.
     */
    protected String target = null;



    // ------------------------------------------------------------- Properties


    /**
     * Return the logical forward name.
     */
    public String getForward() {

	return (this.forward);

    }


    /**
     * Set the logical forward name.
     *
     * @param forward The new logical forward name
     */
    public void setForward(String forward) {

	this.forward = forward;

    }


    /**
     * Return the hyperlink URI.
     */
    public String getHref() {

	return (this.href);

    }


    /**
     * Set the hyperlink URI.
     *
     * @param href Set the hyperlink URI
     */
    public void setHref(String href) {
        if (cat.isDebugEnabled()) cat.debug("Setting href to : '" + href + "'");
	this.href = href;

    }


    /**
     * Return the bean name.
     */
    public String getName() {

	return (this.name);

    }


    /**
     * Set the bean name.
     *
     * @param name The bean name
     */
    public void setName(String name) {

	this.name = name;

    }


    /**
     * Return the property name.
     */
    public String getProperty() {

	return (this.property);

    }


    /**
     * Set the property name.
     *
     * @param property The property name
     */
    public void setProperty(String property) {

	this.property = property;

    }


    /**
     * Return the window target.
     */
    public String getTarget() {

	return (this.target);

    }


    /**
     * Set the window target.
     *
     * @param target The new window target
     */
    public void setTarget(String target) {

	this.target = target;

    }


    //--------------------------------------------------------- Public Methods

    /**
     * Intialize the hyperlink.
     *
     * @exception JspException if a JSP exception has occurred
     */
    public int doStartTag() throws JspException {

	// Validate our attributes
	if ((forward == null) && (href == null))
	    throw new JspException
		(messages.getMessage("linkTag.destination"));
	else if ((forward != null) && (href != null))
	    throw new JspException
		(messages.getMessage("linkTag.destination"));

	hrefURL = new StringBuffer("<a href=\"");

        HttpServletResponse response =
          (HttpServletResponse) pageContext.getResponse();
	hrefURL.append(response.encodeURL(ResponseUtils.filter(hyperlink())));

        if (cat.isDebugEnabled()) cat.debug("hrefURL = '" + hrefURL.toString());

	// Evaluate the body of this tag
	return (EVAL_BODY_TAG);

    }

    /**
     * Add a new parameter to the request
     *
     * @param name the name of the request parameter
     * @param value the value of the request parameter
     */
    public void addRequestParameter(String name, String value) {
        if (cat.isDebugEnabled()) cat.debug("Adding '" + name + "' with value '" + value + "'");

        boolean question = (hrefURL.toString().indexOf('?') >= 0);

        if (question) { // There are request parameter already
            hrefURL.append('&');
        }
        else hrefURL.append('?');

        hrefURL.append(name);
        hrefURL.append('=');
        hrefURL.append(URLEncoder.encode(value));

        if (cat.isDebugEnabled()) cat.debug("hrefURL = '" + hrefURL.toString() + "'");
    }

    /**
     * Render the href reference
     *
     * @exception JspException if a JSP exception has occurred
     */
    public int doEndTag() throws JspException {

	hrefURL.append("\"");
	if (target != null) {
	    hrefURL.append(" target=\"");
    	    hrefURL.append(target);
	    hrefURL.append("\"");
	}
	hrefURL.append(">");

	// Print this element to our output writer
	JspWriter writer = pageContext.getOut();
	try {
	    writer.print(hrefURL.toString());
            writer.print(body);
            writer.print("</a>");
	} catch (IOException e) {
	    throw new JspException
		(messages.getMessage("common.io", e.toString()));
	}

	return (EVAL_PAGE);

    }

    /**
     * handle the body content
     *
     * @exception JspException if a JSP exception has occurred
     */
    public int doAfterBody() {
        BodyContent bodyContent = getBodyContent();
        this.body = bodyContent.getString();
        return SKIP_BODY;
    }

    /**
     * Release any acquired resources.
     */
    public void release() {

	super.release();
	forward = null;
	href = null;
	name = null;
	property = null;
	target = null;

    }


    // ----------------------------------------------------- Protected Methods

    /**
     * Return the specified hyperlink, modified as necessary with optional
     * request parameters.
     *
     * @exception JspException if an error occurs preparing the hyperlink
     */
    protected String hyperlink() throws JspException {

	String href = this.href;

	// If "forward" was specified, compute the "href" to forward to
	if (forward != null) {
	    ActionForwards forwards = (ActionForwards)
		pageContext.getAttribute(Action.FORWARDS_KEY,
					 PageContext.APPLICATION_SCOPE);
	    if (forwards == null)
		throw new JspException
		    (messages.getMessage("linkTag.forwards"));
	    ActionForward forward = forwards.findForward(this.forward);
	    if (forward == null)
		throw new JspException
		    (messages.getMessage("linkTag.forward"));
	    HttpServletRequest request =
		(HttpServletRequest) pageContext.getRequest();
	    href = request.getContextPath() + forward.getPath();
	}

	// Just return the "href" attribute if there is no bean to look up
	if ((property != null) && (name == null))
	    throw new JspException
		(messages.getMessage("getter.name"));
	if (name == null)
	    return (href);

	// Look up the map we will be using
	Object bean = pageContext.findAttribute(name);
	if (bean == null)
	    throw new JspException
		(messages.getMessage("getter.bean", name));
	Map map = null;
	if (property == null) {
	    try {
		map = (Map) bean;
	    } catch (ClassCastException e) {
		throw new JspException
		    (messages.getMessage("linkTag.type"));
	    }
	} else {
	    try {
		map = (Map) PropertyUtils.getProperty(bean, property);
		if (map == null)
		    throw new JspException
			(messages.getMessage("getter.property", property));
	    } catch (IllegalAccessException e) {
		throw new JspException
		    (messages.getMessage("getter.access", property, name));
	    } catch (InvocationTargetException e) {
		Throwable t = e.getTargetException();
		throw new JspException
		    (messages.getMessage("getter.result",
					 property, t.toString()));
	    } catch (ClassCastException e) {
		throw new JspException
		    (messages.getMessage("linkTag.type"));
	    } catch (NoSuchMethodException e) {
		throw new JspException
		    (messages.getMessage("getter.method", property, name));
	    }
	}

	// Append the required query parameters
	StringBuffer sb = new StringBuffer(href);
	boolean question = (href.indexOf("?") >= 0);
	Iterator keys = map.keySet().iterator();
	while (keys.hasNext()) {
	    String key = (String) keys.next();
	    Object value = map.get(key);
	    if (value instanceof String[]) {
		String values[] = (String[]) value;
		for (int i = 0; i < values.length; i++) {
		    if (question)
			sb.append('&');
		    else {
			sb.append('?');
			question = true;
		    }
		    sb.append(key);
		    sb.append('=');
		    sb.append(URLEncoder.encode(values[i]));
		}
	    } else {
		if (question)
		    sb.append('&');
		else {
		    sb.append('?');
		    question = true;
		}
		sb.append(key);
		sb.append('=');
		sb.append(URLEncoder.encode(value.toString()));
	    }
	}

	// Return the final result
	return (sb.toString());

    }
}