001/* 002 * ============================================================================ 003 * Copyright © 2002-2024 by Thomas Thrien. 004 * All Rights Reserved. 005 * ============================================================================ 006 * Licensed to the public under the agreements of the GNU Lesser General Public 007 * License, version 3.0 (the "License"). You may obtain a copy of the License at 008 * 009 * http://www.gnu.org/licenses/lgpl.html 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 013 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 014 * License for the specific language governing permissions and limitations 015 * under the License. 016 */ 017 018package org.tquadrat.foundation.exception; 019 020import static java.util.Arrays.stream; 021import static org.apiguardian.api.API.Status.STABLE; 022import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument; 023 024import java.io.Serial; 025import java.util.Collection; 026import java.util.stream.Stream; 027 028import org.apiguardian.api.API; 029import org.tquadrat.foundation.annotation.ClassVersion; 030import org.tquadrat.foundation.function.Functions; 031 032/** 033 * <p>{@summary A "container" exception for exception thrown within 034 * lambda expressions.}</p> 035 * <p>When a method that emits a checked exception is called inside a lambda 036 * function, this has to be caught <i>inside</i> that function; it is not 037 * possible to declare that exception for the method, as long as the 038 * underlying 039 * {@linkplain FunctionalInterface functional interface} 040 * have not done that already.</p> 041 * <p>Unfortunately, the methods from the interfaces in the package 042 * {@link java.util.function} 043 * do not declare any exception, for good reason. So the code below is not 044 * possible:</p> 045 * <pre><code> … 046 * Appendable appendable = … 047 * Consumer appender = s -> appendable.append( s ); 048 * appender.accept( "…" ); 049 * …</code></pre> 050 * <p>because 051 * {@link Appendable#append(CharSequence)} 052 * declares to throw an 053 * {@link java.io.IOException}.</p> 054 * <p>This class now is meant to wrap those exceptions and to allow them to 055 * bubble up to the caller:</p> 056 * <pre><code> … 057 * Appendable appendable = … 058 * Consumer appender = 059 * { 060 * try 061 * { 062 * s -> appendable.append( s ); 063 * } 064 * catch( IOException e ) 065 * { 066 * throw new LambdaContainerException( e ); 067 * } 068 * } 069 * 070 * try 071 * { 072 * appender.accept( "…" ); 073 * } 074 * catch( LambdaContainerException e ) 075 * { 076 * throw (IOException) e.getCause(); 077 * } 078 * …</code></pre> 079 * <p>When said above that the methods from the functional interfaces in the 080 * {@code java.util.function} <i>unfortunately</i> do not declare any 081 * exception, this was only focused on their use with code that may emit 082 * checked exceptions. But in fact it is a good thing that the methods in 083 * these interfaces do not declare any exceptions, as this would have polluted 084 * any of the APIs that make use of these functional interfaces. And using the 085 * pattern above would be an alternative. Another would be the methods 086 * provided in the class 087 * {@link Functions}.</p> 088 * 089 * @extauthor Thomas Thrien - thomas.thrien@tquadrat.org 090 * @version $Id: LambdaContainerException.java 1084 2024-01-03 15:31:20Z tquadrat $ 091 * @since 0.1.0 092 * 093 * @UMLGraph.link 094 */ 095@SuppressWarnings( "removal" ) 096@ClassVersion( sourceVersion = "$Id: LambdaContainerException.java 1084 2024-01-03 15:31:20Z tquadrat $" ) 097@API( status = STABLE, since = "0.1.0" ) 098public final class LambdaContainerException extends CheckedExceptionWrapper 099{ 100 /*---------------*\ 101 ====** Inner Classes **==================================================== 102 \*---------------*/ 103 /** 104 * The exception that is thrown when the container holds an unexpected 105 * exception. 106 */ 107 private final class UnexpectedException extends RuntimeException 108 { 109 /*------------------------*\ 110 ====** Static Initialisations **======================================= 111 \*------------------------*/ 112 /** 113 * The serial version UID for objects of this class: {@value}. 114 */ 115 @Serial 116 private static final long serialVersionUID = 1L; 117 118 /*--------------*\ 119 ====** Constructors **================================================= 120 \*--------------*/ 121 /** 122 * Creates a new {@code UnexpectedException} instance. 123 */ 124 public UnexpectedException() 125 { 126 super( "The Exception '%s' was not expected".formatted( LambdaContainerException.this.getCause().getClass().getName() ) ); 127 setStackTrace( LambdaContainerException.this.getCause().getStackTrace() ); 128 } // UnexpectedException() 129 130 /*---------*\ 131 ====** Methods **====================================================== 132 \*---------*/ 133 /** 134 * {@inheritDoc} 135 * 136 * @see Throwable#getCause() 137 */ 138 @Override 139 public final synchronized Throwable getCause() { return LambdaContainerException.this.getCause(); } 140 } 141 // class UnexpectedException 142 143 /*------------------------*\ 144 ====** Static Initialisations **=========================================== 145 \*------------------------*/ 146 /** 147 * The serial version UID for objects of this class: {@value}. 148 * 149 * @hidden 150 */ 151 @Serial 152 private static final long serialVersionUID = 1L; 153 154 /*--------------*\ 155 ====** Constructors **===================================================== 156 \*--------------*/ 157 /** 158 * Creates a new {@code LambdaContainerException} instance for the given 159 * exception. 160 * 161 * @param e The exception to wrap; <i>cannot</i> be {@code null}. 162 */ 163 public LambdaContainerException( final Exception e ) 164 { 165 super( requireNonNullArgument( e, "e" ) ); 166 } // LambdaContainerException() 167 168 /*---------*\ 169 ====** Methods **========================================================== 170 \*---------*/ 171 /** 172 * Checks whether the contained Exception is somehow expected. 173 * 174 * @param expected The expected exceptions. 175 * @return {@code true} if the contained Exception is among the list of 176 * expected exceptions, {@code false} otherwise. 177 */ 178 public final boolean checkIfExpected( final Stream<Class<? extends Exception>> expected ) 179 { 180 final var cause = getCause(); 181 final var retValue = requireNonNullArgument( expected, "expected" ).anyMatch( e -> e.isInstance( cause ) ); 182 183 //---* Done *---------------------------------------------------------- 184 return retValue; 185 } // checkIfExpected() 186 187 /** 188 * Checks whether the contained Exception is somehow expected. 189 * 190 * @param expected The expected exceptions. 191 * @return {@code true} if the contained Exception is among the list of 192 * expected exceptions, {@code false} otherwise. 193 */ 194 public final boolean checkIfExpected( final Collection<Class<? extends Exception>> expected ) 195 { 196 final var retValue = checkIfExpected( requireNonNullArgument( expected, "expected" ).stream() ); 197 198 //---* Done *---------------------------------------------------------- 199 return retValue; 200 } // checkIfExpected() 201 202 /** 203 * Checks whether the contained Exception is somehow expected. 204 * 205 * @param expected The expected exceptions. 206 * @return {@code true} if the contained Exception is among the list of 207 * expected exceptions, {@code false} otherwise. 208 */ 209 @SafeVarargs 210 public final boolean checkIfExpected( final Class<? extends Exception>... expected ) 211 { 212 final var retValue = checkIfExpected( stream( requireNonNullArgument( expected, "expected" ) ) ); 213 214 //---* Done *---------------------------------------------------------- 215 return retValue; 216 } // checkIfExpected() 217 218 /** 219 * Returns the contained Exception if it is of the given type; otherwise 220 * a 221 * {@link RuntimeException} 222 * is thrown. 223 * 224 * @param <T> The expected Exception type. 225 * @param exceptionType The expected type. 226 * @return The contained Exception. 227 * @throws RuntimeException This is in fact an 228 * {@link UnexpectedException} that is thrown when the contained 229 * Exception was not expected. 230 */ 231 @SuppressWarnings( {"ProhibitedExceptionDeclared", "OverlyBroadThrowsClause"} ) 232 public final <T> T getCheckedCause( final Class<T> exceptionType ) throws RuntimeException 233 { 234 final var cause = getCause(); 235 if( !requireNonNullArgument( exceptionType, "exceptionType" ).isInstance( cause ) ) 236 { 237 throw new UnexpectedException(); 238 } 239 240 final var retValue = exceptionType.cast( cause ); 241 242 //---* Done *---------------------------------------------------------- 243 return retValue; 244 } // getCheckedCause() 245} 246// class LambdaContainerException 247 248/* 249 * End of File 250 */