Type mismatch error in method with generic return (Why do I have to do a cast to the generic type in this method, but not in similar ones?)

StackOverflow https://stackoverflow.com/questions/15887844

Question

I am getting the following error from a method in which I need a generic return type:

Description Resource    Path    Location    Type
Type mismatch: cannot convert from PageTypeOne to P SecuredPage.java

I can get rid of the error by casting the object to the generic type parameter, but I don't understand why I am required to do this in this particular method, but not in other similar methods I have written elsewhere in other classes.

The basic structure of the project is this:

A base class Page for unsecured web pages
A base class SecuredPage for all secured web pages

I had a similar question earlier today which was solved with help from the Stack Overflow community: Bound mismatch error and java generic method.

I am now having another problem with a similar method.

The base page class w/ helper method that builds pages is this:

public abstract class Page<T extends Page<T>> extends SlowLoadableComponent<T> {

    protected static final <T extends Page<T>> T constructPage(WebDriver driver, 
    int timeoutInSeconds, java.lang.Class<T> pageClass) 
    {
        Page<T> p = null;

        try {
            Constructor<T> pageConstructor = pageClass.getConstructor(
                WebDriver.class, String.class, Integer.TYPE);
            p = pageConstructor.newInstance(driver, driver.getCurrentUrl(), 
                timeoutInSeconds);
            p.get();

        } catch(Exception e) {

        }

        return pageClass.cast(p);       
    }
}

SecuredPage class is as follows:

public class SecuredPage<T extends SecuredPage<T>> extends Page<T> {

    .....
}

This is the method I am trying to implement from within SecuredPage -- I want to be able to have a single method which will return both types of pages that result from opening a link in a method in my web application:

public final <P extends SecuredPage<P>> P loadContext(final String context) throws
NoSuchElementException {
    Menu m = pageHeader.getMenu();
    WebElement link = m.clickLink(context);
    String linkHref = link.getAttribute("href");

    if (linkHref.contains("somematchtext")) {
            return Page.constructPage(getDriver(), getTimeoutInSeconds(),   
                PageTypeOne.class); <==== This is where I get the error
    } else if (linkHref.contains("someothermatchtext")) {
            return Page.constructPage(getDriver(), getTimeoutInSeconds(), 
                PageTypeTwo.class); <==== This is wheere I get the error
    }
 }

PageTypeOne and PageTypeTwo both extend SecuredPage:

public final class PageTypeOne extends SecuredPage<PageTypeOne> {
    .....
}


public final class PageTypeTwo extends SecuredPage<PageTypeTwo> {
    .....
}

I have a similar method in my LoginPage class which returns a LoginPage if someone attempts to log in with invalid credentials, and a HomePage (which extends SecuredPage), if they log in with valid credentials. I don't get this error with that method. Nor do I get the error with the constructPage method in my Page class, so I don't really understand why I am getting this error with my loadContext method in SecuredPage:

public final class LoginPage extends Page<LoginPage>  {

    public final HomePage loginWithGoodCredentials(final User user) {
        return login(user, HomePage.class);
    }

    public final LoginPage loginWithBadCredentials(final User user) {
        return login(user, LoginPage.class);
    }

    public final <T extends Page<T>> T login(final User user, final Class<T>     
        expectedPage) {
        enterUsername(user.getUsername());
        enterPassword(user.getPassword());
        loginButton.click();

        return Page.constructPage(getDriver(), getTimeoutInSeconds(), 
            expectedPage);
    }
}
Was it helpful?

Solution

Usually the compiler infer a method generic type (P in your case) from the method signature (loadContext in your case). But loadContext method doesn't use P on the parameters to help the compiler infer P.

Instead you are forcing the compiler to infer the type P from within the method. You want to make it believe is PageTypeOne. Is not working like this.

Imagine that someone specified the generic parameter on the method call:

securedpageInstance.<SomeCompliantClassWithPBounds>loadContext(...)

SomeCompliantClassWithPBounds might be different from PageTypeOne.

Your second example works because you have Class<T> on login method signature. The compiler knows who is T and is sure that the return type is the same.

OTHER TIPS

I agree with dcernahoschi's answer.

To go on, you may define loadContext as public final SecuredPage<? extends SecuredPage<?>> loadContext(...) and work with an "unknown" subclass of SecuredPage from here on out.
You could also change the code in loadContext to return (P) Page.constructPage(...); and later on, "trick" the compiler into allowing something like PageTypeOne page = loadContext(...);

I would highly suggest using the first approach, as the calling code should not know the outcome of loadContext(). If on the other hand, the calling code expects a certain page to be loaded, it should pass along the class of the page (and you get rid of the if/else in loadContext), as your login example does.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top