I'm currently working on a commandline-client for automated testing. For this, I want to be able to perform a request just by a given name, so I have to use reflection.
However, I get the following error:
NoSuchMethodException: java.util.ArrayList.add(some.path.Foo)
on this code:
Object job = Class.forName(sClassName).getConstructor().newInstance();
List<?> jobObject = (List<?>) Request.getClass().getDeclaredMethod(sMethodName).invoke(Request);
jobObject.getClass().getDeclaredMethod("add", job.getClass()).invoke(Request, job);
On the other hand, the following code works (but is not the solution as Foo should be dynamic, not hardcoded)
Object job = Class.forName(sClassName).getConstructor().newInstance();
List<Foo> jobObject = (List<Foo>) Request.getClass().getDeclaredMethod(sMethodName)).invoke(Request);
jobObject.add((Foo)job);
Sidenote:
sClassName = some.path.Foo
.getDeclaredMethod(sMethod).invoke(Request) returns an Object (which is a List< Foo>)
What I want to achieve is to get the code working with dynamic inputs (for example Bar instead of Foo)
Due to type erasure, the method add of a List expects at runtime an object of type Object (in other words its signature is boolean add(Object o)) moreover you need to invoke it on jobObject not on Request as it is not a List, so your code should rather be:
jobObject.getClass().getDeclaredMethod("add", Object.class).invoke(jobObject, job);
Unless I miss something for me you don't even need reflection in this case
List<Object> jobObject = (List<Object>)...
jobObject.add(job);
Related
I'm attempting to dynamically add services that are passed into the function concatOrNotFound
import akka.grpc.javadsl.ServiceHandler;
import akka.http.javadsl.model.HttpRequest;
import akka.http.javadsl.model.HttpResponse;
import akka.japi.function.Function;
Function<HttpRequest, CompletionStage<HttpResponse>> greeterService =
GreeterServiceHandlerFactory.create(new GreeterServiceImpl(mat), sys);
Function<HttpRequest, CompletionStage<HttpResponse>> echoService =
EchoServiceHandlerFactory.create(new EchoServiceImpl(), sys);
#SuppressWarnings("unchecked")
Function<HttpRequest, CompletionStage<HttpResponse>> serviceHandlers =
ServiceHandler.concatOrNotFound(greeterService, echoService);
Http.get(sys)
.newServerAt("127.0.0.1", 8090)
.bind(serviceHandlers)
(https://doc.akka.io/docs/akka-grpc/current/server/walkthrough.html)
The concatOrNotFound has following method signature :
#scala.annotation.varargs
def concatOrNotFound(handlers : akka.japi.Function[akka.http.javadsl.model.HttpRequest, java.util.concurrent.CompletionStage[akka.http.javadsl.model.HttpResponse]]*) : akka.japi.Function[akka.http.javadsl.model.HttpRequest, java.util.concurrent.CompletionStage[akka.http.javadsl.model.HttpResponse]] = { /* compiled code */ }
How to pass greeterService & echoService into as List and then invoke ServiceHandler.concatOrNotFound on this List ?
As handlers is a varargs I've tried the following :
List serviceList = new ArrayList();
serviceList.add(eventService);
serviceList.add(echoService);
#SuppressWarnings("unchecked") // Calling varargs method with generic instances
final Function<HttpRequest, CompletionStage<HttpResponse>> serviceHandlers =
ServiceHandler.concatOrNotFound(l.toArray(new Function[serviceList.size()]));
But this fails with compiler error :
Cannot resolve method 'concatOrNotFound(java.lang.Object[])'
What I’m attempting to achieve is use a list of services that is converted to varargs to be passed into the concatOrNotFound method. The reason I want use a list is that the list will be populated based on conditional logic that will determine which services are added to the list.
The other option I’m considering is iterate over the list and invoke concatOrNotFound for each list element where each list element is a service.
Update:
This appears to enable passing the List into method with a varargs parameter:
List<akka.japi.Function<akka.http.javadsl.model.HttpRequest, java.util.concurrent.CompletionStage<akka.http.javadsl.model.HttpResponse>>> serviceList = new ArrayList();
serviceList.add(eventService);
serviceList.add(echoService);
final Function<HttpRequest, CompletionStage<HttpResponse>> serviceHandlers =
ServiceHandler.concatOrNotFound(Iterables.toArray(serviceList, akka.japi.Function.class));
Is explicitly typing the List type parameters to
List<akka.japi.Function<akka.http.javadsl.model.HttpRequest, java.util.concurrent.CompletionStage<akka.http.javadsl.model.HttpResponse>>>
required as a result of type erasure ?
I assume that in this case, scala sourced compiled byte code may differ from the actual intended written source code. It's one of the pitfalls of scala programming, and it's especially true with vararg Scala/Java interoperability.
Either that, or greeterService and echoService are inferred as different types with no common hierarchy, maybe due to vararg conversion itself. That's the cause for them being is inferred as Object types in your 1st attempt. Look at ServiceHandler's source code: JFunction is expected
For your second attempt, you're doing List serviceList = new ArrayList();
which again, infers the elements as Object instances and thus the compiler fails.
Is explicitly typing the List type parameters to
List<akka.japi.Function<akka.http.javadsl.model.HttpRequest, java.util.concurrent.CompletionStage<akka.http.javadsl.model.HttpResponse>>>
required as a result of type erasure ?
Yes it is required, but it's not related to type-erasure. Generic types are discarded as part of the compilation process. Providing the concrete element type simply allows the compiler to understand correct types are passed to function calls expecting them
I don't have the source code in front of me, but you can initially type convert your services to JFunction instances .you might have better luck with that. Another approach would be to gather them using a correctly typed JFunction array to begin with.
I have a class that, in essence, looks like this:
class Checkpointer {
public <Input,Output> Output runFunction(Input input, Function<Input,Output> function) {
Output output;
// Sometimes run the function, sometimes return an Output from a cache
return output
}
}
I would like to mock this class using Mockito doAnswer:
Checkpointer checkpointer; // mocked via #Mock annotation
Mockito
.doAnswer(/* ??? */)
.when(checkpointer)
.runFunction(Mockito.any(), Mockito.any());
The function I want to mock needs to be generic. Can this be done?
For example, my first attempt produced the following. Not only did I resort to Object as the type arguments for Function, but the compiler was still unhappy with unchecked casting:
Mockito.doAnswer((invocation) ->
{
// compiler is not happy with this cast V
Function<Object,Object> function = (Function<Object,Object>)invocation.getArguments()[1];
return function.apply(invocation.getArgument(0));
}).when(checkpointer).runFunction(Mockito.any(), Mockito.any());
If this can't be done, I think can try writing my own mock class extending the first and use Mockito.spy.
The problem here is that you insist on using getArguments, which returns an Object[]
Since you know the index of the Function argument, you can use getArgument(index), as you're doing the line after that.
final Function<String, String> argument = invocation.getArgument(1);
Is this what you're looking for? Type inference for the getArgument generic type is working fine.
If not, can you provide a more elaborate example?
There have been some questions answered on this before.
How can I pass a scala object reference around in Java
How can I use a Scala singleton object in Java?
But my problem is that I have nested scala objects, something like:
object Criteria {
object ActionCriteria {
case class Action (parameter: String) {
def this(parameter: String) = { this(paramerter) }
}
object Action {
def apply(parameter: String): Action = { apply(parameter) }
}
}
}
In java I then need to create a list of Actions. I have tried this... to no avail:
import Criteria.ActionCriteria.Action$
....
List<Criteria.ActionCriteria.Action$.MODULE$> actions = new ArrayList<>();
As well as a bunch of other combinations like adding $.MODULE$ with every object. Right now I am getting the following error:
error: cannot find symbol Criteria.ActionCriteria
List<Criteria$ActionCriteria$Action> actions = new ArrayList<>();
Seems to work fine. Found this with Scala REPL:
scala> classOf[Criteria.ActionCriteria.Action]
res1: Class[Criteria.ActionCriteria.Action] = class Criteria$ActionCriteria$Action
If you want the type of Action object, not case class (highly unlikely, but for the sake of completeness):
scala> Criteria.ActionCriteria.Action.getClass
res2: Class[_ <: Criteria.ActionCriteria.Action.type] = class Criteria$ActionCriteria$Action$
The difference is caused by Scala expecting Action to be a type in classOf[Action], so it returns the type corresponding to the case class. When you use Action in a context where a value is expected, it returns the singleton instance instead, so you can call standard Java method getClass to get the type of object Action.
In case you need other types:
Criteria$ cm = Criteria$.MODULE$;
Criteria.ActionCriteria$ cacm = Criteria.ActionCriteria$.MODULE$;
Criteria$ActionCriteria$Action$ cacam = Criteria$ActionCriteria$Action$.MODULE$;
Criteria$ActionCriteria$Action caca = new Criteria$ActionCriteria$Action("Foo");
Criteria.ActionCriteria$ is breaking the pattern here. Why? According to Iulian Dragos' comment under bug SI-2034 this is a special case:
since objects are "the equivalent of static" in the Java world, we
wanted to make it easier for Java code to use static inner classes.
When there's only one level of nesting, there's a guaranteed
companion: every top-level object gets a mirror class (if there isn't
one) that creates static forwarders to module methods (that's how one
can run a main method defined inside an object). Therefore, a
special case for one-level nesting: those classes use the flattened
name (without a $ suffix) as outer_name. So, Java code can say new Outer.Inner.
Summary
For every level of nesting other than first you replace . with $ in your class names
If the target type is also an object you add $ at the end
If you want an instance you add .MODULE$
I have Map declared as following:
Map<String, Object> data
I put a String in it and verify its value like this:
assertEquals("value", data.get("key"));
Now, I'd like to rewrite the verification to use assertThat instead of assertEquals. I've tried the following:
assertThat(data.get("key"), equalTo("value"));
And of course it didn't work because of type mismatch:
Wrong 2nd argument type. Found: 'org.hamcrest.Matcher<java.lang.String>', required: 'org.hamcrest.Matcher<? super java.lang.Object>' less...
Explicit type cast of the first argument to String helps, but I'd like to avoid it. For example assertEquals doesn't require type cast.
So, how can I check that the value, which was put into Map object, declared above, is equal to particular String, using the assertThat method?
The "more assertThat" way of doing things would be:
Map<String, Object> expectedData = Collections.singletonMap("key", "value");
asssertThat(data, is(expectedData));
Please note:
Maybe you need type hints for the call to singletonMap
Besides the is matcher, there are other matchers that would allow you to check that data contains your "expected" map data
For your specific problem: that is caused because how generics come into play here; it might be sufficient to use (String) data.get("key") - to tell the compiler that the "actual" argument is of type String.
In the end - I have no idea what your problem is. I wrote down this piece of code:
public void test() {
Map<String, Object> data = new HashMap<>();
data.put("key", "value");
assertThat(data.get("key"), is("value"));
Map<String, Object> expectedData = Collections.singletonMap("key", "value");
assertThat(data, is(expectedData));
}
It compiles fine, and the unit test runs and passes. In other words: actually I am unable to repro your problem.
try this
assertThat(data.get("key"), equalTo("value"))
or
assertThat(data.get("key"), CoreMatchers.equalTo("value"))
Having read the documentation on "Inline Maps" for Spring Expression Language, I am passing an object to a SpelExpressionParser and .toString()ing the Object, so to speak; however, in doing so, I am receiving the following error:
org.springframework.expression.spel.standard.SpelExpression cannot be
cast to java.util.Map
The Object passed to as the argument to the .parseExpression function is the result of the annotation #PreAuthorize("hasPermission(#object, {name: 'roofus', animal: 'dog'}) "affixed" to a method.
Through the PermissionEvaluator interface implementation, it is passed in its Object form to a method:
private boolean doSomething (Object animal) { //....
Within this method is found an #Autowired SpelExpressionEvaluator. This is used in the following way:
Map animalMap = (Map) parser.parseExpression(animal.toString());
Through debugging, I know that the .toString() method results in: {name=roofus, animal=dog}
Resulting in the aforementioned error. Am I missing something? The goal is to be able to pass in a "JSON"-esque String (as specified by the linked documentation) for evaluation purposes.
Your problem that parser.parseExpression can't return Map independently of circumstances .
Looks like you misunderstood the architecture a bit. The SpelParser is for building Expression object from the the String. After that you can evaluate that expression using one of its getValue() method.
So, only after the evaluation you can get your Map object:
Expression expression = parser.parseExpression("{name: 'roofus', animal: 'dog'}");
Map map = expression.getValue(Map.class);
animal is already a Map (notice the formatting in the OP of the .toString() result) and not in the correct format expected by the parser. In this case, you don't even need the parser if the objective is to retrieve information from the Map:
((Map<String, String>)animal).get("animal")
So, in response to the solution proposed by Artem Bilan, this would actually work:
Map animalMap = (Map) parser.parseExpression("{name: 'roofus', animal: 'dog'}").getValue();
However, again, what is actually received (and the cause of the problem in the OP) is: parser.parseExpression("{name=roofus, animal=dog}")