/***
 *** Title: DeclaredName Utility
 *** Author: Itsuki Noda
 *** Date: 2006.08.18
 ***/

package daruma.xml ;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.Text;

import org.xml.sax.Locator;
import org.xml.sax.SAXParseException;

import java.util.ArrayList ;
import java.util.List ;

import daruma.xml.NameSpace;
import daruma.xml.util.XMLParseErrorException;

import daruma.util.Pair ;

/**
 * default prefix が設定された NameSpace とLocalName を
 * 統一的に扱うためのクラス。
 * 
 * 予約語はできるだけこれで宣言しておく。
 */

public class DeclaredName {

    //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
    /* instance variables
     */

    public NameSpace ns ;
    public String localname ;

    //------------------------------------------------------------
    /* constructor
     */
    public DeclaredName() { 
	set(null, null) ; 
    }

    public DeclaredName(String localname) { 
	set(localname, null) ; 
    }

    public DeclaredName(String localname, NameSpace ns) {
	set(localname, ns) ; 
    }

    //------------------------------------------------------------
    /* set value
     */
    public DeclaredName set(String localname) 
    { 
	return set(localname, null) ; 
    }

    public DeclaredName set(String localname, NameSpace ns) {
	this.localname = localname ;
	this.ns = ns ;

	return this ;
    }

    //------------------------------------------------------------
    /* generate qualified name
     */
    public String genQName() { return this.genQName(this.ns) ; }
    public String genQName(NameSpace ns) {
	return NameSpace.genQName(ns, this.localname) ;
    }
    public String genQName(String prefix){
	return NameSpace.genQName(prefix, this.localname) ;
    }

    //------------------------------------------------------------
    /* generate simple Element with text
     * Common.java から移動
     */
    /* !!! [06/08/18 23:19 I.Noda] !!! 
     * obsolute に */
    //    Element genElementSimple(String text, 
    //			     Document document,
    //			     Node parent, 
    //			     boolean useParentPrefix) 
    //	throws Exception 
    //    {
    //	String prefix = null ;
    //	String nsUri = null ;
    //	if(this.ns != null) {
    //	    prefix = this.ns.defaultPrefix ;
    //	    nsUri = this.ns.uri ;
    //	}
    //	
    //	return DeclaredName.genElementSimple(prefix, nsUri, this.localname,
    //					     text, document, parent,
    //					     useParentPrefix) ;
    //    }

    //------------------------------------------------------------
    /**
     * QName から localname を取り出す。
     */
    public static String getLocalNameInQName(String qname) {
	int seppos = qname.indexOf(NameSpace.PrefixSeparator) ;
	if(seppos < 0) {
	    return qname ;
	} else {
	    return qname.substring(seppos + 1) ;
	}
    }

    //------------------------------------------------------------
    /**
     * QName から prefix を取り出す。
     */
    public static String getPrefixInQName(String qname) {
	int seppos = qname.indexOf(NameSpace.PrefixSeparator) ;
	if(seppos < 0) {
	    return null ;
	} else {
	    return qname.substring(0,seppos) ;
	}
    }

    /* !!! [06/10/16 15:34 I.Noda] !!! */
    //------------------------------------------------------------
    /**
     * 等価チェック。
     * matches と同じ。
     * Map 用に用意
     */
    public boolean equals(Node node) {
	return matches(node) ;
    }

    /* !!! [06/07/22 13:24 I.Noda] !!! */
    //------------------------------------------------------------
    /**
     * 与えられた XML node の名前が等しいかチェックする。
     */
    public boolean matches(Node node) {
	return matches(node, true) ;
    }

    public boolean matches(Node node, boolean checkNsP) {
	return (node.getLocalName().equals(this.localname) &&
		(!checkNsP ||
		 (this.ns == null) ||
		 this.ns.includes(node))) ;
    }

    /* !!! [06/10/17 20:44 I.Noda] !!! */
    public boolean matches(String nsUri, String localname) {
	return (this.localname.equals(localname) &&
		this.ns.equals(nsUri)) ;
    }

    /* !!! [06/10/16 15:34 I.Noda] !!! */
    //------------------------------------------------------------
    /**
     * 与えられた XML node の名前が等しいかチェックする。
     * マッチしなければ XMLParseErrorException を起こす。
     */
    public boolean matchesOrXmlException(Node node, String description) 
	throws XMLParseErrorException
    {
	if(! this.matches(node) ) {
	    throw new
		XMLParseErrorException(this.genExceptionMessage
				       (node, description,
					this.matches(node, false))) ;
	}
	return true ;
    }

