/**
 * Copyright 2007 Dr. Matthias Laux
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package ml.options;

import javax.xml.validation.*;

/**
 * Validator for XML documents using XML schema. This is based on JDK 5.0 and requires 
 * no outside library.
 */
public class SchemaValidator extends org.xml.sax.helpers.DefaultHandler {

    private final String CLASS = "SchemaValidator";
    private final String xsdFile = "config/options.xsd";
    private String error = null;

    /**
     * The actual validation method. If validation is not successful, the errors found can be retrieved
     * using the {@link #getError()} method.
     * <p>
     * @param xmlReader The reader for the XML file to validate
     * <p>
     * @return <code>true</code> if the XML file could be validated against the XML schema, else <code>false</code>
     * @throws java.io.IOException
     * @throws org.xml.sax.SAXException 
     */
    public boolean validate(java.io.Reader xmlReader) throws java.io.IOException,
                                                              org.xml.sax.SAXException {

        if (xmlReader == null) {
            throw new IllegalArgumentException(CLASS + ": xmlReader may not be null");
        }

//.... Get the XML schema from the JAR and create a validator

        SchemaFactory factory = SchemaFactory.newInstance(javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI);
        ClassLoader loader = this.getClass().getClassLoader();
        java.net.URL url = loader.getResource(xsdFile);
        Schema schema = factory.newSchema(url);
        Validator validator = schema.newValidator();

        validator.setErrorHandler(this);

//.... Try to validate the XML file given

        org.xml.sax.InputSource source = new org.xml.sax.InputSource(new java.io.BufferedReader(xmlReader));
        validator.validate(new javax.xml.transform.sax.SAXSource(source));

        return getError() == null ? true : false;

    }

//-----------------------------------------------------------------------------------------
// Below are helper methods that are required to improve the error handling (specifically,
// to make sure all thrown exceptions are reported, and row and column numbers are added
// to the output for better debugging
//-----------------------------------------------------------------------------------------
    /**
     * Retrieve the error message set by the <code>org.xml.sax.ErrorHandler</code> methods.
     * If no error has been found, <code>null</code> is returned.
     * <p>
     * @return A string describing the error encountered
     */
    public String getError() {
        return error;
    }

    /**
     * A method required by the <code>org.xml.sax.ErrorHandler</code> interface
     * <p>
     * @param ex A parsing exception
     */
    @Override
    public void warning(org.xml.sax.SAXParseException ex) throws org.xml.sax.SAXException {
        getError("Warning", ex);
    }

    /**
     * A method required by the <code>org.xml.sax.ErrorHandler</code> interface
     * <p>
     * @param ex A parsing exception
     */
    @Override
    public void error(org.xml.sax.SAXParseException ex) throws org.xml.sax.SAXException {
        getError("Error", ex);
    }

    /**
     * A method required by the <code>org.xml.sax.ErrorHandler</code> interface
     * <p>
     * @param ex A parsing exception
     */
    @Override
    public void fatalError(org.xml.sax.SAXParseException ex) throws org.xml.sax.SAXException {
        getError("Fatal Error", ex);
    }

    /**
     * A helper method for the formatting
     */
    private void getError(String type, org.xml.sax.SAXParseException ex) {

        StringBuilder out = new StringBuilder(200);

        out.append(type);

        if (ex == null) {
            out.append("!!!");
        }

        String systemId = ex.getSystemId();
        if (systemId != null) {
            int index = systemId.lastIndexOf('/');
            if (index != -1) {
                systemId = systemId.substring(index + 1);
            }
            out.append(systemId);
        }
        out.append(": Row ");
        out.append(ex.getLineNumber());
        out.append(" /`Col ");
        out.append(ex.getColumnNumber());
        out.append(": ");
        out.append(ex.getMessage());

//.... There may be multiple exceptions thrown, we don't want to miss any information

        if (error == null) {
            error = out.toString();
        } else {
            error += "\n" + out.toString();
        }

    }
}

