001/**
002 * Powerunit - A JDK1.8 test framework
003 * Copyright (C) 2014 Mathieu Boretti.
004 *
005 * This file is part of Powerunit
006 *
007 * Powerunit is free software: you can redistribute it and/or modify
008 * it under the terms of the GNU General Public License as published by
009 * the Free Software Foundation, either version 3 of the License, or
010 * (at your option) any later version.
011 *
012 * Powerunit is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
015 * GNU General Public License for more details.
016 *
017 * You should have received a copy of the GNU General Public License
018 * along with Powerunit. If not, see <http://www.gnu.org/licenses/>.
019 */
020package ch.powerunit;
021
022import java.lang.reflect.InvocationTargetException;
023import java.lang.reflect.Method;
024import java.util.Objects;
025
026import ch.powerunit.exception.InternalError;
027
028/**
029 * Definition of a statement (piece of code that can thrown Throwable).
030 * <p>
031 * A statement can be used inside test (to isolate a code that must thrown an
032 * exception) and are used internally by the framework to compose and execute
033 * test sequence element.
034 *
035 * @author borettim
036 * @param <P>
037 *            The type of the parameter
038 * @param <T>
039 *            the exception type
040 */
041@FunctionalInterface
042public interface Statement<P, T extends Throwable> {
043
044    /**
045     * Executable code.
046     * 
047     * @param parameter
048     *            A parameter for the statement
049     * @throws Throwable
050     *             in case of error.
051     */
052    void run(P parameter) throws Throwable;// should be T, but T seem to produce
053                                           // a bug in the compiler
054
055    /**
056     * Used to provide a name (for internal use purpose).
057     * 
058     * @return the string, by default null.
059     * @since 0.1.0
060     */
061    default String getName() {
062        return null;
063    }
064
065    /**
066     * Aggregate this statement and then the following. The second statement is
067     * done, even in case of exception in the first one.
068     * 
069     * @param after
070     *            the next statement
071     * @return the new statement
072     */
073    default Statement<P, T> andThenAlways(Statement<P, T> after) {
074        Objects.requireNonNull(after);
075        return (p) -> {
076            try {
077                run(p);
078            } finally {
079                after.run(p);
080            }
081        };
082    }
083
084    /**
085     * Aggregate this statement and then the following. The second statement is
086     * done except in case of exception in the first one.
087     * 
088     * @param after
089     *            the next statement
090     * @return the new statement
091     */
092    default Statement<P, T> andThenOnlySuccess(Statement<P, T> after) {
093        Objects.requireNonNull(after);
094        return (p) -> {
095            run(p);
096            after.run(p);
097        };
098    }
099
100    /**
101     * Build a around statement (do something, then something others, and after
102     * one a third statement, event in case of exception.
103     * 
104     * @param internal
105     *            the internal part
106     * @param before
107     *            the first statement
108     * @param after
109     *            the last statement, done event in case of exception.
110     * @return the new statement.
111     * @param <P>
112     *            The type of the parameter
113     * @param <T>
114     *            the exception type
115     */
116    static <P, T extends Throwable> Statement<P, T> around(
117            Statement<P, T> internal, Statement<P, T> before,
118            Statement<P, T> after) {
119        Objects.requireNonNull(internal);
120        Objects.requireNonNull(before);
121        Objects.requireNonNull(after);
122        return before.andThenOnlySuccess(internal).andThenAlways(after);
123    }
124
125    /**
126     * Build a statement based on a method-
127     * 
128     * @param target
129     *            the target object
130     * @param method
131     *            the method
132     * @return the new statement.
133     * @param <P>
134     *            The type of the parameter
135     * @param <T>
136     *            the exception type
137     */
138    static <P, T extends Throwable> Statement<P, T> reflectionMethod(
139            Object target, Method method) {
140        Objects.requireNonNull(target);
141        Objects.requireNonNull(method);
142        return new Statement<P, T>() {
143
144            @Override
145            public void run(P parameter) throws Throwable {
146                try {
147                    method.invoke(target);
148                } catch (InvocationTargetException e) {
149                    throw e.getCause();
150                } catch (IllegalAccessException | IllegalArgumentException e) {
151                    throw new InternalError("Unexpected error "
152                            + e.getMessage(), e);
153                }
154            }
155
156            @Override
157            public String getName() {
158                return method.getName();
159            }
160        };
161    }
162
163}