/*--
 
Copyright (C) 2000 Brett McLaughlin & Jason Hunter.
 All rights reserved.

 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions
 are met:

 1. Redistributions of source code must retain the above copyright
    notice, this list of conditions, and the following disclaimer.

 2. Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions, and the disclaimer that follows
    these conditions in the documentation and/or other materials
    provided with the distribution.

 3. The name "JDOM" must not be used to endorse or promote products
    derived from this software without prior written permission.  For
    written permission, please contact license@jdom.org.

 4. Products derived from this software may not be called "JDOM", nor
    may "JDOM" appear in their name, without prior written permission
    from the JDOM Project Management (pm@jdom.org).

 In addition, we request (but do not require) that you include in the
 end-user documentation provided with the redistribution and/or in the
 software itself an acknowledgement equivalent to the following:
     "This product includes software developed by the
      JDOM Project (http://www.jdom.org/)."
 Alternatively, the acknowledgment may be graphical using the logos
 available at http://www.jdom.org/images/logos.

 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 DISCLAIMED.  IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT
 CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 SUCH DAMAGE.

 This software consists of voluntary contributions made by many
 individuals on behalf of the JDOM Project and was originally
 created by Brett McLaughlin <brett@jdom.org> and
 Jason Hunter <jhunter@jdom.org>.  For more information on the
 JDOM Project, please see <http://www.jdom.org/>.

 */


package org.jdom2.test.cases.input;

/**
 * Tests of SAXBuilder functionality.  Since most of these methods are tested in other parts
 * of the test suite, many tests are not filled.
 * 
 * @author Philip Nelson
 * @version 0.5
 */
import static org.jdom2.test.util.UnitTestUtil.checkException;
import static org.jdom2.test.util.UnitTestUtil.failNoException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.io.ByteArrayInputStream;
import java.io.CharArrayReader;
import java.io.CharArrayWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.CharBuffer;
import java.util.Arrays;
import java.util.List;

import org.junit.Ignore;
import org.junit.Test;
import org.xml.sax.Attributes;
import org.xml.sax.DTDHandler;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLFilter;
import org.xml.sax.XMLReader;
import org.xml.sax.ext.LexicalHandler;
import org.xml.sax.helpers.XMLFilterImpl;

import org.jdom2.Content;
import org.jdom2.DefaultJDOMFactory;
import org.jdom2.Document;
import org.jdom2.EntityRef;
import org.jdom2.JDOMConstants;
import org.jdom2.JDOMException;
import org.jdom2.JDOMFactory;
import org.jdom2.UncheckedJDOMFactory;
import org.jdom2.input.SAXBuilder;
import org.jdom2.input.sax.BuilderErrorHandler;
import org.jdom2.input.sax.SAXEngine;
import org.jdom2.input.sax.SAXHandler;
import org.jdom2.input.sax.SAXHandlerFactory;
import org.jdom2.input.sax.XMLReaderJDOMFactory;
import org.jdom2.input.sax.XMLReaderSAX2Factory;
import org.jdom2.input.sax.XMLReaders;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;
import org.jdom2.test.util.FidoFetch;
import org.jdom2.test.util.UnitTestUtil;


@SuppressWarnings("javadoc")
public final class TestSAXBuilder {
	
	private static final String testxml = "<?xml version=\"1.0\"?><root/>";
	private static final String testpattern = "\\s*<\\?xml\\s+version=\"1.0\"\\s+encoding=\"UTF-8\"\\s*\\?>\\s*<root\\s*/>\\s*";

	private class MySAXBuilder extends SAXBuilder {
		public MySAXBuilder() {
			super();
		}
		
		@SuppressWarnings("deprecation")
		public MySAXBuilder(String driver) {
			super(driver);
		}
		
		public MySAXBuilder(XMLReaderJDOMFactory fac) {
			super(fac);
		}
		
		/**
		 * This sets and configures the parser (SAXBuilder just sets).
		 */
		@Override
		public XMLReader createParser() throws JDOMException {
			XMLReader reader = super.createParser();
			configureParser(reader, new SAXHandler());
			return reader;
		}
	}
	
	@SuppressWarnings("deprecation")
	@Test
	public void testSAXBuilder() {
		SAXBuilder sb = new SAXBuilder();
		assertNull(sb.getDriverClass());
		assertTrue(sb.getEntityResolver() == null);
		assertTrue(sb.getDTDHandler() == null);
		assertTrue(sb.getXMLFilter() == null);
		assertFalse(sb.isValidating());
		assertTrue(sb.getExpandEntities());		
	}

	@SuppressWarnings("deprecation")
	@Test
	public void testSAXBuilderBooleanFalse() {
		SAXBuilder sb = new SAXBuilder(false);
		assertNull(sb.getDriverClass());
		assertTrue(sb.getEntityResolver() == null);
		assertTrue(sb.getDTDHandler() == null);
		assertTrue(sb.getXMLFilter() == null);
		assertFalse(sb.isValidating());
		assertTrue(sb.getExpandEntities());		
	}

	@SuppressWarnings("deprecation")
	@Test
	public void testSAXBuilderBooleanTrue() {
		SAXBuilder sb = new SAXBuilder(true);
		assertNull(sb.getDriverClass());
		assertTrue(sb.getEntityResolver() == null);
		assertTrue(sb.getDTDHandler() == null);
		assertTrue(sb.getXMLFilter() == null);
		assertTrue(sb.isValidating());
		assertTrue(sb.getExpandEntities());		
	}

