package example.common.util;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import example.common.util.GenericTemplate;

import junit.framework.TestCase;

public class GenericTemplateTest extends TestCase {

    List<String> log = new ArrayList<String>();

    @Override
    protected void setUp() throws Exception {
        log.clear();
    }

    @Override
    protected void tearDown() throws Exception {
        log.clear();
    }

    /**
     * 検査例外をスローする場合。
     * 利用側は catch または throws が必要。
     */
    public void testGenericTemplate() throws Exception {
        // void test(String ,int) throws IOException;
        GenericTemplate<Void, IOException> template
                = new GenericTemplate<Void, IOException>("test", "abc", 123) {
            protected void execute() throws Throwable {}
        };
        assertEquals("test", template.getName());
        assertEquals(2, template.getArgs().length);
        assertEquals("abc", template.getArg(0));
        assertEquals(123, template.getArg(1));
        assertEquals(IOException.class, template.getThrowableClass());
        assertNull(template.getReturning());
        assertNull(template.getThrowing());
        
        template.invoke();
        assertNull(template.getReturning());
        assertNull(template.getThrowing());
    }

    /**
     * 非検査例外をスローする場合。
     * 利用側は catch および throws が不要。
     */
    public void testGenericTemplate_RuntimeException() throws Exception {
        // int test() /*throws RuntimeException*/
        GenericTemplate<Integer, RuntimeException> template
                = new GenericTemplate<Integer, RuntimeException>("test") {
            protected void execute() throws Throwable {
                setReturning(123);
            }
        };
        assertEquals("test", template.getName());
        assertEquals(0, template.getArgs().length);
        assertEquals(RuntimeException.class, template.getThrowableClass());
        assertNull(template.getReturning());
        assertNull(template.getThrowing());
        
        int n = template.invoke();
        assertEquals(123, n);
        assertEquals(123, template.getReturning().intValue());
        assertNull(template.getThrowing());
    }

    /**
     * サブクラスを定義した場合。
     */
    public void testGenericTemplate_Subclass() throws IOException {
        // String test(int) throws IOException
        ExampleTemplate template = new ExampleTemplate("test", 123);
        assertEquals("test", template.getName());
        assertEquals(1, template.getArgs().length);
        assertEquals(123, template.getArg(0));
        assertEquals(IOException.class, template.getThrowableClass());
        assertNull(template.getReturning());
        assertNull(template.getThrowing());
        
        String s = template.invoke();
        assertNull(s);
        assertNull(template.getReturning());
        assertNull(template.getThrowing());
    }

    /**
     * 正常終了する場合。
     */
    public void testInvoke() throws IOException {
        ExampleTemplate template = new ExampleTemplate("test", 123) {
            @Override
            protected void execute() throws Throwable {
                super.execute();
                setReturning("abc");
            }
        };
        String s = template.invoke();
        assertEquals("abc", s);
        
        assertEquals(4, log.size());
        assertEquals("before",    log.get(0));
        assertEquals("execute",   log.get(1));
        assertEquals("returning", log.get(2));
        assertEquals("after",     log.get(3));
    }

    /**
     * before() で例外が発生する場合。
     */
    public void testInvoke_ExceptionAtBefore() throws IOException {
        ExampleTemplate template = new ExampleTemplate("test", 123) {
            @Override
            protected void before() throws Throwable {
                super.before();
                throw new IllegalArgumentException("before");
            }
        };
        try {
            template.invoke();
            fail();
        } catch (IllegalArgumentException e) {
            assertEquals("before", e.getMessage());
        }
        
        assertEquals(1, log.size());
        assertEquals("before", log.get(0));
    }

    /**
     * execute() で例外が発生する場合。
     */
    public void testInvoke_ExceptionAtExecute() {
        ExampleTemplate template = new ExampleTemplate("test", 123) {
            @Override
            protected void execute() throws Throwable {
                super.execute();
                throw new IOException("execute");
            }
        };
        try {
            template.invoke();
            fail();
        } catch (IOException e) {
            assertEquals("execute", e.getMessage());
        }
        
        assertEquals(4, log.size());
        assertEquals("before",   log.get(0));
        assertEquals("execute",  log.get(1));
        assertEquals("throwing", log.get(2));
        assertEquals("after",    log.get(3));
    }

    /**
     * afterReturning() で例外が発生する場合。
     */
    public void testInvoke_ExceptionAtReturning() throws IOException {
        ExampleTemplate template = new ExampleTemplate("test", 123) {
            @Override
            protected void afterReturning() throws Throwable {
                super.afterReturning();
                throw new IllegalStateException("returning");
            }
        };
        try {
            template.invoke();
        } catch (IllegalStateException e) {
            assertEquals("returning", e.getMessage());
        }
        
        assertEquals(5, log.size());
        assertEquals("before",    log.get(0));
        assertEquals("execute",   log.get(1));
        assertEquals("returning", log.get(2));
        assertEquals("throwing",  log.get(3));
        assertEquals("after",     log.get(4));
    }

