/* XMLUtils.java
 * =========================================================================
 * This file is part of the GrInvIn project - http://www.grinvin.org
 * 
 * Copyright (C) 2005-2008 Universiteit Gent
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 * 
 * This program 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
 * General Public License for more details.
 * 
 * A copy of the GNU General Public License can be found in the file
 * LICENSE.txt provided with the source distribution of this program (see
 * the META-INF directory in the source jar). This license can also be
 * found on the GNU website at http://www.gnu.org/licenses/gpl.html.
 * 
 * If you did not receive a copy of the GNU General Public License along
 * with this program, contact the lead developer, or write to the Free
 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

package org.grinvin.xml;

import java.io.IOException;
import java.io.InputStream;

import org.grinvin.io.IOFormatException;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;

import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 * Helper methods for XML-processing.
 */
public final class XMLUtils {
    
    // Should not be instantiated
    private XMLUtils() {
    }
    
    //
    private static final EntityResolver RESOLVER
            = new MyEntityResolver("/org/grinvin/dtds/"); // needs slash at the end
    
    //
    private static final SAXBuilder BUILDER = new SAXBuilder(true);

    //
    private static final SAXBuilder NONVALIDATING_BUILDER = new SAXBuilder(false);
    
    static {
        BUILDER.setEntityResolver(RESOLVER);
        NONVALIDATING_BUILDER.setEntityResolver(RESOLVER);
    }
    
    //
    private static final Class<XMLUtils> XML_UTILS_CLASS
            = XMLUtils.class;
    
    /**
     * Converts the given XML file into a JDOM element. The file should
     * reside in the class path, and all external entities (mainly DTDs)
     * are searched for in the package <code>org.grinvin.dtds</code>, also
     * in the class path.<p>
     * Has the same effect as a call to {@link #loadFromClassPath(ClassLoader,String)}
     * with the class loader of this class as a first parameter.
     * @param filename Filename relative to the root of the class path, must <i>not</i> start
     * with a slash (/).
     * @return the JDOM element corresponding to the root of file,
     * or null if the file did not exist.
     * @throws IOException if the file could not be read
     * @throws IOFormatException if something was wrong with the file format
     */
    public static Element loadFromClassPath(String filename) throws IOException {
        return loadFromClassPath(XML_UTILS_CLASS.getClassLoader(), filename);
    }
    
    /**
     * Converts the given XML file into a JDOM element. The file will
     * be loaded by the given class loader and all external entities (mainly DTDs)
     * are searched for in the package <code>org.grinvin.dtds</code>, but
     * resolved by the class loader of this class.
     * @param classLoader Class loader to be used for loading the XML file.
     * @param filename Filename relative to the root of the class path, must <i>not</i> start
     * with a slash (/).
     * @return the JDOM element corresponding to the root of file,
     * or null if the file did not exist.
     * @throws IOException if the file could not be read
     * @throws IOFormatException if something was wrong with the file format
     */
    public static Element loadFromClassPath(ClassLoader classLoader, String filename) throws IOException {
        InputStream stream = classLoader.getResourceAsStream(filename);
        if (stream == null)
            return null;
        try {
            return loadFromInputStream(stream);
        } catch (JDOMException ex) {
            throw new IOFormatException
                    ("Could not parse XML for file '" + filename + "'", ex);
        } finally {
            stream.close();
        }
    }
    
    /**
     * Converts the given XML on the input stream into a JDOM element with all
     * external entities (mainly DTDs) searched for in the package
     * <code>org.grinvin.dtds</code>, but resolved by the class loader of this class.
     * @param stream InputStream containing the XML code.
     * @return the JDOM element corresponding to the root of the XML.
     * @throws JDOMException if there is a problem parsing the XML.
     * @throws IOException if something was wrong with the InputStream.
     */
    public static Element loadFromInputStream(InputStream stream) throws JDOMException, IOException {
        return loadDocumentFromInputStream(stream).getRootElement();
    }
    
    public static Document loadDocumentFromInputStream(InputStream stream) throws JDOMException, IOException {
        return BUILDER.build(stream);
    }

    public static Element loadFromInputStreamNonValidating(InputStream stream) throws JDOMException, IOException {
        if (stream == null)
            return null;
        return NONVALIDATING_BUILDER.build(stream).getRootElement();
    }
    
    private static class MyEntityResolver implements EntityResolver {
        
        //
        private final String basePackage;
        
        //
        MyEntityResolver(String basePackage) {
            this.basePackage = basePackage;
        }
        
        public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
            if (publicId.startsWith("-//GrInvIn//")) {
                int pos = systemId.lastIndexOf('/');
                String resourceName = basePackage +  systemId.substring(pos+1);
                InputStream stream = XML_UTILS_CLASS.getResourceAsStream(resourceName);
                return new InputSource(stream);
            } else if (publicId.startsWith("-//GrInvIn IO//")) {
                int pos = "http://downloads.grinvin.org/dtds".length();
                String resourceName = basePackage +  systemId.substring(pos+1);
                InputStream stream = XML_UTILS_CLASS.getResourceAsStream(resourceName);
                return new InputSource(stream);
            } else
                return null;
        }
        
    }
    
}
