Compilation error using Java generics and interfaces - java

I'm attempting to make a Repository interface, that our business logic can use, with the idea that if we decide to change the data source that backs the repositories, that the business logic would not be affected. We have many clients that would be using this library, so we have begun making a suite of controllers that can be reused among clients. This is the repository interface:
package //redacted
import java.util.Collection;
import java.util.List;
public interface Repository<T extends Object, R extends RepositoryQuery<T>> {
T add(T entity);
Collection<T> add(Collection<T> entities);
void remove(T entity);
Collection<T> getAll();
T get(Integer id) throws InvalidEntryException;
List<T> get(Collection<Integer> ids);
List<T> query(R query);
List<T> query(T query);
}
This is the controller I'm having problems with:
package //redacted
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import /* redacted */.entities.pointbank.PointBankBalance;
import /* redacted */.entities.user.User;
import /* redacted */.queries.PendingPointsBalance;
import /* redacted */.repository.Repository;
import /* redacted */.repository.RepositoryQuery;
public class RetrievePendingPointBalance {
private Repository<PointBankBalance, ? extends RepositoryQuery<PointBankBalance>> repository;
private Constructor<? extends PendingPointsBalance> pendingQuery;
public RetrievePendingPointBalance(Repository<PointBankBalance, ? extends RepositoryQuery<PointBankBalance>> repository,
Constructor<? extends PendingPointsBalance> pendingQuery) {
this.repository = repository;
this.pendingQuery = pendingQuery;
}
public PointBankBalance execute(User user) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
PendingPointsBalance query = pendingQuery.newInstance();
query.setUser(user);
return repository.query(query).get(0);
}
}
I'm trying to create RepositoryQuery, which is what will be responsible for handling more than just CRUD operations. Here is the interface for that:
package //redacted
import java.util.List;
public interface RepositoryQuery<T extends Object> {
List<T> execute();
}
The idea there, is that each query can extend can extend the interface with setters for the parameters it will need. Then each implementation can extend that query interface with specifics of what it will need (example: setJdbcTemplate(...) )
On the controller above though, the line:
return repository.query(query).get(0);
is a compilation error, and I do not know why. Is my approach inherently flawed, or am I just missing something?
Full Error:
[ERROR] /Users/redacted/src/main/java/com/redacted/controllers/RetrievePendingPointBalance.java:[26,28] no suitable method found for query(com.redacted.queries.PendingPointsBalance)
method com.redated.repository.Repository.query(capture#1 of ? extends com.redated.repository.RepositoryQuery<com.redacted.entities.pointbank.PointBankBalance>) is not applicable
(argument mismatch; com.redacted.queries.PendingPointsBalance cannot be converted to capture#1 of ? extends com.redacted.repository.RepositoryQuery<com.redacted.entities.pointbank.PointBankBalance>)
method com.redacted.repository.Repository.query(com.redacted.entities.pointbank.PointBankBalance) is not applicable
(argument mismatch; com.redacted.queries.PendingPointsBalance cannot be converted to com.redacted.entities.pointbank.PointBankBalance)
And my error from Eclipse:
Edit: PointBankBalance interface
package //redacted
import com./* redacted */.entities.pointbank.PointBankBalance;
import com./* redacted */.entities.user.User;
import com./* redacted */.repository.RepositoryQuery;
public interface PendingPointsBalance extends RepositoryQuery<PointBankBalance> {
void setUser(User user);
}

Your problem is that the method takes an R and you defined R as ? extends RepositoryQuery<PointBankBalance> in the repository field declaration. It's uncertain which subclass ? refers to, so the compiler won't let you use a specific one. You could potentially set that field to an instance of Repository<PointBankBalance, AnotherPointsBalance>, in which case PendingPointsBalance wouldn't be a valid argument to its query method.
One way to fix it would be replacing Repository<PointBankBalance, ? extends RepositoryQuery<PointBankBalance>> with Repository<PointBankBalance, PendingPointsBalance>.
Or you could simplify things by removing the R parameter entirely:
public interface Repository<T extends Object> {
...
List<T> query(RepositoryQuery<T> query);
List<T> query(T query);
}
Then just replace Repository<PointBankBalance, ? extends RepositoryQuery<PointBankBalance>> with Repository<PointBankBalance>.

Related

Log the actual repository class instead of CrudRepository of JPARepository

Pre note: Really surprised and annoyed that why cant I find this simple thing.
public Object interceptMethod(ProceedingJoinPoint joinPoint) throws Throwable {
joinPoint.getTarget(); //returns SimpleJpaRepository
joinPoint.getSignature().getDeclaringTypeName(); //Returns CrudRepository
}
How do I get the actual repository name ?
One way of getting the actual repository name
#Before("execution(* org.sec3.jpa.bean.*.deleteById(*)) && target(bean)")
public void getRepositoryName(JoinPoint jp , Object bean ) throws Exception {
Advised advised = (Advised) bean;
for(Class<?> clazz : advised.getProxiedInterfaces())
System.out.println(clazz);
}
will print
interface org.sec3.jpa.bean.TestEmployeeRepository
interface org.springframework.data.repository.Repository
interface org.springframework.transaction.interceptor.TransactionalProxy
TestEmployeeRepository
package org.sec3.jpa.bean;
import org.springframework.data.repository.CrudRepository;
public interface TestEmployeeRepository extends JpaRepository<JpaEmployee, Long> {
}
My answer to a similar question here

Getting Error while using Generic Dao in room with java

when I try to use Generic Dao in android Room I get this Erro :
Cannot use unbound generics in query methods. It must be bound to a type through base Dao class.
import android.arch.lifecycle.LiveData;
import android.arch.persistence.room.Dao;
import android.arch.persistence.room.Delete;
import android.arch.persistence.room.Insert;
import android.arch.persistence.room.RawQuery;
import android.arch.persistence.room.Update;
import java.util.List;
#Dao
public interface BaseDaoAccess<T> {
#Insert
Long Insert(T entity);
#Update
void Update(T entity);
#Delete
void Delete(T entity);
#RawQuery
LiveData<List<T>> RowQuery(String query);
}
Due to type erasure, Java can't tell at runtime what T you mean. You can provide this information by creating a subtype that has the T bound to a specific type, such as this:
public interface CarDao extends BaseDaoAccess<Car> { }

Overriding the generic abstract method of `ParamConvertorProvider`

I am providing an implementation of ParamConverterProvider in a JAX-RS application. This implementation provides gives a definition of the abstract method in the interface with signature as:
public <T> ParamConverter<T> getConverter(Class<T> rawType, Type genericType, Annotation annotations[]);
I am playing with an online tutorial and modified the implementation as follows.
package org.koushik.javabrains.rest;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.Calendar;
import javax.ws.rs.ext.ParamConverter;
import javax.ws.rs.ext.ParamConverterProvider;
import javax.ws.rs.ext.Provider;
import org.koushik.javabrains.rest.MyDate;
#Provider
public class MyDateConverterProvider implements ParamConverterProvider {
#Override
public <MyDate> ParamConverter<MyDate> getConverter(final Class<MyDate> rawType, Type genericType, Annotation[] annotations) {
return new ParamConverter<MyDate>() {
#Override
public MyDate fromString(String value) {
Calendar requestedDate = Calendar.getInstance();
if ("tomorrow".equalsIgnoreCase(value)) {
requestedDate.add(Calendar.DATE, 1);
}
else if ("yesterday".equalsIgnoreCase(value)) {
requestedDate.add(Calendar.DATE, -1);
}
org.koushik.javabrains.rest.MyDate myDate = new org.koushik.javabrains.rest.MyDate();
myDate.setDate(requestedDate.get(Calendar.DATE));
myDate.setMonth(requestedDate.get(Calendar.MONTH));
myDate.setYear(requestedDate.get(Calendar.YEAR));
return rawType.cast(myDate);
}
#Override
public String toString(MyDate myBean) {
if (myBean == null) {
return null;
}
return myBean.toString();
}
};
}
}
I have a few questions:
Why do i need to provide the entire package name when instatiatiating the type T that will be returned from getConverter. It has the same package name as this current class, still I need to write the fully qualified name , else I get a compilation error , cannot instantiate.
org.koushik.javabrains.rest.MyDate myDate = new org.koushik.javabrains.rest.MyDate();. It doesn't make a difference if I import this at the top. import org.koushik.javabrains.rest.MyDate;
At the start of the method I get this warning The type parameter MyDate is hiding the type MyDate.
This provider works fine and am able to cast request path params, but am still wondering how can I avoid the above two.
You don't. You already import the class, therefore you should not require the fully qualified name.
Don't re-parameterize your overridden method. Just remove the type parameter from the method signature. Note its absence in the below snippet.
#Override
public ParamConverter<MyDate> getConverter(final Class<MyDate> rawType, Type genericType, Annotation[] annotations) {

Can't compile sub class that implements abstract method from base class

Having problems compiling sub classes of a base class that I've defined that has a single method and each sub class implements the abstract base method, but javac is saying that they don't even though it is quite clearly defined in the sub class.
DbModel.java (the base class)
package com.manodestra.db;
import java.sql.ResultSet;
import java.sql.SQLException;
public abstract class DbModel<T extends DbModel> extends Model {
abstract T newInstance(ResultSet rs) throws SQLException;
}
DbModel extends Model, which only has a generic toString method.
MenuPermissions.java (the sub class)
package com.manodestra.csa.db.model.configNew;
import com.manodestra.db.DbModel;
import java.sql.Date;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
public class MenuPermissions extends DbModel<MenuPermissions> {
private final String menuId;
private final String userLevel;
public MenuPermissions(
String menuId,
String userLevel
) {
this.menuId = menuId;
this.userLevel = userLevel;
}
public String getMenuId() {
return this.menuId;
}
public String getUserLevel() {
return this.userLevel;
}
public MenuPermissions newInstance(ResultSet rs) throws SQLException {
return new MenuPermissions(
rs.getString("menu_id"),
rs.getString("user_level")
);
}
}
Compilation Error
[javac] Compiling 487 source files to C:\Media\Code\manodestra_java\bin
[javac] C:\Media\Code\manodestra_java\src\com\manodestra\csa\db\model\configNew\MenuPermissions.java:10:
error: MenuPermissions is not abstract
and does not override abstract method newInstance(ResultSet) in DbModel
[javac] public class MenuPermissions extends DbModel<MenuPermissions> {
[javac] ^
Anyone see what the problem is here? I'm guessing that I'm overlooking something really simple.
Further info on requirements:
I'm building an entity framework that generates model objects from a given database. MenuPermissions above is one such model object (auto-generated by a class that I've written called GenerateModel). I want each model to have a method that will allow me to get a new instance of each objecct type based on a resultset, which will populate the object accordingly and return it. Ideally, it should be a static method, but I've tried it as a concrete method for the moment as I need to enforce its existence in each sub class of DbModel. Hope that makes sense.
Your abstract method newInstance has package access. I don't know if that was intended but if it is in a different package then you would get an error.
Edit:
So the abstract method in the parent class can not be resolved since it is not declared a public. A possible remedy is to add public to the method definition or move the child class into the same package as the parent class :-D

Method intercepted twice even though it was called once

In the following code snippet I'm calling the method doStuff once on an instance of Subclass. However it is intercepted twice.
Note that doStuff was defined in the parent class SuperClass. If doStuff was defined in SubClass the interception logic would work as expected: only one interception.
Am I using Byte Buddy incorrectly?
package com.test;
import static net.bytebuddy.matcher.ElementMatchers.any;
import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;
import java.util.concurrent.Callable;
import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType.Builder;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.SuperCall;
import org.junit.Test;
public class ReproBugTest {
#Test
public void reproBug() {
new AgentBuilder.Default().type(nameStartsWith("com.test"))
.transform(new AgentBuilder.Transformer() {
#Override
public Builder<?> transform(
Builder<?> builder,
TypeDescription td) {
return builder.method(any())
.intercept(
MethodDelegation.to(MethodInterceptor.class));
}
})
.installOn(
ByteBuddyAgent.installOnOpenJDK());
SubClass subClass = new SubClass();
subClass.doStuff();
}
}
class SuperClass {
public void doStuff() {
System.out.println("Doing stuff...");
}
}
class SubClass extends SuperClass {
}
class MethodInterceptor {
#RuntimeType
public static Object intercept(#SuperCall Callable<?> zuper)
throws Exception {
// Intercepted twice, bug?
System.out.println("Intercepted");
Object returnValue = zuper.call();
return returnValue;
}
}
You are intercepting the method call for every type, i.e. for both Subclass and SuperClass. You need to further specify your interceptor for what methods to intercept. In you case, you only want to intercept methods if they are declared by a given type.
This is easy to implement. Instead of builder.method(any()), you should intercept builder.method(isDeclaredBy(td)). This way, a method is only intercepted if it is declared by the intercepted type.
Finally, I can see from, your source code that you are using an older version of Byte Buddy. Version 0.7-rc6 runs stable, has additional features and fixes several bugs. (However, some APIs still need to be changed.)

Categories

Resources