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}