/*
 * Fichero:     DocumentoXML.java
 * Fecha:       20/10/06
 * Autor:       Alvaro Sarmiento Losada
 * Asignatura:  Proyecto Fin de Carrera
 * Descripción: Contiene la clase DocumentoXML, que representa a un documento
 *              de contenido XML mediante una representación JDOM.
 *
 * Este fichero pertenece al proyecto fin de carrera del autor,
 * consistente en el estudio de JXTA y su aplicación al comercio 
 * electrónico b2b.
 */

package com.pfc.lib.xml;

import java.io.*;
import java.util.*;
import org.jdom.*;
import org.jdom.input.SAXBuilder;
import org.jdom.output.XMLOutputter;
import org.jdom.xpath.*;

/**
 * Esta clase representa a un objeto XML construido mediante JDOM. Contiene una serie de métodos útiles para generar estructuras XML y  para poder acceder a los nodos JDOM. 
 * @author   Alvaro Sarmiento Losada
 * @see  MensajeXML
 */
public class DocumentoXML extends    Object 
                          implements Serializable, 
                                     Cloneable {
    
    /**
     * Almacena un documento XML de JDOM
     */
    public Document documento;
    
    /** 
     * Constructor básico de la clase. Crea una nueva instancia de
     * <code>DocumentoXML</code> vacía.
     */
    public DocumentoXML() {
        documento = new Document();
    }
    
    /**
     * Crea una nueva instancia de <code>DocumentoXML</code> a partir de un
     * documento JDOM.
     *
     * @param documento el objeto <code>Document</code> JDOM a partir del cual
     *                  se crea un nuevo <code>DocumentoXML</code>.
     */
    public DocumentoXML(Document documento) {
        this.documento = documento;
    }
    
    /**
     * Crea una nueva instancia de <code>DocumentoXML</code> a partir de un
     * archivo que debe contener un texto XML válido cualesquiera.
     *
     * @param  archivo un archivo que contiene XML a partir del cual se
     *                 construirá el objeto <code>DocumentoXML</code>.
     * @throws IOException si ocurre un error de I/O que impida que el
     *                     documento contenido en el archivo sea leido
     *                     completamente.
     */
    public DocumentoXML(File archivo) throws IOException {
        //Se crea un constructor de árboles JDOM basado en SAX.
        SAXBuilder constructorSAX = new SAXBuilder();
        /*
         * No se valida puesto que si la DTD no está disponible, se 
         * produciría un error.
         */
        constructorSAX.setValidation(false);
        
        try {
            //Se crea un nuevo objeto Document de JDOM a partir del archivo.
            documento = constructorSAX.build(archivo);
        } catch (JDOMException je) {
            documento = null; //Se capturan los errores de parseo
        } catch (IOException ioe) {
            throw ioe;        //pero no los relacionados con I/O del fichero
        }
    }
    
    /**
     * Crea una nueva instancia de <code>DocumentoXML</code> e inicializa
     * la declaración de tipo de documento XML con los valores 
     * especificados en <code>tipo</code> y <code>dtd</code>.<br>
     * 
     * @param tipo el tipo de documento XML que se desea construir.
     * @param dtd  la localización del DTD que se quiere usar en este 
     *             <code>DocumentoXML</code>.
     */
    public DocumentoXML(String tipo, String dtd) {
        this();
        
        //Se crea el elemento de tipo de documento XML
        DocType doctype = new DocType(tipo);
        doctype.setSystemID(dtd);
        
        documento.setDocType(doctype); //se asigna al documento JDOM
    }
    
    /**
     * Obtiene una representación de este objeto <code>DocumentoXML</code>
     * en forma de cadena <code>String</code> en formato XML.
     *
     * @return una cadena <code>String</code> con el texto XML de este objeto
     *         <code>DocumentoXML</code>.
     */
    public String toString() {
        //Se crea un objeto que transforma árboles JDOM en flujos XML.
        XMLOutputter out = new XMLOutputter();
        
        //El JDOM se convierte en flujo XML en forma de String
        return out.outputString(documento);
    }
    
    /**
     * Crea una nueva instancia de <code>DocumentoXML</code> idéntica
     * a este objeto.
     *
     * @return un objeto idéntico a este <code>DocumentoXML</code>.
     */
    public Object clone() {
        return (Object) new DocumentoXML(documento);
    }
    
    /**
     * Determina si el objeto especificado es igual a este.
     *
     * @param  objeto el objeto a comparar.
     * @return        si el objeto a comparar y este objeto son iguales.
     */
    public boolean equals(Object objeto) {
        return documento.equals(objeto);
    }
    
    /**
     * Almacena el documento XML representado por este objeto en el archivo
     * especificado. El texto XML se guardará como texto formateado e
     * indentado.
     *
     * @param  archivo el archivo donde se quiere almacenar este objeto 
     *                 en forma de XML 
     * @return         <code>true</code> si el XML se ha guardado 
     *                 correctamente. <br> <code>false</code> si ocurrió
     *                 algún error durante la operación.
     */
    public boolean guardarEnArchivo(File archivo) {
        boolean resultado;
        
        try {
            //Se crea un objeto que transforma árboles JDOM en flujos
            XMLOutputter out = new XMLOutputter();
            //Se crea un flujo de escritura hacia el archivo
            OutputStream flujo = new FileOutputStream(archivo);
            
            
           
            //Se escribe en el archivo el flujo XML
            out.output(documento, flujo);
            flujo.close();
            
            resultado = true;
        } catch(Exception ex) {
            //No se procesa el error, pero se devuelve false
            resultado = false;
        }
        
        return resultado;        
    }
    
    /**
	 * Devuelve un documento <code>Document</code> con la representación en JDOM del XML de este objeto.
	 * @return  un documento JDOM con el XML de este objeto.
	 * @uml.property  name="documento"
	 */
    public Document getDocumento() {
        return documento;
    }
    
    /**
	 * Establece el contenido XML del documento <code>Document</code> JDOM especificado en este objeto.
	 * @param documento  el documento JDOM cuyo contenido XML se quiere   establecer en este objeto.
	 * @uml.property  name="documento"
	 */
    public void setDocumento(Document documento) {
        this.documento = documento;
    }
    
    /**
     * Devuelve el elemento raiz en forma de elemento JDOM de este
     * documento XML.
     *
     * @return el elemento JDOM raíz de este documento.
     */
    public Element getElementoRaiz() {
        return documento.getRootElement();
    }
    
    /**
     * Establece el elemento raiz de este documento XML a partir de
     * un elemento JDOM
     *
     * @param raiz el elemento JDOM que se quiere establecer como elemento
     *             raiz de este objeto.
     */
    public void setElementoRaiz(Element raiz) {
        documento.setRootElement(raiz);
    }
    
    /**
     * Obtiene la declaración de tipo de documento XML de este documento.
     *
     * @return un objeto <code>DocType</code> JDOM con la declaración
     *         de tipo de documento XML de este objeto.
     */
    public DocType getTipoDocumento() {
        return documento.getDocType();
    }
    
    /**
     * Obtiene un sólo objeto JDOM a partir de una ruta válida XPATH.
     * Este método es una implementación básica de XPATH proporcionado
     * por JDOM.
     *
     * @param  ruta  una expresión XPATH válida para seleccionar un objeto.
     * @return       un objeto JDOM, cuyo tipo depende de la expresión XPATH
     *               especificada en <code>ruta</code>.Devuelve 
     *               <code>null</code> si ocurre algún error al evaluar
     *               la expresión XPATH.
     */
    public Object getObjetoPorXPath(String ruta) {
       Object resultado=null;
        try {
            XPath xpath = Ayuda2.newInstance(ruta);
        	//XPath xpath = XPathFactory.newInstance().newXPath();

            /*
             * Se selecciona un solo elemento XML, que depende de la
             * expresión XPATH usada.
             */
            resultado = xpath.selectSingleNode(documento);
        	
        	//resultado=xpath.evaluate(ruta, documento, XPathConstants.NODE);

        } catch (Exception jde) {
            resultado = null;
        }
        return resultado;
    }
    
    /**
     * Obtiene una lista de objetos JDOM a partir de una ruta válida XPATH.
     * Este método es una implementación básica de XPATH proporcionado
     * por JDOM.
     *
     * @param  ruta  una expresión XPATH válida para seleccionar la lista
     * @return       una lista de objetos JDOM. El tipo de los objetos 
     *               de la lista depende de la expresión XPATH. Devuelve 
     *               <code>null</code> si ocurre algún error al evaluar
     *               la expresión XPATH.
     */    
    public List getObjetosPorXPath(String ruta) {        
        List resultado = null;
        try {
            XPath xpath = Ayuda2.newInstance(ruta);
        	//XPath xpath = XPathFactory.newInstance().newXPath();
            /*
             * Se selecciona varios elementos XML, dependiendo de la
             * expresión XPATH usada.
             */
            resultado = xpath.selectNodes(documento);
        	//resultado=(List)xpath.evaluate(ruta, documento, XPathConstants.NODESET);

        } catch (Exception jde) {
            resultado = null;
        }
        return resultado;
    }
    
    /**
     * Obtiene un elemento <code>Element</code> JDOM a partir de la ruta
     * XPATH especificada.
     *
     * @param  ruta una expresión XPATH válida para seleccionar el elemento.
     * @return      el elemento JDOM seleccionado mediante la ruta XPATH.
     */
    public Element getElemento(String ruta) {
        return (Element) getObjetoPorXPath(ruta);
    }
    
    /**
     * Obtiene una cadena con el valor de un atributo de un elemento XML,
     * dado una ruta XPATH válida hacia el elemento y el nombre del atributo
     * cuyo valor se quiere obtener.
     *
     * @param  ruta     la ruta XPATH válida hacia el elemento con el
     *                  atributo cuyo valor se desea conocer.
     * @param  atributo el nombre del atributo cuyo valor se desea conocer.
     * @return          una cadena <code>String</code> con el valor del
     *                  atributo.
     */
    public String getValorAtributo(String ruta, String atributo) {                
        Attribute at = (Attribute) getObjetoPorXPath(ruta + "/@" + atributo);
        return at.getValue();
    }
    
    /**
     * Devuelve si el documento XML representado por este objeto es
     * bien formado respecto a la sintaxis XML.
     *
     * @return <code>true</code> si el documento XML es bien formado. <br>
     *         <code>false</code> si no lo es, o este objeto no tiene
     *         aún XML asociado.
     */
    public boolean esBienFormado() {
        boolean resultado = false;
        InputStream is;
        
        if( documento != null) {
            is = convertirEnInputStream();
            resultado = AnalizadorXML.esBienFormado(is);
        }
        
        return resultado;
    }
    
    /**
     * Devuelve si el documento XML representado por este objeto es
     * válido respecto al DTD referenciado en la declaración de tipo
     * de documento XML incluida en el documenti XML.
     *
     * @return <code>true</code> si el documento XML es válido. <br>
     *         <code>false</code> si no lo es, o este objeto no tiene
     *         aún XML asociado.
     */
    public boolean esValido() {
        boolean resultado = false;
        InputStream is;
        
        if( documento != null) {
            is = convertirEnInputStream();
            resultado = AnalizadorXML.esValido(is);
        }
        
        return resultado;
    }
    
    /**
     * Convierte el documento XML representado por este objeto en un flujo
     * de entrada <code>InputStream</code>.
     *
     * @return un flujo que representa el texto XML contenido en este
     *         documento. Devuelve <code>null</code> si ocurrió algún error
     *         o no hay XML contenido en este documento.
     */
    private InputStream convertirEnInputStream() {
        InputStream resultado;
        //Se crea un objeto que transforma árboles JDOM en flujos de bytes
        XMLOutputter out = new XMLOutputter();
               
        if( documento != null) {
            try {
                //Se crea el flujo de salida
                ByteArrayOutputStream os = new ByteArrayOutputStream();
                //Se escribe del XML al flujo de salida
                out.output(documento, os);
                
                resultado = new ByteArrayInputStream(os.toByteArray());
            } catch (IOException ioe) {
                resultado = null;
            }
            
        } else {
            resultado = null;
        }
        return resultado;
    }
    
}