Search code examples
javadesign-patternsreflectionfunctional-programminghashmap

How to call a function that will create an object of a class if required?


Context:

I've found a bug in a selenium framework I'm working on, in which the Web Browser (at least Chrome) crashes unexpectedly with no notification.
As a patch, I'm reinitializing the WebDriver so it keeps working, but right now I'm creating a new EdgeDriver and I want to create a new WebDriver of the same type it was before (the one that crashed).

I came up with this approach:

driver = Map.of(
            ChromeDriver.class, getFunction(ChromeDriver.class),
            EdgeDriver.class, getFunction(EdgeDriver.class),
            FirefoxDriver.class, getFunction(FirefoxDriver.class),
            OperaDriver.class, getFunction(OperaDriver.class)
      ).entrySet().stream().filter((e) -> e.getKey().isInstance(driver))
      .map((e)->e.getValue().identity()).findFirst().orElseThrow(() -> new RuntimeException("WebDriver not detected"));

...

@SneakyThrows
static Function<Class<? extends RemoteWebDriver>, RemoteWebDriver> getFunction(Class<? extends RemoteWebDriver> driverClass){
   return c -> {
            try {
               return c.getConstructor().newInstance();
            } catch (IllegalAccessException | InstantiationException e) {
               throw new RuntimeException(e);
            }
         };
}

The problem is that I can't use this type of call

e.getValue().identity()

Do you know how can I achieve this?

I'm using the Map approach so I don't have to specify a bunch of if's with all the code inside.
And I'm using a separate method returning a Function so I don't have to spend resources in creating the instances before (and if they) are required.

I have faced this problem before and I'm pretty sure I'm going to keep facing it, I have almost no idea what I'm writing in the functional programming section because I'm very new to it. I hope I'm on the right track but honestly, I doubt it.

As an extra tip if you can provide a way to become an expert in functional programming in Java will be great because I've been looking for a course or any resources deep enough and I have not been able to find anything so far. Not only will solve this problem but it will help me to solve any future problems like this.


Solution

  • Since getFunction(Class) returns a Function<Class<? extends RemoteWebDriver>, RemoteWebDriver> you'd need to call it like e.getValue().apply(driver.getClass()). However, Supplier<RemoteWebDriver> seems to be more appropriate:

    @SneakyThrows
    static Supplier<RemoteWebDriver> getSupplier(Class<? extends RemoteWebDriver> driverClass){
      return () -> {
            try {
               return driverClass.getConstructor().newInstance();
            } catch (IllegalAccessException | InstantiationException e) {
               throw new RuntimeException(e);
            }
         };
    }
    

    Then use it like ....map((e)->e.getValue().get()).

    What did I change appart from the method name?

    • return a Supplier<RemoveWebDriver> which has the get() method to execute it and return the result
    • since suppliers don't take arguments the lambda changes from c -> { ... } to () -> { ... }.
    • c is not available anymore but you'd want to "bind" the argument to getSupplier() to the lambda anyway which is possible since it's "effectively final". That means instead of c.getConstructor() you can use driverClass.getConstructor()

    However, all that complexity isn't needed - you can just put the suppliers directly into the map:

    //the <Class<? extends RemoteWebDriver>, Supplier<? extends RemoteWebDriver>> is needed to help the compiler infer the generic types
    Map.<Class<? extends RemoteWebDriver>, Supplier<? extends RemoteWebDriver>>of(ChromeDriver.class, ChromeDriver::new, 
            EdgeDriver.class, EdgeDriver::new,
            FirefoxDriver.class, FirefoxDriver::new,
            OperaDriver.class, OperaDriver::new)
            ...
    

    If you don't like this <Class<? extends RemoteWebDriver>, Supplier<? extends RemoteWebDriver>> you could help the compiler with a small method that has the type "baked in":

    static Supplier<? extends RemoteWebDriver> supply(Supplier<? extends I> s) { return s ; }
    
    Map.of(ChromeDriver.class, supply(ChromeDriver::new), 
            EdgeDriver.class, supply(EdgeDriver::new),
            FirefoxDriver.class, supply(FirefoxDriver::new),
            OperaDriver.class, supply(OperaDriver::new))
            ...