	@SuppressWarnings("deprecation")
	@Test
	public void testSAXBuilderString() {
		MySAXBuilder sb = new MySAXBuilder("org.apache.xerces.parsers.SAXParser");
		assertEquals("org.apache.xerces.parsers.SAXParser", sb.getDriverClass());
		assertTrue(sb.getEntityResolver() == null);
		assertTrue(sb.getDTDHandler() == null);
		assertTrue(sb.getXMLFilter() == null);
		assertFalse(sb.isValidating());
		assertTrue(sb.getExpandEntities());		
		try {
			XMLReader reader = sb.createParser();
			assertNotNull(reader);
			assertEquals("org.apache.xerces.parsers.SAXParser", reader.getClass().getName());
			
		} catch (Exception e) {
			e.printStackTrace();
			fail("Could not create parser: " + e.getMessage());
		}

		sb = new MySAXBuilder("com.sun.org.apache.xerces.internal.parsers.SAXParser");
		assertEquals("com.sun.org.apache.xerces.internal.parsers.SAXParser", sb.getDriverClass());
		assertTrue(sb.getEntityResolver() == null);
		assertTrue(sb.getDTDHandler() == null);
		assertTrue(sb.getXMLFilter() == null);
		assertFalse(sb.isValidating());
		assertTrue(sb.getExpandEntities());		
		try {
			XMLReader reader = sb.createParser();
			assertNotNull(reader);
			assertTrue(reader.getClass().getName().equals("com.sun.org.apache.xerces.internal.parsers.SAXParser"));
			
		} catch (Exception e) {
			e.printStackTrace();
			fail("Could not create parser: " + e.getMessage());
		}
	}

	@SuppressWarnings("deprecation")
	@Test
	public void testSAXBuilderStringNew() {
		XMLReaderSAX2Factory fac = new XMLReaderSAX2Factory(false, "org.apache.xerces.parsers.SAXParser");
		MySAXBuilder sb = new MySAXBuilder(fac);
		assertEquals("org.apache.xerces.parsers.SAXParser", sb.getDriverClass());
		assertTrue(sb.getEntityResolver() == null);
		assertTrue(sb.getDTDHandler() == null);
		assertTrue(sb.getXMLFilter() == null);
		assertFalse(sb.isValidating());
		assertTrue(sb.getExpandEntities());		
		try {
			XMLReader reader = sb.createParser();
			assertNotNull(reader);
			assertEquals("org.apache.xerces.parsers.SAXParser", reader.getClass().getName());
			
		} catch (Exception e) {
			e.printStackTrace();
			fail("Could not create parser: " + e.getMessage());
		}

		fac = new XMLReaderSAX2Factory(false, "com.sun.org.apache.xerces.internal.parsers.SAXParser");
		sb = new MySAXBuilder("com.sun.org.apache.xerces.internal.parsers.SAXParser");
		assertEquals("com.sun.org.apache.xerces.internal.parsers.SAXParser", sb.getDriverClass());
		assertTrue(sb.getEntityResolver() == null);
		assertTrue(sb.getDTDHandler() == null);
		assertTrue(sb.getXMLFilter() == null);
		assertFalse(sb.isValidating());
		assertTrue(sb.getExpandEntities());		
		try {
			XMLReader reader = sb.createParser();
			assertNotNull(reader);
			assertTrue(reader.getClass().getName().equals("com.sun.org.apache.xerces.internal.parsers.SAXParser"));
			
		} catch (Exception e) {
			e.printStackTrace();
			fail("Could not create parser: " + e.getMessage());
		}
	}

	@SuppressWarnings("deprecation")
	@Test
	public void testSAXBuilderStringTrue() {
		SAXBuilder sb = new SAXBuilder("org.apache.xerces.parsers.SAXParser", true);
		assertEquals("org.apache.xerces.parsers.SAXParser", sb.getDriverClass());
		assertTrue(sb.getEntityResolver() == null);
		assertTrue(sb.getDTDHandler() == null);
		assertTrue(sb.getXMLFilter() == null);
		assertTrue(sb.getValidation());
		assertTrue(sb.getExpandEntities());
	}

	@SuppressWarnings("deprecation")
	@Test
	public void testSAXBuilderStringFalse() {
		SAXBuilder sb = new SAXBuilder("org.apache.xerces.parsers.SAXParser", false);
		assertEquals("org.apache.xerces.parsers.SAXParser", sb.getDriverClass());
		assertTrue(sb.getEntityResolver() == null);
		assertTrue(sb.getDTDHandler() == null);
		assertTrue(sb.getXMLFilter() == null);
		assertFalse(sb.isValidating());
		assertTrue(sb.getExpandEntities());
	}

	@SuppressWarnings("deprecation")
	@Test
	public void testGetJDOMFactory() {
		SAXBuilder sb = new SAXBuilder(true);
		assertNull(sb.getDriverClass());
		assertTrue(sb.getEntityResolver() == null);
		assertTrue(sb.getDTDHandler() == null);
		assertTrue(sb.getXMLFilter() == null);
		assertTrue(sb.isValidating());
		assertTrue(sb.getExpandEntities());
		assertTrue(sb.getJDOMFactory() instanceof DefaultJDOMFactory);
		assertTrue(sb.getJDOMFactory() == sb.getFactory());
	}

	@SuppressWarnings("deprecation")
	@Test
	public void testSetJDOMFactory() throws JDOMException {
		SAXBuilder sb = new SAXBuilder(true);
		assertNull(sb.getDriverClass());
		assertTrue(sb.getEntityResolver() == null);
		assertTrue(sb.getDTDHandler() == null);
		assertTrue(sb.getXMLFilter() == null);
		assertTrue(sb.getValidation());
		assertTrue(sb.getExpandEntities());
		JDOMFactory fac = sb.getJDOMFactory();
		assertTrue(fac instanceof DefaultJDOMFactory);
		UncheckedJDOMFactory udf = new UncheckedJDOMFactory();
		sb.setJDOMFactory(udf);
		assertTrue(sb.getJDOMFactory() == udf);
		assertTrue(sb.buildEngine().getJDOMFactory() == udf);
		sb.setFactory(fac);
		assertTrue(sb.getJDOMFactory() == fac);
		assertTrue(sb.buildEngine().getJDOMFactory() == fac);
	}

	@Test
	public void testGetSAXHandlerFactory() {
		SAXBuilder sb = new SAXBuilder();
		assertTrue(sb.getSAXHandlerFactory() != null);
	}

