/*
 * Copyright (c) 2003, Morpho Project.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. Neither the name of the Morpho Project nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
package jp.morpho.config;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

import org.xml.sax.Attributes;

/**
 * <P>
 * SetNode クラスは、set タグを表します。
 * set タグには、#PCDATA 、call 、new および SystemProperty タグを含めることができます。
 * </P>
 * <P>
 * 例えば set タグは、次のように配備記述子内に記述されます。
 * </P>
 * <P>
 * <PRE>
 *     &lt;set name="someMethod"&gt;someValue&lt;/set&gt;
 * </PRE>
 * </P>
 * <P>
 * これは、次のようなコードを書くことと同じです。
 * </P>
 * <P>
 * <PRE>
 *     obj.setSomeMethod("someValue");
 * </PRE>
 * </P>
 * @author Kumiko Hiroi
 */
public class SetNode extends Node
{
	private String prefix = null;

	/** プリミティブ型 */
	private static Class[] primitiveTypes =
	{
		Boolean.TYPE,
		Character.TYPE,
		Byte.TYPE,
		Short.TYPE,
		Integer.TYPE,
		Long.TYPE,
		Float.TYPE,
		Double.TYPE,
		Void.TYPE
	};

	/** プリミティブ型のラッパ */
	private static Class[] primitiveWrappers =
	{
		Boolean.class,
		Character.class,
		Byte.class,
		Short.class,
		Integer.class,
		Long.class,
		Float.class,
		Double.class,
		Void.class
	};

	/**
	 * <P>
	 * 親ノードとノードの属性を指定して SetNode オブジェクトを作成します。
	 * </P>
	 * @param parent このノードの親ノード
	 * @param attributes このノードの属性
	 */
	public SetNode(Node parent, Attributes attributes)
	{
		this(parent, attributes, "set");
	}

	/**
	 * <P>
	 * 親ノードとノードの属性、メソッドのプレフィックス (例えば set) を指定して SetNode オブジェクトを作成します。
	 * このコンストラクタは、SetNode クラスと同じような動作をするサブクラスを作成する場合に有効です。
	 * </P>
	 * @param parent このノードの親ノード
	 * @param attributes このノードの属性
	 * @param prefix メソッドのプレフィックス (例えば set)
	 */
	protected SetNode(Node parent, Attributes attributes, String prefix)
	{
		super(parent, attributes);
		this.prefix = prefix;
	}

	/**
	 * <P>
	 * タグ値を返します。
	 * </P>
	 * @return タグ値
	 * @see jp.morpho.config.Node#getTag()
	 */
	public int getTag()
	{
		return NodeTypes.SET;
	}

	/**
	 * <P>
	 * 指定されたオブジェクトを形成します。
	 * クラスの説明にあるように、オブジェクト obj の set メソッドを呼び出します。
	 * </P>
	 * @param obj 形成するオブジェクト
	 * @return 形成されたオブジェクト
	 * @exception ConfigureException オブジェクトが形成できなかった場合
	 * @see jp.morpho.config.Node#configure(Object)
	 */
	public Object configure(Object obj) throws ConfigureException
	{
		try
		{
			String name = getNodeName();
			String methodName = prefix + name;
			Object value = getValue(obj);
			Object[] arg = { value };

			Class objCls = getNodeClass();
			if (objCls != null)
			{
				obj = null;
			}
			else
			{
				objCls = obj.getClass();
			}

			Class[] argCls = { Object.class };
			if (value != null)
			{
				argCls[0] = value.getClass();
			}

			try
			{
				Method method = objCls.getMethod(methodName, argCls);
				method.invoke(obj, arg);
				return null;
			}
			catch (IllegalArgumentException e)
			{
			}
			catch (IllegalAccessException e)
			{
			}
			catch (NoSuchMethodException e)
			{
			}

			try
			{
				Field type = argCls[0].getField("TYPE");
				argCls[0] = (Class) type.get(null);
				Method method = objCls.getMethod(methodName, argCls);
				method.invoke(obj, arg);
				return null;
			}
			catch (NoSuchFieldException e)
			{
			}
			catch (IllegalArgumentException e)
			{
			}
			catch (IllegalAccessException e)
			{
			}
			catch (NoSuchMethodException e)
			{
			}

			Method[] methods = objCls.getMethods();
			Method method = null;
			for (int s = 0; methods != null && s < methods.length; s++)
			{
				if (methodName.equals(methods[s].getName()) && methods[s].getParameterTypes().length == 1)
				{
					try
					{
						method = methods[s];
						methods[s].invoke(obj, arg);
						return null;
					}
					catch (IllegalArgumentException e)
					{
					}
					catch (IllegalAccessException e)
					{
					}
				}
			}

			if (method != null)
			{
				try
				{
					Class paramCls = method.getParameterTypes()[0];
					if (paramCls.isPrimitive())
					{
						for (int t = 0; t < primitiveTypes.length; t++)
						{
							if (paramCls.equals(primitiveTypes[t]))
							{
								paramCls = primitiveWrappers[t];
								break;
							}
						}
					}
					Constructor cons = paramCls.getConstructor(argCls);
					arg[0] = cons.newInstance(arg);
					method.invoke(obj, arg);
					return null;
				}
				catch (NoSuchMethodException e)
				{
				}
				catch (IllegalAccessException e)
				{
				}
				catch (InstantiationException e)
				{
				}
			}

			throw new NoSuchMethodException(objCls + "." + methodName + "(" + argCls[0] + ")");
		}
		catch (Exception e)
		{
			throw new ConfigureException(e);
		}
	}

}
