/*
 * 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.util.HashMap;
import java.util.Map;
import java.util.Vector;

import org.xml.sax.Attributes;

/**
 * <P>
 * 各種ノードの抽象クラスです。
 * ノードとは、配備記述子に記述されるタグです。
 * </P>
 * <P>
 * このクラスは、形式は異なるが主要な機能は共通である、複数のノードの抽象化です。
 * たとえば set や get など各種のノードでも、ノードのツリー構造や、属性の格納方法などは共通します。
 * </P>
 * <P>
 * Node クラスのサブクラスには、それぞれ異なる種類の情報のセットが格納され、オブジェクトの形成方法が異なります。
 * しかしこれらのノードはすべて、Node クラスのサブクラス化によって実装できます。
 * </P>
 * @author Kumiko Hiroi
 */
public abstract class Node
	extends Vector
{
	private Node parent = null;
	private Map attributes = null;

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

		this.parent = parent;
		if (attributes != null)
		{
			this.attributes = new HashMap();
			for (int i = 0; i < attributes.getLength(); i++)
			{
				String key = attributes.getLocalName(i);
				if (key == null || key.equals(""))
				{
					key = attributes.getQName(i);
				}
				this.attributes.put(key, attributes.getValue(i));
			}
		}
	}

	/**
	 * <P>
	 * このノードのすべての属性がマップされた ConversionMap オブジェクトを取得します。
	 * </P>
	 * @return すべての属性がマップされた ConversionMap オブジェクト
	 */
	public Map getAttributes()
	{
		return attributes;
	}

	/**
	 * <P>
	 * 指定された名前の属性の値を取得します。
	 * </P>
	 * @return 属性の値
	 */
	public String getAttribute(String name)
	{
		return (String)attributes.get(name);
	}

	/**
	 * <P>
	 * このノードの親ノードを取得します。
	 * </P>
	 * @return 親ノード
	 */
	public Node getParent()
	{
		return parent;
	}

	/**
	 * <P>
	 * このノードの class 属性に指定されたクラスを取得します。
	 * </P>
	 * @return class 属性に指定されたクラス
	 * @exception ClassNotFoundException クラスが見つからなかった場合
	 */
	protected Class getNodeClass() throws ClassNotFoundException
	{
		String className = getAttribute("class");
		if (className == null)
		{
			return null;
		}

		return Thread.currentThread().getContextClassLoader().loadClass(className);
	}

	/**
	 * <P>
	 * このノードの name 属性に指定された名前を取得します。
	 * 名前は 1 文字目が大文字に変換されます。
	 * </P>
	 * @return name 属性に指定された名前
	 */
	protected String getNodeName()
	{
		String name = getAttribute("name");
		if (name == null || name.length() == 0)
		{
			return "";
		}
		else if (name.length() == 1)
		{
			return name.toUpperCase();
		}
		else
		{
			return name.substring(0, 1).toUpperCase() + name.substring(1);
		}
	}

	/**
	 * <P>
	 * 指定されたオブジェクトが表す値を取得します。
	 * オブジェクトが Node オブジェクトの場合は、その Node オブジェクトが表す値を取得します。
	 * </P>
	 * @param obj 値が取得されるオブジェクト
	 * @return オブジェクトが表す値
	 */
	protected Object getValue(Object obj) throws ConfigureException
	{
		try
		{
			if (size() == 0)
			{
				return null;
			}

			int first = 0;
			int last = size() - 1;

			Object item = null;
			while (first <= last)
			{
				item = get(first);
				if (!(item instanceof String))
				{
					break;
				}
				item = ((String) item).trim();
				if (((String) item).length() > 0)
				{
					break;
				}
				first++;
			}

			while (first < last)
			{
				item = get(last);
				if (!(item instanceof String))
				{
					break;
				}
				item = ((String) item).trim();
				if (((String) item).length() > 0)
				{
					break;
				}
				last--;
			}

			if (first > last)
			{
				return null;
			}

			Object value = null;

			if (first == last)
			{
				value = getItemValue(obj, get(first));
			}
			else
			{
				StringBuffer buf = new StringBuffer();
				synchronized (buf)
				{
					for (int i = first; i <= last; i++)
					{
						Object o = get(i);
						buf.append(getItemValue(obj, o));
					}
					value = buf.toString();
				}
			}

			if (value != null && value instanceof String)
			{
				return ((String) value).trim();
			}
			return value;
		}
		catch (Exception e)
		{
			throw new ConfigureException(e);
		}
	}

	/**
	 * <P>
	 * オブジェクト item が表す値を取得します。
	 * item が Node オブジェクトの場合は、item.configure(obj) を呼び出して形成されたオブジェクトを返します。
	 * </P>
	 * @param obj item が Node オブジェクトの場合に形成されるオブジェクト
	 * @return item が表す値
	 */
	protected Object getItemValue(Object obj, Object item) throws ConfigureException
	{
		try
		{
			if (item instanceof String)
			{
				return item;
			}

			Node node = (Node) item;
			return node.configure(obj);
		}
		catch (Exception e)
		{
			throw new ConfigureException(e);
		}
	}

	/**
	 * <P>
	 * このメソッドは抽象メソッドのため何もしません。
	 * このメソッドは、自身のノードのタグ値を返すために、サブクラスによって実装されます。
	 * </P>
	 * @return タグ値
	 */
	public abstract int getTag();

	/**
	 * <P>
	 * このメソッドは抽象メソッドのため何もしません。
	 * このメソッドは、指定されたオブジェクトを形成するために、サブクラスによって実装されます。
	 * </P>
	 * @param obj 形成するオブジェクト
	 * @return このメソッドで値が返されることはありません
	 * @exception ConfigureException オブジェクトが形成できなかった場合
	 */
	public abstract Object configure(Object obj) throws ConfigureException;

	/**
	 * <P>
	 * 指定されたオブジェクトを形成します。
	 * </P>
	 * @param obj 形成するオブジェクト
	 * @param index このノードに格納された子ノードのリストのインデックス
	 * @exception ConfigureException オブジェクトが形成できなかった場合
	 */
	public void configure(Object obj, int index) throws ConfigureException
	{
		try
		{
			for (int i = index; i < size(); i++)
			{
				Object o = get(i);
				if (o instanceof String)
				{
					continue;
				}
				Node node = (Node) o;
				node.configure(obj);
			}
		}
		catch (Exception e)
		{
			throw new ConfigureException(e);
		}
	}

}
