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.extensions.exceptions;
021
022import static java.util.Arrays.stream;
023
024import java.util.Collection;
025import java.util.Comparator;
026import java.util.Optional;
027import java.util.function.Function;
028
029/**
030 * Interface to help create mapper function for exception wrapping.
031 *
032 * @since 2.0.0
033 */
034public interface ExceptionMapper extends Function<Exception, RuntimeException> {
035
036        /**
037         * Exception wrapper, that may be used to copy error code and sql state to the
038         * {@code WrappedException} message.
039         * <p>
040         * <b>This mapper will only works correctly if the module <i>java.sql</i> is
041         * available.</b>
042         * <p>
043         *
044         * @return the Mapper for {@code SQLException}.
045         * @throws NoClassDefFoundError
046         *             In case the {@code SQLException} is not available (java.sql
047         *             module missing).
048         */
049        static ExceptionMapper sqlExceptionMapper() {
050                return Optional.ofNullable(Constants.SQL_EXCEPTION_MAPPER)
051                                .orElseThrow(() -> new NoClassDefFoundError("Unable to find the sqlException"));
052        }
053
054        /**
055         * Exception wrapper, that may be used to copy jaxb information to the
056         * {@code WrappedException} message.
057         * <p>
058         * <b>This mapper will only works correctly if the class <i>JAXBException</i> is
059         * available.</b>
060         * <p>
061         *
062         * @return the Mapper for {@code JAXBException}.
063         * @throws NoClassDefFoundError
064         *             In case the {@code JAXBException} is not available.
065         * @since 2.1.0
066         */
067        static ExceptionMapper jaxbExceptionMapper() {
068                return Optional.ofNullable(Constants.JAXBEXCEPTION_EXCEPTION_MAPPER)
069                                .orElseThrow(() -> new NoClassDefFoundError("Unable to find the JAXBException"));
070        }
071
072        /**
073         * Exception wrapper, that may be used to copy sax information to the
074         * {@code WrappedException} message.
075         * <p>
076         * <b>This mapper will only works correctly if the class <i>SAXException</i> is
077         * available.</b>
078         * <p>
079         *
080         * @return the Mapper for {@code SAXException}.
081         * @throws NoClassDefFoundError
082         *             In case the {@code SAXException} is not available (java.xml
083         *             module missing).
084         * @since 2.1.0
085         */
086        static ExceptionMapper saxExceptionMapper() {
087                return Optional.ofNullable(Constants.SAXEXCEPTION_EXCEPTION_MAPPER)
088                                .orElseThrow(() -> new NoClassDefFoundError("Unable to find the SAXException"));
089        }
090
091        /**
092         * Exception wrapper, that may be used to copy transformer exception information
093         * to the {@code WrappedException} message.
094         * <p>
095         * <b>This mapper will only works correctly if the class <i>SAXException</i> is
096         * available.</b>
097         * <p>
098         *
099         * @return the Mapper for {@code TransformerException}.
100         * @throws NoClassDefFoundError
101         *             In case the {@code TransformerException} is not available
102         *             (java.xml module missing).
103         * @since 2.1.0
104         */
105        static ExceptionMapper transformerExceptionMapper() {
106                return Optional.ofNullable(Constants.TRANSFORMEREXCEPTION_EXCEPTION_MAPPER)
107                                .orElseThrow(() -> new NoClassDefFoundError("Unable to find the TransformerException"));
108        }
109
110        Class<? extends Exception> targetException();
111
112        private boolean accept(Exception e) {
113                return targetException().isInstance(e);
114        }
115
116        /**
117         * This method is used when ExceptionMapper are registered as default Mapper to
118         * defines the right order to select then.
119         *
120         * @return an ordering key, by default 0.
121         * @since 2.2.0
122         */
123        default int order() {
124                return 0;
125        }
126
127        /**
128         * Helper method to create exception wrapper that check the exception class.
129         *
130         * @param clazz
131         *            the class of the exception to be wrapped.
132         * @param mapper
133         *            the exception mapper.
134         * @param <E>
135         *            the type of the exception.
136         * @return A new exception mapper, which use the one received as parameter or
137         *         for the other exception just create a {@code WrappedException}.
138         */
139        static <E extends Exception> ExceptionMapper forException(Class<E> clazz, Function<E, RuntimeException> mapper) {
140                return forException(clazz, mapper, 0);
141        }
142
143        /**
144         * Helper method to create exception wrapper that check the exception class.
145         *
146         * @param clazz
147         *            the class of the exception to be wrapped.
148         * @param mapper
149         *            the exception mapper.
150         * @param order
151         *            the order
152         * @param <E>
153         *            the type of the exception.
154         * @return A new exception mapper, which use the one received as parameter or
155         *         for the other exception just create a {@code WrappedException}.
156         * @since 2.2.0
157         */
158        static <E extends Exception> ExceptionMapper forException(Class<E> clazz, Function<E, RuntimeException> mapper,
159                        int order) {
160                return new ExceptionMapper() {
161
162                        @Override
163                        public RuntimeException apply(Exception t) {
164                                return clazz.isInstance(t) ? mapper.apply(clazz.cast(t)) : new WrappedException(t);
165                        }
166
167                        @Override
168                        public Class<E> targetException() {
169                                return clazz;
170                        }
171
172                        @Override
173                        public int order() {
174                                return order;
175                        }
176                };
177        }
178
179        /**
180         * Helper method to create exception wrapper to use the first mapper if
181         * applicable or else the second.
182         *
183         * @param mapper1
184         *            the first mapper to be used.
185         * @param mapper2
186         *            the second mapper to used.
187         * @return the mapping function.
188         */
189        static Function<Exception, RuntimeException> forExceptions(ExceptionMapper mapper1, ExceptionMapper mapper2) {
190                return e -> mapper1.accept(e) ? mapper1.apply(e) : mapper2.apply(e);
191        }
192
193        /**
194         * Helper method to create exception wrapper to use the first mapper if
195         * applicable or else the second or else the third
196         *
197         * @param mapper1
198         *            the first mapper to be used.
199         * @param mapper2
200         *            the second mapper to used.
201         * @param mapper3
202         *            the third mapper to used.
203         * @return the mapping function.
204         */
205        static Function<Exception, RuntimeException> forExceptions(ExceptionMapper mapper1, ExceptionMapper mapper2,
206                        ExceptionMapper mapper3) {
207                return e -> (mapper1.accept(e) ? mapper1 : forExceptions(mapper2, mapper3)).apply(e);
208        }
209
210        /**
211         * Helper method to create exception wrapper that use the first one that is
212         * applicable.
213         *
214         * @param mappers
215         *            the mapper to be tried
216         * @return the mapping function.
217         */
218        static Function<Exception, RuntimeException> forExceptions(ExceptionMapper... mappers) {
219                if (mappers.length == 0) {
220                        return WrappedException::new;
221                }
222                return e -> stream(mappers).sequential().filter(m -> m.accept(e)).limit(1).map(m -> m.apply(e)).findFirst()
223                                .orElseGet(() -> new WrappedException(e));
224        }
225
226        /**
227         * Helper method to create exception wrapper that use the first one that is
228         * applicable, but having them sorted using the {@link #order()} method.
229         *
230         * @param mappers
231         *            the mapper to be tried
232         * @return the mapping function.
233         * @since 2.2.0
234         */
235        static Function<Exception, RuntimeException> forOrderedExceptions(Collection<ExceptionMapper> mappers) {
236                if (mappers.isEmpty()) {
237                        return WrappedException::new;
238                }
239                return forExceptions(mappers.stream().sorted(Comparator.comparingInt(ExceptionMapper::order))
240                                .toArray(ExceptionMapper[]::new));
241        }
242
243}