	@SuppressWarnings("deprecation")
	@Test
	public void testSetSAXHandlerFactory() {
		SAXBuilder sb = new SAXBuilder(true);
		SAXHandlerFactory fac = sb.getSAXHandlerFactory();
		sb.setSAXHandlerFactory(null);
		assertTrue(fac == sb.getSAXHandlerFactory());
		SAXHandlerFactory fbee = new SAXHandlerFactory() {
			@Override
			public SAXHandler createSAXHandler(JDOMFactory factory) {
				return new SAXHandler();
			}
		};
		sb.setSAXHandlerFactory(fbee);
		assertTrue(fbee == sb.getSAXHandlerFactory());
		sb.setSAXHandlerFactory(null);
		assertTrue(fac == sb.getSAXHandlerFactory());
	}

	@SuppressWarnings("deprecation")
	@Test
	public void testSetValidation() {
		SAXBuilder sb = new SAXBuilder(true);
		assertNull(sb.getDriverClass());
		assertTrue(sb.getEntityResolver() == null);
		assertTrue(sb.getDTDHandler() == null);
		assertTrue(sb.getXMLFilter() == null);
		assertTrue(sb.getValidation());
		assertTrue(sb.isValidating());
		assertTrue(sb.getExpandEntities());
		
		sb.setValidation(false);
		assertFalse(sb.getValidation());
		assertFalse(sb.isValidating());
		
		sb.setValidation(true);
		assertTrue(sb.getValidation());
		assertTrue(sb.isValidating());

	}

	@SuppressWarnings("deprecation")
	@Test
	public void testGetErrorHandler() throws JDOMException {
		SAXBuilder sb = new SAXBuilder(true);
		assertTrue(sb.getEntityResolver() == null);
		assertTrue(sb.getErrorHandler() == null);
		assertTrue(sb.getDTDHandler() == null);
		assertTrue(sb.getXMLFilter() == null);
		assertTrue(sb.isValidating());
		assertTrue(sb.getExpandEntities());
		
		assertTrue(sb.buildEngine().getErrorHandler() instanceof BuilderErrorHandler);
		
		ErrorHandler handler = new BuilderErrorHandler();
		
		sb.setErrorHandler(handler);
		assertTrue(handler == sb.getErrorHandler());		
		assertTrue(handler == sb.buildEngine().getErrorHandler());
	}

	@SuppressWarnings("deprecation")
	@Test
	public void testGetEntityResolver() {
		SAXBuilder sb = new SAXBuilder(true);
		assertTrue(sb.getEntityResolver() == null);
		assertTrue(sb.getErrorHandler() == null);
		assertTrue(sb.getDTDHandler() == null);
		assertTrue(sb.getXMLFilter() == null);
		assertTrue(sb.isValidating());
		assertTrue(sb.getExpandEntities());

		EntityResolver er = new EntityResolver() {
			@Override
			public InputSource resolveEntity(String arg0, String arg1) {
				return null;
			}
		};
		
		sb.setEntityResolver(er);
		assertTrue(er == sb.getEntityResolver());		
	}

	@SuppressWarnings("deprecation")
	@Test
	public void testGetDTDHandler() {
		SAXBuilder sb = new SAXBuilder(true);
		assertTrue(sb.getEntityResolver() == null);
		assertTrue(sb.getErrorHandler() == null);
		assertTrue(sb.getDTDHandler() == null);
		assertTrue(sb.getXMLFilter() == null);
		assertTrue(sb.isValidating());
		assertTrue(sb.getExpandEntities());

		DTDHandler dtd = new DTDHandler() {
			@Override
			public void notationDecl(String arg0, String arg1, String arg2)
					throws SAXException {
				// do nothing
			}
			@Override
			public void unparsedEntityDecl(String arg0, String arg1,
					String arg2, String arg3) throws SAXException {
				// do nothing
			}
		};
		
		sb.setDTDHandler(dtd);
		assertTrue(dtd == sb.getDTDHandler());		
	}
	
	@Test
	public void testGetSetXMLReaderFactory() {
		SAXBuilder sb = new SAXBuilder();
		XMLReaderJDOMFactory xrjf = sb.getXMLReaderFactory();
		assertTrue(xrjf == XMLReaders.NONVALIDATING);
		sb.setXMLReaderFactory(XMLReaders.XSDVALIDATING);
		assertTrue(sb.getXMLReaderFactory() == XMLReaders.XSDVALIDATING);
		sb.setXMLReaderFactory(null);
		assertTrue(xrjf == XMLReaders.NONVALIDATING);
	}

	@Test
	public void testXMLFilter() {
		MySAXBuilder sb = new MySAXBuilder();
		assertTrue(sb.getEntityResolver() == null);
		assertTrue(sb.getErrorHandler() == null);
		assertTrue(sb.getDTDHandler() == null);
		assertTrue(sb.getXMLFilter() == null);
		assertTrue(sb.getExpandEntities());

		XMLFilter filter = new XMLFilterImpl() {
			@Override
			public void startElement(String arg0, String arg1, String arg2,
					Attributes arg3) throws SAXException {
				super.startElement(arg0, "f" + arg1, arg2, arg3);
			}
			@Override
			public void endElement(String arg0, String arg1, String arg2) throws SAXException {
				super.endElement(arg0, "f" + arg1, arg2);
			}
		};
		
		XMLFilter gilter = new XMLFilterImpl() {
			@Override
			public void startElement(String arg0, String arg1, String arg2,
					Attributes arg3) throws SAXException {
				super.startElement(arg0, "g" + arg1, arg2, arg3);
			}
			@Override
			public void endElement(String arg0, String arg1, String arg2) throws SAXException {
				super.endElement(arg0, "g" + arg1, arg2);
			}
		};
		
		filter.setParent(gilter);
		sb.setXMLFilter(filter);
		assertTrue(filter == sb.getXMLFilter());
		
		try {
			Document doc = sb.build(new CharArrayReader(testxml.toCharArray()));
			assertTrue(doc.hasRootElement());
			assertEquals("fgroot", doc.getRootElement().getName());
		} catch (Exception e) {
			e.printStackTrace();
			fail("Could not parse XML " + testxml + ": " + e.getMessage());
		}
		
	}

