/*
 * $Id$
 * Copyright (C) 2004-2007 SUGIMOTO Ken-ichi
 * 作成日: 2007/06/11
 */
package feat2.impl;

import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;

import feat2.ApplicationException;
import feat2.CommandContext;
import feat2.FileUploadException;
import feat2.ObjectUtil;
import feat2.PropertyAccessException;
import feat2.Response;
import feat2.TemplateParsingException;
import feat2.config.BeanXMLConfig;
import feat2.config.type.Scope;

/**
 * XMLBeanレスポンスの実装。
 * @author SUGIMOTO Ken-ichi
 */
public class BeanXMLResponse implements Response {

    private BeanXMLConfig config;


    public BeanXMLResponse(BeanXMLConfig config) {
        this.config = config;
    }


    /**
     * <p>オブジェクトをXMLで出力する。</p>
     * <p>値がプリミティブ型, String型, Date型の場合はproperty要素、
     * List型または配列の場合はlist要素、それ以外のオブジェクトはbeanとして扱い
     * object要素で出力。ただし、Map型はキーをプロパティ名として扱う。</p>
     */
    public String output(CommandContext ctx) throws IOException,
            PropertyAccessException, TemplateParsingException,
            ServletException, ApplicationException, FileUploadException {


        // 指定のスコープから値を取り出す

        Scope scope = config.getScope();
        if ( scope == null )
            scope = new Scope("local");

        Object retVal = ctx.getAttribute(config.getRef().getName(), scope);


        // XMLに変換

        XMLWriter w = new XMLWriter();
        writeObject("", retVal, w);
        String xml = w.toString();


        // レスポンスに出力

        HttpServletResponse resp = ctx.getResponse();
        resp.setContentType("text/xml; charset=UTF-8");

        PrintWriter writer = resp.getWriter();
        writer.print(xml);

        return null;

    }


    private void writeObject(String name, Object val, XMLWriter w) {

        if ( val == null )
            return;

        if ( val instanceof List )
            writeList(name, (List)val, w);

        else if ( val.getClass().isArray() )
            writeArray(name, (Object[])val, w);

        else if ( val instanceof Map )
            writeMap(name, (Map)val, w);


        // 数値

        else if ( val instanceof Number ) {
            w.writeNumber(name, (Number)val);
        }

        // Number形以外の値

        else if ( val instanceof Boolean ) {
            w.writeBoolean(name, ((Boolean)val).booleanValue());
        }

        else if ( val instanceof Character ) {
            w.writeString(name, val.toString());
        }

        else if ( val instanceof String ) {
            w.writeString(name, val.toString());
        }

        else if ( val instanceof Date ) {
            w.writeDate(name, (Date)val);
        }

        else {
            writeBean(name, val, w);
        }


    }


    private void writeList(String name, List list, XMLWriter w) {

        w.startList(name);

        for(int i=0; i<list.size(); i++) {

            writeObject("", list.get(i), w);

        }

        w.endList();

    }


    private void writeArray(String name, Object[] a, XMLWriter w) {

        w.startList(name);

        for(int i=0; i<a.length; i++) {
            writeObject("", a[i], w);
        }

        w.endList();

    }


    private void writeMap(String name, Map map, XMLWriter w) {

        w.startObject(name, map.getClass().getName());

        for(Iterator keys = map.keySet().iterator(); keys.hasNext(); ) {

            String key = (String)keys.next();
            writeObject(key, map.get(key), w);

        }

        w.endObject();

    }


    private void writeBean(String name, Object bean, XMLWriter w) {

        w.startObject(name, bean.getClass().getName());

        String[] propNames = ObjectUtil.getPropertyNames(bean);
        for(int i=0; i<propNames.length; i++) {
            if ( !propNames[i].equals("class") ) {
                try {
                    Object prop = ObjectUtil.getProperty(bean, propNames[i]);
                    writeObject(propNames[i], prop, w);
                }
                catch (NoSuchMethodException ex) {
                }
                catch (InvocationTargetException ex) {
                }
                catch (IllegalAccessException ex) {
                }
            }
        }

        w.endObject();

    }

}