    /* !!! [06/10/16 15:34 I.Noda] !!! */
    //------------------------------------------------------------
    /**
     * 与えられた XML node の名前が等しいかチェックする。
     * マッチしなければ SAXParseException を起こす。
     */
    public boolean matchesOrSaxException(Node node, Locator locator,
					 String description) 
	throws SAXParseException
    {
	if(! this.matches(node) ) {
	    throw new
		SAXParseException(this.genExceptionMessage
				  (node, description,
				   this.matches(node, false)),
				  locator) ;
	}
	return true ;
    }

    //------------------------------------------------------------
    /**
     * 与えられた XML node の名前が等しいかチェックする。
     * マッチしなければ SAXParseException を起こす。
     */
    public boolean matchesOrSaxException(String nsUri, String prefix,
					 String localname, Locator locator,
					 String description) 
	throws SAXParseException
    {
	if(! this.matches(nsUri, localname) ) {
	    throw new
		SAXParseException(this.genExceptionMessage
				  (nsUri, prefix, localname, description,
				   true),
				  locator) ;
	}
	return true ;
    }

    /* !!! [06/10/18 00:41 I.Nodaw] !!! */
    //------------------------------------------------------------
    /**
     * マッチしなかった場合の Exception のメッセージを返す。
     */
    public String genExceptionMessage(Node node, String description, 
				      boolean isNameSpaceError) 
    {
	return genExceptionMessage(node.getNamespaceURI(),
				   node.getPrefix(),
				   node.getLocalName(),
				   description,
				   isNameSpaceError) ;
    }

    public String genExceptionMessage(String nsUri, String prefix, 
				      String localname, String description, 
				      boolean isNameSpaceError) 
    {
	String descStr = 
	    (description == null) ? "" : "\n\t(" + description + ")" ;
	String tagName =
	    prefix != null ?
	    NameSpace.genQName(prefix, localname) :
	    NameSpace.genUName(nsUri, localname) ;
	
	if(isNameSpaceError) {
	    return ("Unexpected XML Node:" + "[" + tagName + "]:"
		    + "Namespace \n\t{" + this.ns.uri + "}\n is expected. "
		    + "But \n\t{" + nsUri + "}\n is used."
		    + descStr) ;
	} else {
	    return ("Unexpected XML Node:" + "[" + tagName + "]:"
		    + "Localname '" + this.localname + "' in namespace{"
		    + this.ns.uri + "} is expected." 
		    + descStr) ;
	}
    }
	
				      
    

    /* !!! [06/10/16 15:34 I.Noda] !!! */
    //------------------------------------------------------------
    /**
     * hash code を返す。
     */
    public int hashCode() {
	return ns.hashCode() + localname.hashCode() ;
    }

    /* !!! [06/10/16 15:34 I.Noda] !!! */
    //==================================================
    /**
     * DeclaredName 用テーブル
     */
    static public class Table<Value> {
	//==============================
	/**
	 * table entry class
	 */
	public class Entry extends Pair<DeclaredName, Value> {
	    public Entry(DeclaredName dname, Value value) {
		super(dname, value) ;
	    }
	    public DeclaredName getName() {
		return getFirst() ;
	    }
	    public Value getValue() {
		return getSecond() ;
	    }

	};

	//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
	/**
	 * member 変数
	 */
	private List<Entry> list ;

	//------------------------------
	/**
	 * コンストラクタ
	 */
	public Table() {
	    this.list = new ArrayList<Entry>() ;
	}

	//------------------------------
	/**
	 * データ登録
	 */
	public void put(DeclaredName dname, Value value) {
	    this.list.add(new Entry(dname, value)) ;
	}

	//------------------------------
	/**
	 * 検索
	 */
	public Value get(DeclaredName dname) {
	    for(Entry entry : this.list) {
		if(entry.getName().equals(dname)) {
		    return entry.getValue() ;
		}
	    }
	    return null ;
	}

	//------------------------------
	/**
	 * 検索
	 */
	public Value get(Element element) {
	    Entry entry = getEntry(element) ;
	    if(entry == null) {
		return null ;
	    } else {
		return entry.getValue() ;
	    }
	}
		

	//------------------------------
	/**
	 * 検索
	 */
	public Entry getEntry(Element element) {
	    for(Entry entry : this.list) {
		if(entry.getName().matches(element)) {
		    return entry ;
		}
	    }
	    return null ;
	}
	
	//------------------------------
	/**
	 * namespace を無視して検索
	 */
	public Value getByLocalName(String localname) {
	    Entry entry = getEntryByLocalName(localname) ;
	    if(entry == null) {
		return null ;
	    } else {
		return entry.getValue() ;
	    }
	}

	//------------------------------
	/**
	 * namespace を無視して検索
	 */
	public Entry getEntryByLocalName(String localname) {
	    for(Entry entry : this.list) {
		if(entry.getName().localname.equals(localname)) {
		    return entry ;
		}
	    }
	    return null ;
	}
	
    }

}