	@SuppressWarnings("deprecation")
	@Test
	public void testGetIgnoringElementContentWhitespace() throws JDOMException {
		SAXBuilder sb = new SAXBuilder(true);
		assertNull(sb.getDriverClass());
		assertTrue(sb.getEntityResolver() == null);
		assertTrue(sb.getErrorHandler() == null);
		assertTrue(sb.getDTDHandler() == null);
		assertTrue(sb.getXMLFilter() == null);
		assertTrue(sb.isValidating());
		assertTrue(sb.getExpandEntities());
		
		SAXEngine se = sb.buildEngine();
		assertFalse(se.getIgnoringBoundaryWhitespace());
		
		sb.setIgnoringElementContentWhitespace(true);
		assertTrue(sb.getIgnoringElementContentWhitespace());		
		se = sb.buildEngine();
		assertTrue(se.getIgnoringElementContentWhitespace());
		sb.setIgnoringElementContentWhitespace(false);
		assertFalse(sb.getIgnoringElementContentWhitespace());		
		se = sb.buildEngine();
		assertFalse(se.getIgnoringElementContentWhitespace());
	}

	@SuppressWarnings("deprecation")
	@Test
	public void testGetIgnoringBoundaryWhitespace() throws JDOMException {
		SAXBuilder sb = new SAXBuilder(true);
		SAXEngine se = sb.buildEngine();
		assertNull(sb.getDriverClass());
		assertTrue(sb.getEntityResolver() == null);
		assertTrue(sb.getErrorHandler() == null);
		assertTrue(sb.getDTDHandler() == null);
		assertTrue(sb.getXMLFilter() == null);
		assertTrue(sb.isValidating());
		assertTrue(sb.getExpandEntities());
		assertTrue(se.getEntityResolver() == null);
		assertTrue(se.getErrorHandler() != null);
		assertTrue(se.getDTDHandler() != null);
		assertTrue(se.isValidating());
		assertTrue(se.getExpandEntities());
		sb.setIgnoringBoundaryWhitespace(true);
		assertTrue(sb.getIgnoringBoundaryWhitespace());		
		se = sb.buildEngine();
		assertTrue(se.getIgnoringBoundaryWhitespace());		
		sb.setIgnoringBoundaryWhitespace(false);
		assertFalse(sb.getIgnoringBoundaryWhitespace());
		
		se = sb.buildEngine();
		assertFalse(se.getIgnoringBoundaryWhitespace());
	}

	@SuppressWarnings("deprecation")
	@Test
	public void testGetReuseParser() {
		SAXBuilder sb = new SAXBuilder(true);
		assertTrue(sb.getEntityResolver() == null);
		assertTrue(sb.getErrorHandler() == null);
		assertTrue(sb.getDTDHandler() == null);
		assertTrue(sb.getXMLFilter() == null);
		assertTrue(sb.isValidating());
		assertTrue(sb.getExpandEntities());
		
		sb.setReuseParser(true);
		assertTrue(sb.getReuseParser());		
		sb.setReuseParser(false);
		assertFalse(sb.getReuseParser());		
	}

	@Test
	public void testCreateParser() {
		MySAXBuilder sb = new MySAXBuilder();
		try {
			XMLReader reader = sb.createParser();
			assertNotNull(reader);
		} catch (JDOMException e) {
			e.printStackTrace();
			fail("Could not create parser: " + e.getMessage());
		}
	}

	@Test
	public void testSetFeature() {
		String feature = "http://javax.xml.XMLConstants/feature/secure-processing";
		MySAXBuilder sb = new MySAXBuilder();
		try {
			sb.setFeature(feature, true);
			XMLReader reader = sb.createParser();
			assertNotNull(reader);
			assertTrue(reader.getFeature(feature));
			sb.setFeature(feature, false);
			reader = sb.createParser();
			assertNotNull(reader);
			assertFalse(reader.getFeature(feature));
			
		} catch (Exception e) {
			e.printStackTrace();
			fail("Could not create parser: " + e.getMessage());
		}
	}

	@Test
	public void testSetExternalFeature() {
		String feature = "http://xml.org/sax/features/external-general-entities";
		MySAXBuilder sb = new MySAXBuilder();
		try {
			sb.setFeature(feature, true);
			XMLReader reader = sb.createParser();
			assertNotNull(reader);
			assertTrue(reader.getFeature(feature));
			assertNull(reader.getProperty(JDOMConstants.SAX_PROPERTY_DECLARATION_HANDLER));
			sb.setFeature(feature, false);
			reader = sb.createParser();
			assertNotNull(reader);
			assertFalse(reader.getFeature(feature));
			assertNotNull(reader.getProperty(JDOMConstants.SAX_PROPERTY_DECLARATION_HANDLER));
		} catch (Exception e) {
			e.printStackTrace();
			fail("Could not create parser: " + e.getMessage());
		}
	}

	@Test
	public void testSetProperty() {
		LexicalHandler lh = new LexicalHandler() {
			@Override
			public void startEntity(String arg0) throws SAXException {
				// Do nothing;
			}
			@Override
			public void startDTD(String arg0, String arg1, String arg2)
					throws SAXException {
				// Do nothing;
			}
			@Override
			public void startCDATA() throws SAXException {
				// Do nothing;
			}
			@Override
			public void endEntity(String arg0) throws SAXException {
				// Do nothing;
			}
			
			@Override
			public void endDTD() throws SAXException {
				// Do nothing;
			}
			
			@Override
			public void endCDATA() throws SAXException {
				// Do nothing;
			}
			
			@Override
			public void comment(char[] arg0, int arg1, int arg2) throws SAXException {
				// Do nothing;
			}
		};
		
		MySAXBuilder sb = new MySAXBuilder();
		String propname = "http://xml.org/sax/properties/lexical-handler";
		try {
			sb.setProperty(propname, lh);
			XMLReader reader = sb.createParser();
			assertNotNull(reader);
			assertTrue(lh == reader.getProperty(propname));
		} catch (Exception e) {
			e.printStackTrace();
			fail("Could not create parser: " + e.getMessage());
		}
		sb.setProperty("http://java.sun.com/xml/jaxp/properties/schemaLanguage", "XMLSchema");

	}

