The problem is as follows:
There is an ErrorMessageBuilder, instances of the class that implements this interface can generate messages based on exceptions that are passed to them, in addition, there is an option to check if the class can handle the passed exception or not.
interface ErrorMessageBuilder<T extends Exception> {
String buildMessage(T exception);
boolean canHandle(Class<? extends Exception> clazz);
}
Next, there is the class ExceptionHandlerPair which is used to create pairs (Exception+Handler)
class ExceptionHandlerPair<T extends Exception>{
private final Class<T> clazz;
private final ErrorMessageBuilder<? extends Exception> builder;
public ExceptionHandlerPair(Class<T> clazz, ErrorMessageBuilder<? extends Exception> builder) {
if(!builder.canHandle(clazz)){
throw new IllegalArgumentException("Handler can't handle this exception");
}
this.clazz = clazz;
this.builder = builder;
}
public boolean canHandle(Class<? extends Exception> clazz) {
return this.clazz.isAssignableFrom(clazz);
}
public ErrorMessageBuilder<? extends Exception> getBuilder() {
return builder;
}
}
After that I try to create a Bilder and create pairs. For example I create a RuntimeExceptionErrorMessageBuilder which can handle any RuntimeException:
class RuntimeExceptionErrorMessageBuilder implements ErrorMessageBuilder<RuntimeException> {
public String buildMessage(RuntimeException exception) {
return "Some runtime exception";
}
@Override
public boolean canHandle(Class<? extends Exception> clazz) {
return RuntimeException.class.isAssignableFrom(clazz);
}
}
public class SomeClass {
public static void main(String[] args) {
var builder = new RuntimeExceptionErrorMessageBuilder();
// builder.canHandle(NullPointerException.class); // ? -> true
// builder.canHandle(IllegalArgumentException.class); // ? -> true
// builder.canHandle(Exception.class); // ? -> false
var pair = new ExceptionHandlerPair<>(NullPointerException.class, builder);
if(pair.canHandle(NullPointerException.class)){ // ? -> true
var message = pair.getBuilder().buildMessage(new NullPointerException()); // <- error Required type: capture of ? extends Exception, Provided: IllegalArgumentException
System.out.println(message);
}
if(pair.canHandle(IllegalArgumentException.class)){ // ? -> true
var message = pair.getBuilder().buildMessage(new IllegalArgumentException()); // <- error Required type: capture of ? extends Exception, Provided: IllegalArgumentException
System.out.println(message);
}
}
}
We get an error that the buildMessage(x) parameter gives an error that the x parameter should be a descendant of Exception, but IllegalArgumentException is a descendant, what is the problem?
Okay, I thought that if this restriction applies to parameters, then let's add an ExceptionWrapper like this:
class ErrorWrapper<T extends Exception>{
private final T exception;
public ErrorWrapper(T exception) {
this.exception = exception;
}
public T getException() {
return exception;
}
}
After that modify ErrorMessageBuilder and RuntimeExceptionErrorMessageBuilder with ErrorWrapper in mind:
interface ErrorMessageBuilder<T extends Exception> {
String buildMessage(ErrorWrapper<T> exception);
boolean canHandle(Class<? extends Exception> clazz);
}
class RuntimeExceptionErrorMessageBuilder implements ErrorMessageBuilder<RuntimeException> {
public String buildMessage(ErrorWrapper<RuntimeException> exception) {
return "Some runtime exception";
}
@Override
public boolean canHandle(Class<? extends Exception> clazz) {
return RuntimeException.class.isAssignableFrom(clazz);
}
}
Then change the main and try to create messages with the changes, but it does not work even if I try to fool java I get: error Required type: ErrorWrapper <capture of ? extends Exception>, Provided: ErrorWrapper <capture of ? extends Exception>
public class SomeClassWrapper {
public static void main(String[] args) {
var builder = new RuntimeExceptionErrorMessageBuilder();
ExceptionHandlerPair<NullPointerException> pair = new ExceptionHandlerPair<>(NullPointerException.class, builder);
if(pair.canHandle(NullPointerException.class)){ // ? -> true
ErrorWrapper<NullPointerException> npeWrapper = new ErrorWrapper<>(new NullPointerException());
var message = pair.getBuilder().buildMessage(npeWrapper); // <- error Required type: ErrorWrapper <capture of ? extends Exception>, Provided: ErrorWrapper<NullPointerException
System.out.println(message);
}
//
if(pair.canHandle(NullPointerException.class)){ // ? -> true
// and here we'll try to swoon over Java!!! :)
ErrorWrapper<? extends Exception> npeWrapper = new ErrorWrapper<>(new NullPointerException());
var message = pair.getBuilder().buildMessage(npeWrapper); // <- error Required type: ErrorWrapper <capture of ? extends Exception>, Provided: ErrorWrapper <capture of ? extends Exception>
System.out.println(message);
}
}
}
I would be grateful if someone could explain this behavior!!! Thanks in advance!