/* 
 * 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.io.StringReader;

/**
 * This class is for unit testing the ElementRule class.
 *
 * @see ElementRule
 * @author Elias Ross
 * @version 1.0
 */
public class ElementRuleTest
	extends junit.framework.TestCase
{
	public ElementRuleTest(String name) {
		super(name);
	}

	public static void main(String[] args) {
		junit.textui.TestRunner.run(ElementRuleTest.class);
	}

	static final Element A = new Element("a");
	static final Element B = new Element("b");
	static final Element C = new Element("c");
	static final Element D = new Element("d");
	static final Element E = new Element("e");

	static ElementReq createReq(String s)
		throws Exception
	{
		// need a space, because we get EOF premature
		Dtd dtd = new Dtd();
		dtd.addElementRule(A, new ElementRule());
		dtd.addElementRule(B, new ElementRule());
		dtd.addElementRule(C, new ElementRule());
		dtd.addElementRule(D, new ElementRule());
		dtd.addElementRule(E, new ElementRule());
		XmlReader r = new XmlReader(new StringReader(s + " "), dtd);
		return r.contentspec();
	}

	public void testSequenceParse()
		throws Exception
	{
		String s;
		ElementReq req;
		s = "(a, b, c)";
		// basic
		req = createReq(s);
		assertEquals(s, req.toString());
		// multi
		s = "(a+, b?, c*)";
		req = createReq(s);
		assertEquals(s, req.toString());
		// nested
		s = "((a, b))";
		req = createReq(s);
		assertEquals(s, req.toString());
		// deep
		s = "((a, b), (c, (d, (e, f)*)?)+)?";
		req = createReq(s);
		assertEquals(s, req.toString());
	}

	public void testAnyEmpty()
		throws Exception
	{
		String s;
		ElementReq req;
		s = "ANY";
		req = createReq(s);
		assertTrue("Any", req.isANY());
		// EMPTY
		s = "EMPTY";
		req = createReq(s);
		assertTrue("EMPTY", req.size() == 0);
	}

	public void testPCDATA()
		throws Exception
	{
		String s;
		ElementReq req;
		s = "(#PCDATA | a | b)*";
		req = createReq(s);
		assertTrue("PCDATA", req.isPCDATA());
		assertTrue("PCDATA choice", req.isChoice());
		assertTrue("PCDATA star", req.isStar());
		assertEquals("PCDATA string", s, req.toString());
		ElementRule rule = new ElementRule(req, null);
		ElementRule.ElementRuleState state = new ElementRule.ElementRuleState();
		rule.encounterEnd(state);
		try {
			s = "(#PCDATA | a | b)";
			req = createReq(s);
			fail("must have )*");
		} catch (XmlException e) {
		}
		try {
			s = "(#PCDATA , a , b)";
			req = createReq(s);
			fail("must not have comma");
		} catch (XmlException e) {
		}
		try {
			s = "(#PCDATA | (a | b) | c)";
			req = createReq(s);
			fail("must not have subparens");
		} catch (XmlException e) {
		}
	}

	public void testNormalize()
		throws Exception
	{
		String s;
		ElementReq req;
		// star one
		s = "(a | b*)";
		req = createReq(s);
		req.normalize();
		assertTrue("choice", req.isChoice());
		assertEquals("(a | b*)?", req.toString());
		// question
		s = "(a | b | c?)";
		req = createReq(s);
		req.normalize();
		assertEquals("(a | b | c?)?", req.toString());
		// sequence
		s = "(a?, b*)";
		req = createReq(s);
		req.normalize();
		assertEquals("(a?, b*)?", req.toString());
		// nested
		s = "((a?, b*))";
		req = createReq(s);
		req.normalize();
		assertEquals("((a?, b*)?)?", req.toString());
	}

	public void testChoiceParse()
		throws Exception
	{
		String s;
		ElementReq req;
		s = "(a | b | c)";
		// basic
		req = createReq(s);
		assertEquals(s, req.toString());
		// multi
		s = "(a+ | b? | c*)";
		req = createReq(s);
		assertEquals(s, req.toString());
		// nested
		s = "((a | b))";
		req = createReq(s);
		assertEquals(s, req.toString());
		// deep
		s = "((a | b) | (c | (d | (e | f)*)?)+)?";
		req = createReq(s);
		assertEquals(s, req.toString());
	}

	public void testDeep()
		throws Exception
	{
		String s;
		ElementReq req;
		s = "(((a)))";
		// basic
		req = createReq(s);
		assertEquals(s, req.toString());

		s = "(((a*)+)?)";
		// basic
		req = createReq(s);
		assertEquals(s, req.toString());
	}

	public void testEncounterSeq()
		throws Exception
	{
		String s;
		ElementReq req;
		s = "(a, b, c)";
		req = createReq(s);
		ElementRule rule = new ElementRule(req, null);
		ElementRule.ElementRuleState state = 
			new ElementRule.ElementRuleState();
		rule.encounterElement(A, state);
		rule.encounterElement(B, state);
		rule.encounterElement(C, state);
	}

	public void testEncounterEnds()
		throws Exception
	{
		String s;
		ElementReq req;
		s = "(a, b?, c*)";
		req = createReq(s);
		ElementRule rule = new ElementRule(req, null);
		ElementRule.ElementRuleState state
			= new ElementRule.ElementRuleState();
		rule.encounterElement(A, state);
		rule.encounterEnd(state);
		state.clear();
		rule.encounterElement(A, state);
		rule.encounterElement(B, state);
		rule.encounterEnd(state);
		try {
			state.clear();
			rule.encounterEnd(state);
			fail("not done");
		} catch (ElementRuleException e) {
		}
	}

	public void testEncounterEnds2()
		throws Exception
	{
		String s;
		ElementReq req;
		s = "(a, ((b*, c) | d+))";
		req = createReq(s);
		ElementRule rule = new ElementRule(req, null);
		ElementRule.ElementRuleState state
			= new ElementRule.ElementRuleState();
		rule.encounterElement(A, state);
		rule.encounterElement(D, state);
		rule.encounterEnd(state);
		state.clear();
		rule.encounterElement(A, state);
		rule.encounterElement(B, state);
		rule.encounterElement(C, state);
		rule.encounterEnd(state);
		state.clear();
		rule.encounterElement(A, state);
		rule.encounterElement(C, state);
		try {
			state.clear();
			rule.encounterElement(A, state);
			rule.encounterEnd(state);
			fail("not done");
		} catch (ElementRuleException e) {
		}
		try {
			state.clear();
			rule.encounterElement(A, state);
			rule.encounterElement(B, state);
			rule.encounterEnd(state);
			fail("not done");
		} catch (ElementRuleException e) {
		}
	}

	public void testEncounterEnds3()
		throws Exception
	{
		String s;
		ElementReq req;
		s = "(a*, ((b) | (d))+, ((c))+)";
		req = createReq(s);
		ElementRule rule = new ElementRule(req, null);
		ElementRule.ElementRuleState state
			= new ElementRule.ElementRuleState();
		rule.encounterElement(B, state);
		rule.encounterElement(C, state);
		rule.encounterEnd(state);
		state.clear();
		rule.encounterElement(D, state);
		rule.encounterElement(B, state);
		rule.encounterElement(C, state);
		rule.encounterEnd(state);
		try {
			state.clear();
			rule.encounterElement(D, state);
			rule.encounterElement(D, state);
			rule.encounterEnd(state);
			fail("not done");
		} catch (ElementRuleException e) {
		}
	}

	public void testEncounterSeq2()
		throws Exception
	{
		String s;
		ElementReq req;
		s = "(a?, b?, c+)";
		req = createReq(s);
		ElementRule rule = new ElementRule(req, null);
		ElementRule.ElementRuleState state = 
			new ElementRule.ElementRuleState();
		rule.encounterElement(B, state);
		rule.encounterElement(C, state);
		rule.encounterElement(C, state);
	}

	public void testEncounterSeq3()
		throws Exception
	{
		String s;
		ElementReq req;
		s = "(c, (a, b)*, c)";
		req = createReq(s);
		ElementRule rule = new ElementRule(req, null);
		ElementRule.ElementRuleState state = 
			new ElementRule.ElementRuleState();
		rule.encounterElement(C, state);
		rule.encounterElement(C, state);
		state.clear();
		rule.encounterElement(C, state);
		rule.encounterElement(A, state);
		rule.encounterElement(B, state);
		rule.encounterElement(A, state);
		rule.encounterElement(B, state);
		rule.encounterElement(C, state);
	}

	public void testEncounterSeq4()
		throws Exception
	{
		String s;
		ElementReq req;
		s = "(c?, (a?, b?), d+)";
		req = createReq(s);
		ElementRule rule = new ElementRule(req, null);
		ElementRule.ElementRuleState state = 
			new ElementRule.ElementRuleState();
		rule.encounterElement(C, state);
		rule.encounterElement(B, state);
		rule.encounterElement(D, state);
		state.clear();
		rule.encounterElement(D, state);
		state.clear();
		rule.encounterElement(A, state);
		rule.encounterElement(D, state);
		rule.encounterElement(D, state);
	}

	public void testEncounterChoice()
		throws Exception
	{
		String s;
		ElementReq req;
		s = "(a | b | c)+";
		req = createReq(s);
		ElementRule rule = new ElementRule(req, null);
		ElementRule.ElementRuleState state = 
			new ElementRule.ElementRuleState();
		rule.encounterElement(A, state);
		rule.encounterElement(B, state);
		rule.encounterElement(C, state);
		rule.encounterElement(B, state);
		rule.encounterElement(A, state);
		state.clear();
		rule.encounterElement(C, state);
		state.clear();
		try {
			rule.encounterElement(D, state);
			fail("should fail");
		} catch (XmlException e) {
		}
	}

	public void testEncounterMix()
		throws Exception
	{
		String s;
		ElementReq req;
		s = "(a, (b | c), d)";
		req = createReq(s);
		ElementRule rule = new ElementRule(req, null);
		ElementRule.ElementRuleState state = 
			new ElementRule.ElementRuleState();
		rule.encounterElement(A, state);
		rule.encounterElement(C, state);
		rule.encounterElement(D, state);
		state.clear();
		rule.encounterElement(A, state);
		rule.encounterElement(B, state);
		rule.encounterElement(D, state);
		state.clear();
		try {
			rule.encounterElement(A, state);
			rule.encounterElement(D, state);
			fail("should fail");
		} catch (XmlException e) {
		}
	}

	public void testEncounterMix2()
		throws Exception
	{
		String s;
		ElementReq req;
		s = "(a?, (b | c)*, d)";
		req = createReq(s);
		ElementRule rule = new ElementRule(req, null);
		ElementRule.ElementRuleState state = 
			new ElementRule.ElementRuleState();
		rule.encounterElement(A, state);
		rule.encounterElement(D, state);
		state.clear();
		rule.encounterElement(B, state);
		rule.encounterElement(D, state);
		state.clear();
		rule.encounterElement(C, state);
		rule.encounterElement(B, state);
		rule.encounterElement(B, state);
		rule.encounterElement(D, state);
		try {
			rule.encounterElement(D, state);
			fail("should fail");
		} catch (XmlException e) {
		}
	}

	public void testEncounterMix3()
		throws Exception
	{
		String s;
		ElementReq req;
		s = "((b | c)+, (a+ | (d+, c+)))";
		req = createReq(s);
		ElementRule rule = new ElementRule(req, null);
		ElementRule.ElementRuleState state = 
			new ElementRule.ElementRuleState();
		rule.encounterElement(B, state);
		rule.encounterElement(B, state);
		rule.encounterElement(C, state);
		rule.encounterElement(B, state);
		rule.encounterElement(A, state);
		state.clear();
		rule.encounterElement(B, state);
		rule.encounterElement(C, state);
		rule.encounterElement(D, state);
		rule.encounterElement(C, state);
		state.clear();
		try {
			rule.encounterElement(A, state);
			fail("should fail");
		} catch (XmlException e) {
		}
		state.clear();
		rule.encounterElement(B, state);
		rule.encounterElement(A, state);
		rule.encounterElement(A, state);
		try {
			rule.encounterElement(D, state);
			fail("should fail");
		} catch (XmlException e) {
		}
		state.clear();
		rule.encounterElement(C, state);
		rule.encounterElement(D, state);
		try {
			rule.encounterElement(A, state);
			fail("should fail");
		} catch (XmlException e) {
		}
	}

	public void testEncounterSeq5()
		throws Exception
	{
		String s;
		ElementReq req;
		s = "(((a, b))+, (d)?, (c))";
		req = createReq(s);
		ElementRule rule = new ElementRule(req, null);
		ElementRule.ElementRuleState state = 
			new ElementRule.ElementRuleState();
		rule.encounterElement(A, state);
		rule.encounterElement(B, state);
		rule.encounterElement(A, state);
		rule.encounterElement(B, state);
		rule.encounterElement(D, state);
		rule.encounterElement(C, state);
		state.clear();
		rule.encounterElement(A, state);
		rule.encounterElement(B, state);
		rule.encounterElement(D, state);
		rule.encounterElement(C, state);
		state.clear();
		try {
			rule.encounterElement(D, state);
			fail("should fail");
		} catch (XmlException e) {
		}
		state.clear();
	}

	public void testAny()
		throws Exception
	{
		ElementRule rule = new ElementRule();
		ElementRule.ElementRuleState state = 
			new ElementRule.ElementRuleState();
		rule.encounterElement(A, state);
		rule.encounterElement(B, state);
		rule.toString();
	}

	public void testEmpty()
		throws Exception
	{
		ElementRule rule = new ElementRule();
		rule.allowNoElements();
		ElementRule.ElementRuleState state = 
			new ElementRule.ElementRuleState();
		try {
			rule.encounterElement(A, state);
			fail("should not allow any elements");
		} catch (XmlException e) {
		}
		rule.toString();
	}

	public void testIllegalArgument()
		throws Exception
	{
		try {
			new ElementRule(null, null);
			fail("cannot have null root");
		} catch (IllegalArgumentException e) {
		}
		try {
			new ElementRule(null, null, false);
			fail("cannot have null root");
		} catch (IllegalArgumentException e) {
		}
	}

	public void testEncounterNest()
		throws Exception
	{
		String s;
		ElementReq req;
		s = "(((((a?, b?)))))";
		req = createReq(s);
		ElementRule rule = new ElementRule(req, null);
		ElementRule.ElementRuleState state = 
			new ElementRule.ElementRuleState();
		rule.encounterElement(A, state);
		rule.encounterElement(B, state);
		state.clear();
		rule.encounterElement(B, state);
		state.clear();
		rule.encounterElement(A, state);
		try {
			rule.encounterElement(A, state);
			fail("should fail");
		} catch (XmlException e) {
		}
	}

	public void testEncounterSeq4Bad()
		throws Exception
	{
		String s;
		ElementReq req;
		s = "(c?, (a?, b?), d+)";
		req = createReq(s);
		ElementRule rule = new ElementRule(req, null);
		ElementRule.ElementRuleState state = 
			new ElementRule.ElementRuleState();
		rule.encounterElement(A, state);
		rule.encounterElement(B, state);
		try {
			rule.encounterElement(B, state);
			fail("Should fail with B " + s);
		} catch (XmlException e) {
		}
		state.clear();
		rule.encounterElement(A, state);
		try {
			rule.encounterElement(A, state);
			fail("Should fail with A " + s);
		} catch (XmlException e) {
		}
		state.clear();
		rule.encounterElement(D, state);
		rule.encounterElement(D, state);
	}

	public void testEncounterSeqBad()
		throws Exception
	{
		String s;
		ElementReq req;
		s = "(a+, b?, c+)";
		req = createReq(s);
		ElementRule rule = new ElementRule(req, null);
		ElementRule.ElementRuleState state = 
			new ElementRule.ElementRuleState();
		try {
			rule.encounterElement(B, state);
			fail("must have a");
		} catch (XmlException e) {
		}
		state.clear();
		rule.encounterElement(A, state);
		rule.encounterElement(C, state);
		rule.encounterElement(C, state);
		try {
			rule.encounterElement(A, state);
			fail("cannot have an 'a'");
		} catch (XmlException e) {
		} 
	}

	public void testEncounterSeqRep()
		throws Exception
	{
		String s;
		ElementReq req;
		s = "((a?, b?)+)";
		req = createReq(s);
		ElementRule rule = new ElementRule(req, null);
		ElementRule.ElementRuleState state = 
			new ElementRule.ElementRuleState();
		for (int i = 0; i < 10; i++) {
			rule.encounterElement(B, state);
			rule.encounterElement(A, state);
		}
	}

	public void testEncounterSeqBad2()
		throws Exception
	{
		String s;
		ElementReq req;
		s = "(c, (a, b)?, c)";
		req = createReq(s);
		ElementRule rule = new ElementRule(req, null);
		ElementRule.ElementRuleState state = 
			new ElementRule.ElementRuleState();
		rule.encounterElement(C, state);
		rule.encounterElement(A, state);
		try {
			rule.encounterElement(C, state);
		} catch (XmlException e) {
		}
		state.clear();
		rule.encounterElement(C, state);
		rule.encounterElement(A, state);
		try {
			rule.encounterElement(E, state);
		} catch (XmlException e) {
		}
		state.clear();
		rule.encounterElement(C, state);
		rule.encounterElement(A, state);
		rule.encounterElement(B, state);
		try {
			rule.encounterElement(B, state);
		} catch (XmlException e) {
		}
		state.clear();
		rule.encounterElement(C, state);
		rule.encounterElement(C, state);
	}

	public void testEmptyRuleEnd()
		throws Exception
	{
		ElementReq req = createReq("EMPTY");
		ElementRule rule = new ElementRule(req, null);
		ElementRule.ElementRuleState state = 
			new ElementRule.ElementRuleState();
		rule.encounterEnd(state);
	}
	public void testStarRuleEnd()
		throws Exception
	{
		ElementReq req;
		ElementRule rule;
		ElementRule.ElementRuleState state;
		req = createReq("(e)*");
		rule = new ElementRule(req, null);
		state = new ElementRule.ElementRuleState();
		rule.encounterEnd(state);
		req = createReq("(e)?");
		rule = new ElementRule(req, null);
		state = new ElementRule.ElementRuleState();
		rule.encounterEnd(state);
	}
}