	@Test
	public void testSetPropertyTwo() {
		MySAXBuilder sb = new MySAXBuilder();
		try {
			sb.setProperty("http://java.sun.com/xml/jaxp/properties/schemaLanguage", "XMLSchema");
			sb.createParser();
			fail("Should not be able to set the property");
		} catch (JDOMException jde) {
			// good
		} catch (Exception e) {
			e.printStackTrace();
			fail("Could not create parser: " + e.getMessage());
		}

	}

    /**
     * Test that when setExpandEntities is true, enties are
     * always expanded and when false, entities declarations
     * are added to the DocType
     */
    @Test
    public void test_TCM__void_setExpandEntities_boolean() throws JDOMException, IOException {
        //test entity exansion on internal entity
    	
    	URL src = FidoFetch.getFido().getURL("/SAXBuilderTestEntity.xml");

        SAXBuilder builder = new SAXBuilder();

        builder.setExpandEntities(true);
        assertTrue(builder.getExpandEntities());
        
        Document doc = builder.build(src);
        assertTrue("didn't get entity text", doc.getRootElement().getText().indexOf("simple entity") == 0);
        assertTrue("didn't get entity text", doc.getRootElement().getText().indexOf("another simple entity") > 1);        	

        //test that entity declaration appears in doctype
        //and EntityRef is created in content with internal entity
        builder.setExpandEntities(false);
        assertFalse(builder.getExpandEntities());

        doc = builder.build(src);
        assertTrue("got entity text", ! (doc.getRootElement().getText().indexOf("simple entity") > 1));
        assertTrue("got entity text", ! (doc.getRootElement().getText().indexOf("another simple entity") > 1));        	
		List<Content> content = doc.getRootElement().getContent();
		assertTrue("didn't get EntityRef for unexpanded entities",
			content.get(0) instanceof EntityRef);
		assertTrue("didn't get EntityRef for unexpanded entities",
			content.get(2) instanceof EntityRef);
		
        //test entity expansion on external entity
        URL src2 = FidoFetch.getFido().getURL("/SAXBuilderTestEntity2.xml");

        builder.setExpandEntities(true);
        assertTrue(builder.getExpandEntities());

        doc = builder.build(src2);
        assertTrue("didn't get entity text", doc.getRootElement().getText().indexOf("simple entity") == 0);
        assertTrue("didn't get entity text", doc.getRootElement().getText().indexOf("another simple entity") > 1);        	

        //test that entity declaration appears in doctype
        //and EntityRef is created in content with external entity
        builder.setExpandEntities(false);
        assertFalse(builder.getExpandEntities());
        doc = builder.build(src2);
        assertTrue("got entity text", ! (doc.getRootElement().getText().indexOf("simple entity") > 1));
        assertTrue("got entity text", ! (doc.getRootElement().getText().indexOf("another simple entity") > 1));        	
		content = doc.getRootElement().getContent();
		assertTrue("didn't get EntityRef for unexpanded entities",
			content.get(0) instanceof EntityRef);
		assertTrue("didn't get EntityRef for unexpanded entities",
			content.get(2) instanceof EntityRef);



    }

    /**
     * Test that when setExpandEntities is true, enties are
     * always expanded and when false, entities declarations
     * are added to the DocType
     */
    @Test
    public void test_TCU__DTDComments() throws JDOMException, IOException {
        //test entity exansion on internal entity

        SAXBuilder builder = new SAXBuilder();
        //test entity expansion on external entity
        URL file = FidoFetch.getFido().getURL("/SAXBuilderTestDecl.xml");

       //test that entity declaration appears in doctype
        //and EntityRef is created in content with external entity
        builder.setExpandEntities(false);
        Document doc = builder.build(file);

        assertTrue("did not get internal subset comments correctly", doc.getDocType().getInternalSubset().indexOf("foo") > 0);
		//assertTrue("didn't get EntityRef for unexpanded attribute entities",
		//	doc.getRootElement().getAttribute("test").getValue().indexOf("&simple") == 0);
		


    }

    /**
     * Test that when setExpandEntities is true, enties are
     * always expanded and when false, entities declarations
     * are added to the DocType
     */
    @Test
    public void test_TCU__InternalAndExternalEntities() throws JDOMException, IOException {
        //test entity exansion on internal entity

        SAXBuilder builder = new SAXBuilder();
        //test entity expansion on internal and external entity
        URL file = FidoFetch.getFido().getURL("/SAXBuilderTestIntExtEntity.xml");

        builder.setExpandEntities(true);
        Document doc = builder.build(file);
        assertTrue("didn't get internal entity text", doc.getRootElement().getText().indexOf("internal") >= 0);
        assertTrue("didn't get external entity text", doc.getRootElement().getText().indexOf("external") > 0);
		//the internal subset should be empty since entity expansion is off
		assertTrue("invalid characters in internal subset", doc.getDocType().getInternalSubset().length() == 0);
		assertTrue("incorrectly got entity declaration in internal subset for internal entity", 
			doc.getDocType().getInternalSubset().indexOf("internal") < 0);
		assertTrue("incorrectly got external entity declaration in internal subset", 
			doc.getDocType().getInternalSubset().indexOf("external") < 0);
		assertTrue("incorrectly got external entity declaration in internal subset", 
			doc.getDocType().getInternalSubset().indexOf("ldquo") < 0);
        //test that local entity declaration appears in internal subset
        //and EntityRef is created in content with external entity
        builder.setExpandEntities(false);
        doc = builder.build(file);
    	
		EntityRef internal = (EntityRef)doc.getRootElement().getContent().get(0);
		EntityRef external = (EntityRef)doc.getRootElement().getContent().get(6);
		assertNotNull("didn't get EntityRef for unexpanded internal entity", internal);
		assertNotNull("didn't get EntityRef for unexpanded external entity", external);
		assertTrue("didn't get local entity declaration in internal subset", 
			doc.getDocType().getInternalSubset().indexOf("internal") > 0);
		assertTrue("incorrectly got external entity declaration in internal subset", 
			doc.getDocType().getInternalSubset().indexOf("external") < 0);
		assertTrue("incorrectly got external entity declaration in internal subset", 
			doc.getDocType().getInternalSubset().indexOf("ldquo") < 0);
    }
    
