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;
025import java.util.function.Consumer;
026
027import ch.powerunit.exception.InternalError;
028
029/**
030 * Definition of a statement (piece of code that can thrown Throwable).
031 * <p>
032 * A statement can be used inside test (to isolate a code that must thrown an
033 * exception) and are used internally by the framework to compose and execute
034 * test sequence element.
035 *
036 * @author borettim
037 * @param <P>
038 *            The type of the parameter
039 * @param <T>
040 *            the exception type
041 */
042@FunctionalInterface
043public interface Statement<P, T extends Throwable> {
044
045    /**
046     * Executable code.
047     * 
048     * @param parameter
049     *            A parameter for the statement
050     * @throws Throwable
051     *             in case of error.
052     */
053    void run(P parameter) throws Throwable;// should be T, but T seem to produce
054                                           // a bug in the compiler
055
056    /**
057     * Used to provide a name (for internal use purpose).
058     * 
059     * @return the string, by default null.
060     * @since 0.1.0
061     */
062    default String getName() {
063        return null;
064    }
065
066    /**
067     * Aggregate this statement and then the following. The second statement is
068     * done, even in case of exception in the first one.
069     * 
070     * @param after
071     *            the next statement
072     * @return the new statement
073     */
074    default Statement<P, T> andThenAlways(Statement<P, T> after) {
075        Objects.requireNonNull(after);
076        return (p) -> {
077            try {
078                run(p);
079            } finally {
080                after.run(p);
081            }
082        };
083    }
084
085    /**
086     * Aggregate this statement and then the following. The second statement is
087     * done except in case of exception in the first one.
088     * 
089     * @param after
090     *            the next statement
091     * @return the new statement
092     */
093    default Statement<P, T> andThenOnlySuccess(Statement<P, T> after) {
094        Objects.requireNonNull(after);
095        return (p) -> {
096            run(p);
097            after.run(p);
098        };
099    }
100
101    /**
102     * Build a around statement (do something, then something others, and after
103     * one a third statement, event in case of exception.
104     * 
105     * @param internal
106     *            the internal part
107     * @param before
108     *            the first statement
109     * @param after
110     *            the last statement, done event in case of exception.
111     * @return the new statement.
112     * @param <P>
113     *            The type of the parameter
114     * @param <T>
115     *            the exception type
116     */
117    static <P, T extends Throwable> Statement<P, T> around(
118            Statement<P, T> internal, Statement<P, T> before,
119            Statement<P, T> after) {
120        Objects.requireNonNull(internal);
121        Objects.requireNonNull(before);
122        Objects.requireNonNull(after);
123        return before.andThenOnlySuccess(internal).andThenAlways(after);
124    }
125
126    /**
127     * Build a statement based on a method-
128     * 
129     * @param target
130     *            the target object
131     * @param method
132     *            the method
133     * @return the new statement.
134     * @param <P>
135     *            The type of the parameter
136     * @param <T>
137     *            the exception type
138     */
139    static <P, T extends Throwable> Statement<P, T> reflectionMethod(
140            Object target, Method method) {
141        Objects.requireNonNull(target);
142        Objects.requireNonNull(method);
143        return new Statement<P, T>() {
144
145            @Override
146            public void run(P parameter) throws Throwable {
147                try {
148                    method.invoke(target);
149                } catch (InvocationTargetException e) {
150                    throw e.getCause();
151                } catch (IllegalAccessException | IllegalArgumentException e) {
152                    throw new InternalError("Unexpected error "
153                            + e.getMessage(), e);
154                }
155            }
156
157            @Override
158            public String getName() {
159                return method.getName();
160            }
161        };
162    }
163
164    /**
165     * Build a statement based on a method-
166     * 
167     * @param method
168     *            the method
169     * @param param
170     *            the param
171     * @return the new statement.
172     * @param <P>
173     *            The type of the parameter
174     * @param <T>
175     *            the exception type
176     * @since 0.2.0
177     */
178    static <P, T extends Throwable> Statement<P, T> reflectionMethod(
179            Consumer<Object> method, Object param) {
180        Objects.requireNonNull(method);
181        return new Statement<P, T>() {
182
183            @Override
184            public void run(P parameter) throws Throwable {
185                method.accept(param);
186            }
187
188            @Override
189            public String getName() {
190                return "N/A";
191            }
192        };
193    }
194
195}