package org.apache.velocity.test;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.    
 */

import java.io.StringWriter;
import java.io.Writer;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.app.event.EventCartridge;
import org.apache.velocity.app.event.InvalidReferenceEventHandler;
import org.apache.velocity.context.Context;
import org.apache.velocity.runtime.RuntimeConstants;
import org.apache.velocity.runtime.RuntimeServices;
import org.apache.velocity.util.RuntimeServicesAware;
import org.apache.velocity.util.introspection.Info;

/**
 * Tests event handling for all event handlers except IncludeEventHandler.  This is tested
 * separately due to its complexity.
 *
 * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
 * @version $Id: InvalidEventHandlerTestCase.java 832302 2009-11-03 05:32:31Z wglass $
 */
public class InvalidEventHandlerTestCase
extends TestCase
{
    /**
     * Default constructor.
     */
    public InvalidEventHandlerTestCase(String name)
    {
        super(name);
    }
    
    public static Test suite ()
    {
        return new TestSuite(InvalidEventHandlerTestCase.class);
    }
    
    public void testManualEventHandlers()
    throws Exception
    {
        TestEventCartridge te = new TestEventCartridge();
        
        /**
         * Test attaching the event cartridge to the context
         */
        VelocityEngine ve = new VelocityEngine();
        ve.init();
        
        /*
         *  lets make a Context and add the event cartridge
         */
        
        VelocityContext inner = new VelocityContext();
        
        /*
         *  Now make an event cartridge, register all the
         *  event handlers (at once) and attach it to the
         *  Context
         */
        
        EventCartridge ec = new EventCartridge();
        ec.addEventHandler(te);
        ec.attachToContext( inner );
        
        doTestInvalidReferenceEventHandler1(ve, inner);
        doTestInvalidReferenceEventHandler2(ve, inner);
        doTestInvalidReferenceEventHandler3(ve, inner);
        doTestInvalidReferenceEventHandler4(ve, inner);
    }

    /**
     * Test assigning the event handlers via properties
     */

    public void testConfigurationEventHandlers()
            throws Exception
    {
        VelocityEngine ve = new VelocityEngine();
        ve.setProperty(RuntimeConstants.EVENTHANDLER_INVALIDREFERENCES, TestEventCartridge.class.getName());

        ve.init();
        doTestInvalidReferenceEventHandler1(ve, null);
        doTestInvalidReferenceEventHandler2(ve, null);
        doTestInvalidReferenceEventHandler3(ve, null);
        doTestInvalidReferenceEventHandler4(ve, null);
    }

    /**
     * Test deeper structures
     * @param ve
     * @param vc
     * @throws Exception
     */
    private void doTestInvalidReferenceEventHandler4(VelocityEngine ve, VelocityContext vc)
    throws Exception
    {
        VelocityContext context = new VelocityContext(vc);

        Tree test = new Tree();
        test.setField("10");
        Tree test2 = new Tree();
        test2.setField("12");
        test.setChild(test2);
        
        context.put("tree",test);
        String s;
        Writer w;
        
        // show work fine
        s = "$tree.Field $tree.field $tree.child.Field";
        w = new StringWriter();
        ve.evaluate( context, w, "mystring", s );
        
        s = "$tree.x $tree.field.x $tree.child.y $tree.child.Field.y";
        w = new StringWriter();
        ve.evaluate( context, w, "mystring", s );
        
    }
    
    /**
     * Test invalid #set
     * @param ve
     * @param vc
     * @throws Exception
     */
    private void doTestInvalidReferenceEventHandler3(VelocityEngine ve, VelocityContext vc)
    throws Exception
    {
        VelocityContext context = new VelocityContext(vc);
        context.put("a1",new Integer(5));
        context.put("a4",new Integer(5));
        context.put("b1","abc");
        
        String s;
        Writer w;
        
        // good object, bad right hand side
        s = "#set($xx = $a1.afternoon())";
        w = new StringWriter();
        try {
            ve.evaluate( context, w, "mystring", s );
            fail("Expected exception.");
        } catch (RuntimeException e) {}
        
        // good object, bad right hand reference
        s = "#set($yy = $q1)";
        w = new StringWriter();
        try {
            ve.evaluate( context, w, "mystring", s );
            fail("Expected exception.");
        } catch (RuntimeException e) {}
        
    }

    /**
     * Test invalid method calls
     * @param ve
     * @param vc
     * @throws Exception
     */
    private void doTestInvalidReferenceEventHandler2(VelocityEngine ve, VelocityContext vc)
    throws Exception
    {
        VelocityContext context = new VelocityContext(vc);
        context.put("a1",new Integer(5));
        context.put("b1",new Integer(5));
        context.put("a4",new Integer(5));
        context.put("b4",new Integer(5));
        context.put("z1","abc");
        
        String s;
        Writer w;
        
        // good object, bad method
        s = "$a1.afternoon()";
        w = new StringWriter();
        try {
            ve.evaluate( context, w, "mystring", s );
            fail("Expected exception.");
        } catch (RuntimeException e) {}
        
        // good object, bad method
        s = "$!b1.afternoon()";
        w = new StringWriter();
        try {
            ve.evaluate( context, w, "mystring", s );
            fail("Expected exception.");
        } catch (RuntimeException e) {}
        
        // bad object, bad method -- fails on get
        s = "$zz.daylight()";
        w = new StringWriter();
        try {
            ve.evaluate( context, w, "mystring", s );
            fail("Expected exception.");
        } catch (RuntimeException e) {}

        // change result
        s = "$z1.baby()";
        w = new StringWriter();
        ve.evaluate( context, w, "mystring", s );
        assertEquals("www",w.toString());        
    }
    
    /**
     * Test invalid gets/references
     * @param ve
     * @param vc
     * @throws Exception
     */
    private void doTestInvalidReferenceEventHandler1(VelocityEngine ve, VelocityContext vc)
    throws Exception
    {
        String result;
        
        VelocityContext context = new VelocityContext(vc);
        context.put("a1",new Integer(5));
        context.put("b1",new Integer(5));
        context.put("a4",new Integer(5));
        context.put("b4",new Integer(5));
        context.put("z1","abc");
        
        // normal - should be no calls to handler
        String s = "$a1 $a1.intValue() $z1 $z1.length() #set($c1 = '5')";
        Writer w = new StringWriter();
        ve.evaluate( context, w, "mystring", s );
        
        // good object, bad property
        s = "$a1.foobar";
        w = new StringWriter();
        try {
            ve.evaluate( context, w, "mystring", s );
            fail("Expected exception.");
        } catch (RuntimeException e) {}
        
        // good object, bad property / silent
        s = "$!b1.foobar";
        w = new StringWriter();
        try {
            ve.evaluate( context, w, "mystring", s );
            fail("Expected exception.");
        } catch (RuntimeException e) {}
        
        // bad object, bad property            
        s = "$a2.foobar";
        w = new StringWriter();
        try {
            ve.evaluate( context, w, "mystring", s );
            fail("Expected exception.");
        } catch (RuntimeException e) {}
        
        // bad object, bad property / silent            
        s = "$!b2.foobar";
        w = new StringWriter();
        try {
            ve.evaluate( context, w, "mystring", s );
            fail("Expected exception.");
        } catch (RuntimeException e) {}
        
        // bad object, no property            
        s = "$a3";
        w = new StringWriter();
        try {
            ve.evaluate( context, w, "mystring", s );
            fail("Expected exception.");
        } catch (RuntimeException e) {}
        
        // bad object, no property / silent            
        s = "$!b3";
        w = new StringWriter();
        try {
            ve.evaluate( context, w, "mystring", s );
            fail("Expected exception.");
        } catch (RuntimeException e) {}
        
        // good object, bad property; change the value
        s = "$a4.foobar";
        w = new StringWriter();
        ve.evaluate( context, w, "mystring", s );
        result = w.toString();
        assertEquals("zzz", result);

    }
    
    

    /**
     * Test assigning the event handlers via properties
     */
    
    public static class TestEventCartridge
    implements InvalidReferenceEventHandler,
    RuntimeServicesAware
    {
        private RuntimeServices rs;
        
        public TestEventCartridge()
        {
        }
        
        /**
         * Required by EventHandler
         */
        public void setRuntimeServices( RuntimeServices rs )
        {
            // make sure this is only called once
            if (this.rs == null)
                this.rs = rs;
            
            else
                fail("initialize called more than once.");
        }
        
        
        public Object invalidGetMethod(Context context, String reference, Object object, String property, Info info)
        {
            // as a test, make sure this EventHandler is initialized
            if (rs == null)
                fail ("Event handler not initialized!");
            
            // good object, bad property
            if (reference.equals("$a1.foobar"))
            {
                assertEquals(new Integer(5),object);
                assertEquals("foobar",property);
                throw new RuntimeException("expected exception");
            }
            
            // good object, bad property
            else if (reference.equals("$!b1.foobar"))
            {
                assertEquals(new Integer(5),object);
                assertEquals("foobar",property);
                throw new RuntimeException("expected exception");
            }
            
            // good object, bad property
            else if (reference.equals("$a1.foobar"))
            {
                assertEquals(new Integer(5),object);
                assertEquals("foobar",property);
                throw new RuntimeException("expected exception");
            }
            
            // good object, bad property
            else if (reference.equals("$!b1.foobar"))
            {
                assertEquals(new Integer(5),object);
                assertEquals("foobar",property);
                throw new RuntimeException("expected exception");
            }
            
            // bad object, bad property            
            else if (reference.equals("$a2"))
            {
                assertNull(object);
                assertNull(property);
                throw new RuntimeException("expected exception");
            }

            // bad object, bad property            
            else if (reference.equals("$!b2"))
            {
                assertNull(object);
                assertNull(property);
                throw new RuntimeException("expected exception");
            }

            // bad object, no property            
            else if (reference.equals("$a3"))
            {
                assertNull(object);
                assertNull(property);
                throw new RuntimeException("expected exception");
            }
            
            // bad object, no property            
            else if (reference.equals("$!b3"))
            {
                assertNull(object);
                assertNull(property);
                throw new RuntimeException("expected exception");
            }
            
            // good object, bad property; change the value
            else if (reference.equals("$a4.foobar"))
            {
                assertEquals(new Integer(5),object);
                assertEquals("foobar",property);
                return "zzz";
            }

            // bad object, bad method -- fail on the object
            else if (reference.equals("$zz"))
            {
                assertNull(object);
                assertNull(property);
                throw new RuntimeException("expected exception");
            }

            // pass q1 through
            else if (reference.equals("$q1"))
            {

            }

            
            else if (reference.equals("$tree.x"))
            {
                assertEquals("x",property);
            }

            else if (reference.equals("$tree.field.x"))
            {
                assertEquals("x",property);
            }

            else if (reference.equals("$tree.child.y"))
            {
                assertEquals("y",property);
            }
            
            else if (reference.equals("$tree.child.Field.y"))
            {
                assertEquals("y",property);
            }
            
            else
            {
                fail("invalidGetMethod: unexpected reference: " + reference);
            }
            return null;
        }
        
        public Object invalidMethod(Context context, String reference, Object object, String method, Info info)
        {
            // as a test, make sure this EventHandler is initialized
            if (rs == null)
                fail ("Event handler not initialized!");

            // good reference, bad method
            if (object.getClass().equals(Integer.class))
            {
                if (reference.equals("$a1.afternoon()"))
                {
                    assertEquals("afternoon",method);
                    throw new RuntimeException("expected exception");                    
                }
                else if (reference.equals("$!b1.afternoon()"))
                {
                    assertEquals("afternoon",method);
                    throw new RuntimeException("expected exception");                    
                }
                else
                {
                    fail("Unexpected invalid method.  " + method);
                    
                }
            }


            else if (object.getClass().equals(String.class) && "baby".equals(method))
            {
                return "www";
            }

            else
            { 
                fail("Unexpected invalid method.  " + method);
            }

            return null;
        }        
    

        public boolean invalidSetMethod(Context context, String leftreference, String rightreference, Info info)
        {

            // as a test, make sure this EventHandler is initialized
            if (rs == null)
                fail ("Event handler not initialized!");

            // good object, bad method
            if (leftreference.equals("xx"))
            {
                assertEquals("q1.afternoon()",rightreference);
                throw new RuntimeException("expected exception");
            }
            if (leftreference.equals("yy"))
            {
                assertEquals("$q1",rightreference);
                throw new RuntimeException("expected exception");
            }
            else
            { 
                fail("Unexpected left hand side.  " + leftreference);
            }
            
            return false;
        }

    }

    public static class Tree
    {
        String field;
        Tree child;
        
        public Tree()
        {
            
        }

        public String getField()
        {
            return field;
        }

        public void setField(String field)
        {
            this.field = field;
        }

        public Tree getChild()
        {
            return child;
        }

        public void setChild(Tree child)
        {
            this.child = child;
        }

        public String testMethod() 
        {
            return "123";
        }
    }
    
}
