White_King
White_King

Reputation: 774

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.

Upvotes: 0

Views: 69

Answers (2)

Thomas
Thomas

Reputation: 88747

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))
        ...

Upvotes: 1

Alexander Ivanchenko
Alexander Ivanchenko

Reputation: 29038

You can access stored as a Value using method reference Map.Entry::getValue but it wouldn't be applicable.

Because in the map() you need a provide a Function that can dial with a Map.Entry, but your Function expects Class instance as an argument as an argument (i.e. the expected argument doesn't match).

So the correct function can be written as the following lambda expression:

e -> e.getValue().apply(e.getKey())

Example:

RemoteWebDriver target = // initializing the target driver
    
RemoteWebDriver 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(target))
    .map(e -> e.getValue().apply(e.getKey()))
    .findFirst()
    .orElseThrow(() -> new RuntimeException("WebDriver not detected"));

Upvotes: 1

Related Questions