    /**
     * execute()、afterThrowing() で例外が発生する場合。
     */
    public void testInvoke_ExceptionAtExecuteAndThrowing() throws IOException {
        ExampleTemplate template = new ExampleTemplate("test", 123) {
            @Override
            protected void execute() throws Throwable {
                super.execute();
                throw new IOException("execute");
            }
            @Override
            protected void afterThrowing() throws Throwable {
                super.afterThrowing();
                throw new Exception("throwing");
            }
        };
        try {
            template.invoke();
            fail();
        } catch (RuntimeException e) {
            // 本来 throw できない例外の場合はラップされる。
            Throwable cause = e.getCause();
            assertEquals(Exception.class, cause.getClass());
            assertEquals("throwing", cause.getMessage());
        }
        
        assertEquals(4, log.size());
        assertEquals("before",   log.get(0));
        assertEquals("execute",  log.get(1));
        assertEquals("throwing", log.get(2));
        assertEquals("after",    log.get(3));
    }

    /**
     * after() で例外が発生する場合。
     */
    public void testInvoke_ExceptionAtAfter() {
        ExampleTemplate template = new ExampleTemplate("test", 123) {
            @Override
            protected void after() throws Throwable {
                super.after();
                throw new IOException("after");
            }
        };
        try {
            template.invoke();
        } catch (IOException e) {
            assertEquals("after", e.getMessage());
        }
        
        assertEquals(4, log.size());
        assertEquals("before",    log.get(0));
        assertEquals("execute",   log.get(1));
        assertEquals("returning", log.get(2));
        assertEquals("after",     log.get(3));
    }

    /**
     * execute()、after() で例外が発生する場合。
     * after() で発生する例外は、throws で指定した例外。
     */
    public void testInvoke_ExceptionAtExecuteAndAfter() {
        ExampleTemplate template = new ExampleTemplate("test", 123) {
            @Override
            protected void execute() throws Throwable {
                super.execute();
                throw new IOException("execute");
            }
            @Override
            protected void after() throws Throwable {
                super.after();
                throw new IOException("after");
            }
        };
        try {
            template.invoke();
            fail();
        } catch (IOException e) {
            // execute() の例外が優先。
            assertEquals("execute", e.getMessage());
        }
        
        assertEquals(4, log.size());
        assertEquals("before",   log.get(0));
        assertEquals("execute",  log.get(1));
        assertEquals("throwing", log.get(2));
        assertEquals("after",    log.get(3));
    }

    /**
     * execute()、after() で例外が発生する場合。
     * after() で発生する例外は、throws で指定したもの以外の例外。
     */
    public void testInvoke_ExceptionAtExecuteAndAfter2() throws IOException {
        ExampleTemplate template = new ExampleTemplate("test", 123) {
            @Override
            protected void execute() throws Throwable {
                super.execute();
                throw new IOException("execute");
            }
            @Override
            protected void after() throws Throwable {
                super.after();
                throw new RuntimeException("after");
            }
        };
        try {
            template.invoke();
            fail();
        } catch (RuntimeException e) {
            // after() で発生した例外で上書き。
            assertEquals("after", e.getMessage());
        }
        
        assertEquals(4, log.size());
        assertEquals("before",   log.get(0));
        assertEquals("execute",  log.get(1));
        assertEquals("throwing", log.get(2));
        assertEquals("after",    log.get(3));
    }

    /**
     * execute()、after() で例外が発生する場合。
     * after() で発生する例外はエラー。
     */
    public void testInvoke_ExceptionAtExecuteAndAfter3() throws IOException {
        ExampleTemplate template = new ExampleTemplate("test", 123) {
            @Override
            protected void execute() throws Throwable {
                super.execute();
                throw new IOException("execute");
            }
            @Override
            protected void after() throws Throwable {
                super.after();
                throw new Error("after");
            }
        };
        try {
            template.invoke();
            fail();
        } catch (Error e) {
            // after() で発生したエラーで上書き。
            assertEquals("after", e.getMessage());
        }
        
        assertEquals(4, log.size());
        assertEquals("before",   log.get(0));
        assertEquals("execute",  log.get(1));
        assertEquals("throwing", log.get(2));
        assertEquals("after",    log.get(3));
    }

    /**
     * テスト用の GenericTemplate。
     */
    class ExampleTemplate extends GenericTemplate<String, IOException> {

        public ExampleTemplate(String name, Object... args) {
            super(name, args);
        }

        @Override
        public String invoke() throws IOException {
            try {
                return doInvoke();
            } catch (Throwable t) {
                throw toThrowableException(t);
            }
        }

        @Override
        protected void before() throws Throwable {
            log.add("before");
        }

        protected void execute() throws Throwable {
            log.add("execute");
        }

        @Override
        protected void afterReturning() throws Throwable {
            log.add("returning");
        }

        @Override
        protected void afterThrowing() throws Throwable {
            log.add("throwing");
        }

        @Override
        protected void after() throws Throwable {
            log.add("after");
        }

    }

}
