Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Duplicate classes in different Java libraries leads to compilation errors

I am having trouble with the Appium java client because it seems they did weird things with their project.

Basically, they are using Selenium in their project which should work just fine but they copied one package from Selenium partly to their project (org.openqa.selenium) and made some small adaptions to the classes inside. Basically, they added generics to the interfaces. Now we have duplicate classes in the same package in different libraries which of course leads to problems.

I created a simple Gradle project to demonstrate that. Following my build.gradle:

plugins {
    id 'java-library'
}

dependencies {
    api 'io.appium:java-client:6.1.0'
}

repositories {
    jcenter()
}

And my class Interactions.java:

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;

public class Interactions {

    public static void touchWebElement(By by, WebDriver driver) {
        touchWebElement(driver.findElement(by), driver);
    }

    public static void touchWebElement(WebElement element, WebDriver driver) {
        // DO SOMETHING
    }
}

Now if I compile that project I get the following error:

The method touchWebElement(By, WebDriver) is ambiguous for the type Interactions    Interactions.java   line 8

I think it is ambiguous because the interface WebElement exists twice.

How can I fix that problem?

  • Using appium-client <= 4.0.0 would work, but I need a newer version.
  • Currently, I just deleted the duplicate package from the jar and included this jar into my project. I really just deleted it with 7zip. This clears that compilation error but I will probably soon face other problems because the appium jar is not complete and the appium project would not even compile without that package.
  • The Selenium guys probably won't change anything (https://github.com/SeleniumHQ/selenium/pull/863).
  • The Appium guys probably don't know how to fix that, so am I: https://github.com/appium/java-client/issues/1021

Solution:

With help of the accepted answer I was able to fix those issues. Although I needed to come up with a slightly different solution. The problem I faced was that classes which called my Interactions.java needed those casts too which would lead in 1000+ adaptions. To prevent this I changed my methods to take Object as parameter:

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;

public class Interactions {
    public static void touchWebElement(Object object, WebDriver driver){
        WebElement webElement = castObjectToWebElement(element, driver);
        //DO SOMETHING
    }

    private static WebElement castObjectToWebElement(Object object, WebDriver driver) {
        if (object instanceof WebElement) {
            return (WebElement) object;
        } else if (object instanceof By) {
            return driver.findElement((By) object);
        }
        throw new IllegalArgumentException("Invalid type");
    }
}

It might not be an optimal solution but it works and won't need changes in all our other classes and everyone can work with those Interaction methods as until now.

like image 656
AndiCover Avatar asked Feb 09 '19 08:02

AndiCover


1 Answers

The problem are not duplicate classes but the way generics are used. Here is a little MCVE replicating the situation in Appium's WebDriver class:

package de.scrum_master.stackoverflow;

public interface WebElement {}
package de.scrum_master.stackoverflow;

public interface WebDriver {
  <T extends WebElement> T findElement();
}
package de.scrum_master.stackoverflow;

public class Application {
  static WebDriver webDriver;

  static void myOverloadedMethod(String text) {}
  static void myOverloadedMethod(WebElement text) {}

  public static void main(String[] args) {
    // These 3 variants work
    myOverloadedMethod("test");
    myOverloadedMethod((WebElement) webDriver.findElement());
    WebElement webElement = webDriver.findElement();
    myOverloadedMethod(webElement);

    // This one does not work
    myOverloadedMethod(webDriver.findElement());
  }
}

Explanation: Due to type erasure doSomething's generic return type <T extends WebElement> evaluates to Object, so when trying to use that result for calling myOverloadedMethod(..) the compiler does not know which method to select.

Solution: You need to help by casting or explicitly declaring a type for a variable containing the method parameter.

P.S.: If you would modify the interface definition from interface WebDriver to interface WebDriver<T>, the compilation error would go away. But Appium's implementation does not do that, maybe because they want to stay as compatible(?) as possible to the original Selenium class. You have to ask them.


Update: Because the OP seems to have problems understanding my answer or to believe I did not try his sample code, which of course I used to reproduce and understand his problem:

package de.scrum_master.appium;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;

public class Interactions {
  public static void touchWebElement(WebElement element, WebDriver driver) {}

  public static void touchWebElement(By by, WebDriver driver) {
    // Works
    WebElement webElement = driver.findElement(by);
    touchWebElement(webElement, driver);
    // Works
    touchWebElement((WebElement) driver.findElement(by), driver);
    // Ambiguous due to type erasure -> does not work
    touchWebElement(driver.findElement(by), driver);
  }
}

There is absolutely no need rename methods, re-package any classes or perform other types of Maven/Gradle stunts here.

like image 75
kriegaex Avatar answered Nov 17 '22 19:11

kriegaex