    @Ignore
    @Test
    public void test_TCU__InternalSubset() throws JDOMException, IOException {
    
        SAXBuilder builder = new SAXBuilder();
        //test entity expansion on internal subset
        URL file = FidoFetch.getFido().getURL("/SAXBuilderTestEntity.xml");

        builder.setExpandEntities(true);
        Document doc = builder.build(file);
        String subset = doc.getDocType().getInternalSubset();
        assertEquals("didn't get correct internal subset when expand entities was on"
            , "  <!NOTATION n1 SYSTEM \"http://www.w3.org/\">\n  <!NOTATION n2 SYSTEM \"http://www.w3.org/\">\n  <!ENTITY anotation SYSTEM \"http://www.foo.org/image.gif\" NDATA n1>\n", 
            subset);
        //now do it with expansion off
        builder.setExpandEntities(false);
        doc = builder.build(file);
        String subset2 = doc.getDocType().getInternalSubset();
        final String expect = "<!NOTATION n2 SYSTEM \"http://www.w3.org/\">\n  <!ENTITY anotation SYSTEM \"http://www.foo.org/image.gif\" NDATA n1>\n";
        if (!expect.equals(subset2)) {
        	fail("didn't get correct internal subset when expand entities was off.\n" +
        			"Expect: " + expect + "\n" +
        			"Got:    " + subset2);
        }
    }

	@SuppressWarnings("deprecation")
	@Test
	public void testSetFastReconfigure() {
		SAXBuilder sb = new SAXBuilder(true);
		assertTrue(sb.getEntityResolver() == null);
		assertTrue(sb.getErrorHandler() == null);
		assertTrue(sb.getDTDHandler() == null);
		assertTrue(sb.getXMLFilter() == null);
		assertTrue(sb.isValidating());
		assertTrue(sb.getExpandEntities());
		
		sb.setFastReconfigure(true);
		
		// TODO - Now what?
	}
	
	private void assertXMLMatches(String baseuri, Document doc) {
		XMLOutputter out = new XMLOutputter(Format.getCompactFormat());
		try {
			CharArrayWriter caw = new CharArrayWriter();
			out.output(doc, caw);
			String output = caw.toString();
			if (!output.matches(testpattern)) {
				fail ("Failed to match output:\n  " + output + "\nwith pattern:\n  " + testpattern);
			}
		} catch (IOException e) {
			e.printStackTrace();
			UnitTestUtil.failException("Failed to write Document " + doc + " to CharArrayWriter.", e);
		}
		if (baseuri == null) {
			assertNull(doc.getBaseURI());
		} else {
			if (!baseuri.equals(doc.getBaseURI())) {
				try {
					final String moduri = baseuri.replaceFirst(":/", ":///");
					if (!moduri.equals(doc.getBaseURI())) {
						final String fileuri = new File(baseuri).toURI().toURL().toExternalForm();
						if (!fileuri.equals(doc.getBaseURI())) {
							final String modfileuri = fileuri.replaceFirst(":/", ":///");
							if (!modfileuri.equals(doc.getBaseURI())) {
								fail("Base URI " + doc.getBaseURI() + " is not one of " +
										Arrays.toString(new String[]{baseuri, moduri, fileuri, modfileuri}));
							}
						}
					}
				} catch (MalformedURLException mue) {
					UnitTestUtil.failException("Could not create File URL", mue);
				}
			}
		}
	}
	
	@Test
	public void testBuildInputSource() {
		try {
			SAXBuilder sb = new SAXBuilder();
			InputSource is = null;
			is = new InputSource(new CharArrayReader(testxml.toCharArray()));
			assertXMLMatches(null, sb.build(is));
			is = new InputSource(new CharArrayReader(testxml.toCharArray()));
			assertXMLMatches(null, sb.build(is));
			sb.setReuseParser(false);
			is = new InputSource(new CharArrayReader(testxml.toCharArray()));
			assertXMLMatches(null, sb.build(is));
			is = new InputSource(new CharArrayReader(testxml.toCharArray()));
			assertXMLMatches(null, sb.build(is));
			
			is = new InputSource(new CharArrayReader(testxml.toCharArray()));
			assertXMLMatches(null, sb.buildEngine().build(is));
		} catch (Exception e) {
			e.printStackTrace();
			fail("Failed to parse document: " + e.getMessage());
		}
	}

	@Test
	public void testBuildInputStream() {
		byte[] bytes = testxml.getBytes();
		try {
			SAXBuilder sb = new SAXBuilder();
			assertXMLMatches(null, sb.build(new ByteArrayInputStream(bytes)));
			assertXMLMatches(null, sb.build(new ByteArrayInputStream(bytes)));
			sb.setReuseParser(false);
			assertXMLMatches(null, sb.build(new ByteArrayInputStream(bytes)));
			assertXMLMatches(null, sb.build(new ByteArrayInputStream(bytes)));
			
			assertXMLMatches(null, sb.buildEngine().build(new ByteArrayInputStream(bytes)));
		} catch (Exception e) {
			e.printStackTrace();
			fail("Failed to parse document: " + e.getMessage());
		}
	}

	@Test
	public void testBuildFile() {
		File tmp = null;
		try {
			tmp = File.createTempFile("tst", ".xml");
			tmp.deleteOnExit();
			FileWriter fw = new FileWriter(tmp);
			fw.write(testxml.toCharArray());
			fw.flush();
			fw.close();
			SAXBuilder sb = new SAXBuilder();
			assertXMLMatches(tmp.toURI().toString(), sb.build(tmp));
			assertXMLMatches(tmp.toURI().toString(), sb.build(tmp));
			sb.setReuseParser(false);
			assertXMLMatches(tmp.toURI().toString(), sb.build(tmp));
			assertXMLMatches(tmp.toURI().toString(), sb.build(tmp));
			assertXMLMatches(tmp.toURI().toString(), sb.buildEngine().build(tmp));

		} catch (Exception e) {
			e.printStackTrace();
			fail("Failed to write/parse document to file '" + tmp + "': " + e.getMessage());
		} finally {
			if (tmp != null) {
				tmp.delete();
			}
		}
	}

