Reputation: 2268
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);
}
}
Upvotes: 3
Views: 1162
Reputation: 1747
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.
Upvotes: 1
Reputation: 15250
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.
Upvotes: 3