001/* 002 * ============================================================================ 003 * Copyright © 2002-2026 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 exceptions 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 * <div class="source-container"><pre>… 046 * Appendable appendable = … 047 * Consumer appender = s -> appendable.append( s ); 048 * appender.accept( "…" ); 049 * …</pre></div> 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 * <div class="source-container"><pre>… 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 * …</pre></div> 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 * all 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 * @author Thomas Thrien - thomas.thrien@tquadrat.org 090 * @version $Id: LambdaContainerException.java 1293 2026-10-16 13:12:15Z tquadrat $ 091 * @since 0.1.0 092 * 093 * @UMLGraph.link 094 */ 095@ClassVersion( sourceVersion = "$Id: LambdaContainerException.java 1293 2026-10-16 13:12:15Z tquadrat $" ) 096@API( status = STABLE, since = "0.1.0" ) 097public final class LambdaContainerException extends CheckedExceptionWrapper 098{ 099 /*---------------*\ 100 ====** Inner Classes **==================================================== 101 \*---------------*/ 102 /** 103 * <p>{@summary The exception that is thrown when the container holds an unexpected 104 * exception.}</p> 105 * 106 * @author Thomas Thrien - thomas.thrien@tquadrat.org 107 * @version $Id: LambdaContainerException.java 1293 2026-10-16 13:12:15Z tquadrat $ 108 * @since 0.1.0 109 * 110 * @UMLGraph.link 111 */ 112 private final class UnexpectedException extends RuntimeException 113 { 114 /*------------------------*\ 115 ====** Static Initialisations **======================================= 116 \*------------------------*/ 117 /** 118 * The serial version UID for objects of this class: {@value}. 119 */ 120 @Serial 121 private static final long serialVersionUID = 1L; 122 123 /*--------------*\ 124 ====** Constructors **================================================= 125 \*--------------*/ 126 /** 127 * <p>{@summary {@constructor}}</p> 128 */ 129 public UnexpectedException() 130 { 131 super( "The Exception '%s' was not expected".formatted( LambdaContainerException.this.getCause().getClass().getName() ) ); 132 setStackTrace( LambdaContainerException.this.getCause().getStackTrace() ); 133 } // UnexpectedException() 134 135 /*---------*\ 136 ====** Methods **====================================================== 137 \*---------*/ 138 /** 139 * {@inheritDoc} 140 * 141 * @see Throwable#getCause() 142 */ 143 @Override 144 public final synchronized Throwable getCause() { return LambdaContainerException.this.getCause(); } 145 } 146 // class UnexpectedException 147 148 /*------------------------*\ 149 ====** Static Initialisations **=========================================== 150 \*------------------------*/ 151 /** 152 * The serial version UID for objects of this class: {@value}. 153 * 154 * @hidden 155 */ 156 @Serial 157 private static final long serialVersionUID = 1L; 158 159 /*--------------*\ 160 ====** Constructors **===================================================== 161 \*--------------*/ 162 /** 163 * <p>{@summary Creates a new {@class} instance for the given 164 * exception.}</p> 165 * 166 * @param e The exception to wrap; <i>cannot</i> be {@null}. 167 */ 168 public LambdaContainerException( final Exception e ) 169 { 170 super( requireNonNullArgument( e, "e" ) ); 171 } // LambdaContainerException() 172 173 /*---------*\ 174 ====** Methods **========================================================== 175 \*---------*/ 176 /** 177 * <p>{@summary Checks whether the contained Exception is somehow 178 * expected.}</p> 179 * 180 * @param expected The expected exceptions. 181 * @return {@true} if the contained Exception is among the list of 182 * expected exceptions, {@false} otherwise. 183 */ 184 public final boolean checkIfExpected( final Stream<Class<? extends Exception>> expected ) 185 { 186 final var cause = getCause(); 187 final var retValue = requireNonNullArgument( expected, "expected" ).anyMatch( e -> e.isInstance( cause ) ); 188 189 //---* Done *---------------------------------------------------------- 190 return retValue; 191 } // checkIfExpected() 192 193 /** 194 * <p>{@summary Checks whether the contained Exception is somehow 195 * expected.}</p> 196 * 197 * @param expected The expected exceptions. 198 * @return {@true} if the contained Exception is among the list of 199 * expected exceptions, {@false} otherwise. 200 */ 201 public final boolean checkIfExpected( final Collection<Class<? extends Exception>> expected ) 202 { 203 final var retValue = checkIfExpected( requireNonNullArgument( expected, "expected" ).stream() ); 204 205 //---* Done *---------------------------------------------------------- 206 return retValue; 207 } // checkIfExpected() 208 209 /** 210 * <p>{@summary Checks whether the contained Exception is somehow 211 * expected.}</p> 212 * 213 * @param expected The expected exceptions. 214 * @return {@true} if the contained Exception is among the list of 215 * expected exceptions, {@false} otherwise. 216 */ 217 @SafeVarargs 218 public final boolean checkIfExpected( final Class<? extends Exception>... expected ) 219 { 220 final var retValue = checkIfExpected( stream( requireNonNullArgument( expected, "expected" ) ) ); 221 222 //---* Done *---------------------------------------------------------- 223 return retValue; 224 } // checkIfExpected() 225 226 /** 227 * <p>{@summary Returns the contained Exception if it is of the given 228 * type; otherwise a 229 * {@link RuntimeException} 230 * is thrown.}</p> 231 * 232 * @param <T> The expected Exception type. 233 * @param exceptionType The expected type. 234 * @return The contained Exception. 235 * @throws RuntimeException This is in fact an 236 * {@link UnexpectedException} that is thrown when the contained 237 * Exception was not expected. 238 */ 239 @SuppressWarnings( {"ProhibitedExceptionDeclared", "OverlyBroadThrowsClause"} ) 240 public final <T> T getCheckedCause( final Class<T> exceptionType ) throws RuntimeException 241 { 242 final var cause = getCause(); 243 if( !requireNonNullArgument( exceptionType, "exceptionType" ).isInstance( cause ) ) 244 { 245 throw new UnexpectedException(); 246 } 247 248 final var retValue = exceptionType.cast( cause ); 249 250 //---* Done *---------------------------------------------------------- 251 return retValue; 252 } // getCheckedCause() 253} 254// class LambdaContainerException 255 256/* 257 * End of File 258 */