	@Test
	public void testBuildURL() {
		File tmp = null;
		try {
			tmp = File.createTempFile("tst", ".xml");
			tmp.deleteOnExit();
			FileWriter fw = new FileWriter(tmp);
			fw.write(testxml.toCharArray());
			fw.flush();
			fw.close();
			SAXBuilder sb = new SAXBuilder();
			assertXMLMatches(tmp.toURI().toString(), sb.build(tmp.toURI().toURL()));
			assertXMLMatches(tmp.toURI().toString(), sb.build(tmp.toURI().toURL()));
			sb.setReuseParser(false);
			assertXMLMatches(tmp.toURI().toString(), sb.build(tmp.toURI().toURL()));
			assertXMLMatches(tmp.toURI().toString(), sb.build(tmp.toURI().toURL()));
			
			assertXMLMatches(tmp.toURI().toString(), sb.buildEngine().build(tmp.toURI().toURL()));
		} catch (Exception e) {
			e.printStackTrace();
			fail("Failed to write/parse document to file '" + tmp + "': " + e.getMessage());
		} finally {
			if (tmp != null) {
				tmp.delete();
			}
		}
	}

	@Test
	public void testBuildInputStreamString() {
		byte[] bytes = testxml.getBytes();
		
		try {
			SAXBuilder sb = new SAXBuilder();
			assertXMLMatches("baseID",
					sb.build(new ByteArrayInputStream(bytes), "baseID"));
			assertXMLMatches("baseID",
					sb.build(new ByteArrayInputStream(bytes), "baseID"));
			sb.setReuseParser(false);
			assertXMLMatches("baseID",
					sb.build(new ByteArrayInputStream(bytes), "baseID"));
			assertXMLMatches("baseID",
					sb.build(new ByteArrayInputStream(bytes), "baseID"));
			assertXMLMatches("baseID",
					sb.buildEngine().build(new ByteArrayInputStream(bytes), "baseID"));
		} catch (Exception e) {
			e.printStackTrace();
			UnitTestUtil.failException("Failed to parse document: " + e.getMessage(), e);
		}
	}

	@Test
	public void testBuildReader() {
		char[] chars = testxml.toCharArray();
		try {
			SAXBuilder sb = new SAXBuilder();
			assertXMLMatches(null, sb.build(new CharArrayReader(chars)));
			assertXMLMatches(null, sb.build(new CharArrayReader(chars)));
			sb.setReuseParser(false);
			assertXMLMatches(null, sb.build(new CharArrayReader(chars)));
			assertXMLMatches(null, sb.build(new CharArrayReader(chars)));

			assertXMLMatches(null, sb.buildEngine().build(new CharArrayReader(chars)));
		} catch (Exception e) {
			e.printStackTrace();
			UnitTestUtil.failException("Failed to parse document: " + e.getMessage(), e);
		}
	}

	@Test
	public void testBuildReaderString() {
		char[] chars = testxml.toCharArray();
		try {
			SAXBuilder sb = new SAXBuilder();
			assertXMLMatches("baseID",
					sb.build(new CharArrayReader(chars), "baseID"));
			assertXMLMatches("baseID",
					sb.build(new CharArrayReader(chars), "baseID"));
			sb.setReuseParser(false);
			assertXMLMatches("baseID",
					sb.build(new CharArrayReader(chars), "baseID"));
			assertXMLMatches("baseID",
					sb.build(new CharArrayReader(chars), "baseID"));
			
			assertXMLMatches("baseID",
					sb.buildEngine().build(new CharArrayReader(chars), "baseID"));
		} catch (Exception e) {
			e.printStackTrace();
			fail("Failed to parse document: " + e.getMessage());
		}
	}

	@Test
	public void testBuildString() {
		File tmp = null;
		try {
			tmp = File.createTempFile("tst", ".xml");
			tmp.deleteOnExit();
			FileWriter fw = new FileWriter(tmp);
			fw.write(testxml.toCharArray());
			fw.flush();
			fw.close();
			SAXBuilder sb = new SAXBuilder();
			assertXMLMatches(tmp.getCanonicalFile().toURI().toURL().toString(), 
					sb.build(tmp.toString()));
			assertXMLMatches(tmp.getCanonicalFile().toURI().toURL().toString(), 
					sb.build(tmp.toString()));
			sb.setReuseParser(false);
			assertXMLMatches(tmp.getCanonicalFile().toURI().toURL().toString(), 
					sb.build(tmp.toString()));
			assertXMLMatches(tmp.getCanonicalFile().toURI().toURL().toString(), 
					sb.build(tmp.toString()));

			assertXMLMatches(tmp.getCanonicalFile().toURI().toURL().toString(), 
					sb.buildEngine().build(tmp.toString()));
		} catch (Exception e) {
			e.printStackTrace();
			fail("Failed to write/parse document to file '" + tmp + "': " + e.getMessage());
		} finally {
			if (tmp != null) {
				tmp.delete();
			}
		}
	}
	
	@Test
	public void testBuildStringNegativeNull() {
		SAXBuilder sb = new SAXBuilder();
		try {
			String n = null;
			sb.build(n);
			failNoException(NullPointerException.class);
		} catch (Exception e) {
			checkException(NullPointerException.class, e);
		}
	}
	
	@Test
	public void testBuildStringNegativeBadURI() {
		SAXBuilder sb = new SAXBuilder();
		try {
			sb.build(" `!@#$%^&*() is not a valid URI ");
			failNoException(MalformedURLException.class);
		} catch (Exception e) {
			checkException(MalformedURLException.class, e);
			if (e.getCause() != null) {
				assertFalse(e.getCause() instanceof MalformedURLException);
			}
		}
	}

