/* 
 * E-XML Library:  For XML, XML-RPC, HTTP, and related.
 * Copyright (C) 2002-2008  Elias Ross
 * 
 * genman@noderunner.net
 * http://noderunner.net/~genman
 * 
 * 1025 NE 73RD ST
 * SEATTLE WA 98115
 * USA
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * $Id$
 */

package net.noderunner.exml;

import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * This class contains all the same textual information represented in a DTD as
 * a collection of various Java objects. A DTD, or Document Type Description,
 * describes the entities, element rules, attributes, etc, to be used and
 * allowed in an XML document. This class does the same.
 * <p>
 * Typically, a DTD is built by reading in actual text, using
 * <code>XmlReader</code> on a document-type declaration. However, it may also
 * be built by calling various add methods. Once created, it can be re-used for
 * any document.
 * </p>
 * 
 * @author Elias Ross
 * @version 1.0
 * @see XmlReader
 */
public class Dtd {

	/**
	 * Fixed instance that has no declared entities.
	 */
	public static final Dtd EMPTY_DTD = new Dtd(Collections.<String, Entity>emptyMap(),
			Collections.<String, Entity>emptyMap(), 
			Collections.<String, ElementRule>emptyMap(),
			Collections.<String, Notation>emptyMap(), null);

	/**
	 * Document type name, the name of the root element.
	 */
	public String name;

	/**
	 * Contains document-definied XML entities.
	 */
	private Map<String, Entity> entities;

	/**
	 * Contains parameter entities, for DTD processing.
	 */
	private Map<String, Entity> pentities;

	/**
	 * Known element rules, what they can contain, etc. Maps String to
	 * ElementRule objects.
	 */
	private Map<String, ElementRule> elementRules;

	/**
	 * Known notations. Maps String to Notation objects.
	 */
	private Map<String, Notation> notations;

	/**
	 * Constructs a new DTD with no declared entities or element rules.
	 */
	public Dtd() {
		this(new HashMap(), new HashMap(), new HashMap(), null, null);
	}

	/**
	 * Constructs a new DTD with declared entities, parameter entities, element
	 * rules, notation declarations, and a document name.
	 * 
	 * @param entities
	 *            regular entities; map of String names to values
	 * @param pentities
	 *            paramter entities; map of String names to values
	 * @param elementRules
	 *            map of String names to {@link ElementRule} instances
	 * @param notations
	 *            map of String names to {@link Notation}
	 * @param name
	 *            document name
	 */
	public Dtd(Map<String, Entity> entities, Map<String, Entity> pentities, Map<String, ElementRule> elementRules, 
	        Map<String, Notation> notations,
			String name) {
		this.entities = entities;
		this.pentities = pentities;
		this.elementRules = elementRules;
		this.notations = notations;
		this.name = name;
	}

	/**
	 * Returns a <code>Map</code> of element names, which are
	 * <code>String</code> instances, to <code>ElementRule</code> instances.
	 * 
	 * @see #addElementRule
	 * @see ElementRule
	 */
	public Map<String, ElementRule> getKnownElements() {
		return elementRules;
	}

	/**
	 * Adds an element rule to this DTD.
	 * 
	 * @param name
	 *            element name to apply the rule to
	 * @param rule
	 *            rule to use.entrySet
	 */
	public void addElementRule(String name, ElementRule rule) {
		elementRules.put(name, rule);
	}

	/**
	 * Add an element rule to this DTD.
	 * 
	 * @param element
	 *            element to apply the rule to
	 * @param rule
	 *            rule to use
	 */
	public void addElementRule(Element element, ElementRule rule) {
		elementRules.put(element.getName(), rule);
	}

	/**
	 * Returns a rule stored in this DTD.
	 * 
	 * @param element
	 *            element name to retrive the rule for
	 * @return matching ElementRule or null if no rule
	 */
	public ElementRule getElementRule(String element) {
		return (ElementRule) elementRules.get(element);
	}

	/**
	 * Returns a rule stored in this DTD.
	 * 
	 * @param element
	 *            element to retrive the rule for
	 * @return matching ElementRule or null if no rule
	 */
	public ElementRule getElementRule(Element e) {
		return getElementRule(e.getName());
	}

	/**
	 * Adds a known entity. If this entity already was declared, it is not
	 * added, per the XML specification.
	 * 
	 * @param name
	 *            entity name, without its &amp; and terminating ;
	 * @param value
	 *            entity value
	 */
	public void addEntity(String name, Entity value) {
		if (!entities.containsKey(name))
			entities.put(name, value);
	}

	/**
	 * Returns a stored entity.
	 * 
	 * @return null if the entity is not recognized
	 */
	public Entity getEntity(String name) {
		return (Entity) entities.get(name);
	}

	/**
	 * Returns a stored parameter entity.
	 * 
	 * @return null if the paramter entity is not recognized
	 */
	public Entity getParameterEntity(String name) {
		return pentities.get(name);
	}

	/**
	 * Adds a known parameter entity to recognize.
	 * 
	 * @param name
	 *            parameter entity name, without its &amp; and terminating ;
	 * @param value
	 *            parameter entity value
	 */
	public void addParameterEntity(String name, Entity value) {
		pentities.put(name, value);
	}

	/**
	 * Returns the name of the document type.
	 */
	public String getName() {
		return name;
	}

	/**
	 * Sets the name of the document type.
	 */
	public void setName(String name) {
		this.name = name;
	}

	/**
	 * Adds a recognized notation.
	 */
	public void addNotation(String name, Notation n) {
		if (notations == null)
			notations = new HashMap<String, Notation>();
		notations.put(name, n);
	}

	/**
	 * Returns recognized notations as a <code>String</code> to
	 * <code>Notation</code> map. May return <code>null</code> if no
	 * notations exist.
	 */
	public Map getNotations() {
		return notations;
	}

	/**
	 * Formats and returns this object as a textual <code>DTD</code>.
	 */
	@Override
	public String toString() {
		Iterator i = getKnownElements().entrySet().iterator();
		StringBuffer sb = new StringBuffer(getKnownElements().size() * 32);
		while (i.hasNext()) {
			Map.Entry entry = (Map.Entry) i.next();
			String name = (String) entry.getKey();
			ElementRule rule = (ElementRule) entry.getValue();
			sb.append(rule.elementString(name));
			sb.append('\n');
			sb.append(rule.attlistString(name));
		}
		return sb.toString();
	}

}