	@Test
    public void testXMLNamesVariants() throws JDOMException, IOException {
        String toparse = "<xMl:xml xmlns:xMl='MyURI' xml='xml'/>";
        
        SAXBuilder sb = new SAXBuilder();
        Document doc = sb.build(new CharArrayReader(toparse.toCharArray()));
        
        assertEquals("Should match:   xml", "xml" , doc.getRootElement().getAttributeValue("xml"));
        
        
    }
	
	@Test
	public void testBuildStringNegativeActualXML() {
		SAXBuilder sb = new SAXBuilder();
		try {
			sb.build("<root />");
			failNoException(IOException.class);
		} catch (Exception e) {
			checkException(IOException.class, e);
			// cause should also be a MalformedURLException
			checkException(IOException.class, e.getCause());
		}
	}
	
	@Test
	public void testBuildStringNegativePaddedXML() {
		SAXBuilder sb = new SAXBuilder();
		try {
			sb.build("   <!-- comment -->  ");
			failNoException(IOException.class);
		} catch (Exception e) {
			checkException(IOException.class, e);
			checkException(IOException.class, e.getCause());
		}
	}
	
	@Test
	public void testSplitCDATAinCDATA() throws JDOMException, IOException {
	    // Note the ]]><![CDATA[ in the middle
	    String toparse = "<message><![CDATA[  expected:<[[D/0]]]]><![CDATA[> but was:<[null]>  ]]></message>";
	    
	    SAXBuilder sb = new SAXBuilder();
	    Document doc = sb.build(new CharArrayReader(toparse.toCharArray()));
	    
	    assertEquals("Should match:   expected:<[[D/0]]> but was:<[null]>", "  expected:<[[D/0]]> but was:<[null]>  " , doc.getRootElement().getValue());
	    
	    
	}
	
	@Test
	public void testParserFactory() throws JDOMException, IOException {
		if (System.getProperty("org.jdom2.performance") == null) {
			// for android.
			//Assume.assumeNotNull(System.getProperty("org.jdom2.performance"));
			return;
		}
		long start = 0L, time = 0L;
		loopParser(false, false);
		loopParser(false, false);
		loopParser(false, false);
		start = System.nanoTime();
		loopParser(false, false);
		time = System.nanoTime() - start;
		System.out.printf("SimpleLoop Recreate %.3fms\n", time / 1000000.0);
		
		loopParser(true, false);
		loopParser(true, false);
		loopParser(true, false);
		start = System.nanoTime();
		loopParser(true, false);
		time = System.nanoTime() - start;
		System.out.printf("SimpleLoop Reuse    %.3fms\n", time / 1000000.0);
		
		loopParser(true, true);
		loopParser(true, true);
		loopParser(true, true);
		start = System.nanoTime();
		loopParser(true, true);
		time = System.nanoTime() - start;
		System.out.printf("SimpleLoop Fast     %.3fms\n", time / 1000000.0);
	}
	
	
	@SuppressWarnings("deprecation")
	private void loopParser(boolean reuse, boolean fast) throws JDOMException, IOException {
		if (fast) {
			System.out.println("Fast no longer means anything.");
		}
		SAXBuilder builderval = new SAXBuilder(true);
		SAXBuilder buildernoval = new SAXBuilder(false);
		builderval.setReuseParser(reuse);
		buildernoval.setReuseParser(reuse);
		String docstr = 
				"<?xml version='1.0'?><!DOCTYPE root [  <!ELEMENT root  (#PCDATA)>]><root />";
		char[] chars = docstr.toCharArray();
		ResetReader rr = new ResetReader(chars);
		for (int i = 0; i < 10000; i++) {
			parseMem(builderval, rr);
			parseMem(buildernoval, rr);
		}
		//JAXPFastParserFactory.printTimes();
	}
	
	private void parseMem(SAXBuilder builder, ResetReader reader) throws JDOMException, IOException {
		reader.reset();
		Document doc = builder.build(reader);
		assertTrue(doc.hasRootElement());
		assertEquals("root", doc.getRootElement().getName());
	}
	
	private static final class ResetReader extends Reader {
		
		private final char[] chars;
		private int pos = 0;
		
		public ResetReader(final char[] ch) {
			chars = ch;
		}

		@Override
		public int read(final CharBuffer target) throws IOException {
			final int got = chars.length - pos;
			if (got == 0) {
				return -1;
			}
			final int howmuch = target.remaining();
			final int ret = got > howmuch ? howmuch : got;
			target.put(chars, pos, ret);
			pos += ret;
			return ret;
		}

		@Override
		public int read() throws IOException {
			if (pos >= chars.length) {
				return -1;
			}
			return chars[pos++];
		}

		@Override
		public int read(final char[] cbuf) throws IOException {
			final int got = chars.length - pos;
			if (got == 0) {
				return -1;
			}
			final int ret = got > cbuf.length ? cbuf.length : got;
			System.arraycopy(chars, pos, cbuf, 0, ret);
			pos += ret;
			return ret;
		}

		@Override
		public int read(final char[] cbuf, final int off, final int howmuch) throws IOException {
			final int got = chars.length - pos;
			if (got == 0) {
				return -1;
			}
			final int ret = got > howmuch ? howmuch : got;
			System.arraycopy(chars, pos, cbuf, off, ret);
			pos += ret;
			return ret;
		}

		@Override
		public long skip(final long n) throws IOException {
			long got = chars.length - pos;
			if (got > n) {
				pos += (int)n;
				return n;
			}
			long ret = chars.length - pos;
			pos = chars.length;
			return ret;
		}

		@Override
		public boolean ready() throws IOException {
			return true;
		}

		@Override
		public boolean markSupported() {
			return false;
		}

		@Override
		public void mark(final int readAheadLimit) throws IOException {
			return;
		}

		@Override
		public void reset() throws IOException {
			pos = 0;
		}

		@Override
		public void close() throws IOException {
			return;
		}
		
	}

}
