i have an issue on verifying a call to method of class under test, using the verify() method it tells that the call is not done to that method, this method is defined as abstract in super class (loadFile(String))
find bellow the code :
public abstract class FileParser {
public Iterator<String> loadFile(FileSettingsToSend fileSetting) {
System.out.println("file before staged");
try {
if(!movFile("staged",fileSetting))
return null;
System.out.println("file after move "+fileSetting.getFile().getAbsolutePath());
boolean isValidFormatFile = fileValidator.checkFileFormat(fileSetting);
if (!isValidFormatFile) {
System.out.println("file format is not valid");
return null;
}
return readBlock(fileSetting);
} catch (Exception e) {
System.out.println(e.getMessage());
return null;
} finally {
}
//return null;
}
public abstract Iterator<String> readBlock(FileSettingsToSend fileSettingsToSend)
throws JsonProcessingException, IOException;
}
public class JsonFileParser extends FileParser {
public final ObjectMapper mapper = new ObjectMapper();
#Autowired
public JsonFileParser(FileValidator jsonFileValidatorService, FileAttributeService fileAttributeService) {
super(jsonFileValidatorService, fileAttributeService);
}
#Override
public Iterator<String> readBlock(FileSettingsToSend fileSetting) throws JsonProcessingException, IOException {
ObjectMapper mapper = new ObjectMapper();
System.out.println("inside readBlock json implementation");
List<String> listAttribute = fileAttributeService.getAttributes(fileSetting.getDiretoryPath());
String[] blocDelimitor = fileAttributeService.getDelimitorRepositpry(fileSetting.getDiretoryPath());
System.out.println("after validator");
final JsonNode root = mapper.readTree(fileSetting.getFile());
if (root == null)
return null;
Iterator<JsonNode> nodeIterator = root.elements();
System.out.println("Data is " + root);
return new Iterator<String>() {
JsonNode node;
#Override
public boolean hasNext() {
return nodeIterator.hasNext();
}
#Override
public String next() {
int i = 0;
node = nodeIterator.next();
System.out.println("after nex " + node.toString());
Arrays.stream(blocDelimitor).forEach(e -> {
node = node.path(e);
System.out.println("inside next " + node.toString());
});
String result = null;
if (node.isArray()) {
System.out.println("It is Array");
for (JsonNode node1 : node) {
if (i != 0)
result = result + "," + listAttribute.stream().map(e -> e + "=" + node1.get(e))
.collect(Collectors.joining(","));
else
result = listAttribute.stream().map(e -> e + "=" + node1.get(e))
.collect(Collectors.joining(","));
i++;
}
} else
result = listAttribute.stream().map(e -> e + "=" + node.get(e)).collect(Collectors.joining(","));
return result;
}
};
}
Test method is :
#Mock
FileValidator jsonFileValidatorService;
#Mock
FileAttributeService fileAttributeService;
JsonFileParser jsonFileParserMock = new JsonFileParser(jsonFileValidatorService, fileAttributeService);
#Test
public void validatorNotTrue() throws JsonProcessingException, IOException{
when(jsonFileValidatorService.checkFileFormat( anyObject() )).thenReturn(true);
JsonFileParser jsonFileParser = Mockito.spy(jsonFileParserMock);
doReturn(true).when(jsonFileParser).movFile(anyString(),anyObject() );
assertNull(jsonFileParser.loadFile(null));
verify(jsonFileParser, times(1)).movFile(anyString(),anyObject());
assertTrue(jsonFileParser.movFile(anyString(), anyObject()));
assertTrue(jsonFileValidatorService.checkFileFormat( anyObject() ));
//exception.expect(Exception.class);
verify(jsonFileParser,times(1)).readBlock(anyObject();
}
#BeforeClass
public static void settingUp(){
}
#Before
public void initMock(){
MockitoAnnotations.initMocks(this);
}
the line verify(jsonFileParser,times(1)).readBlock(anyObject(); return false; meaning that the method loadFile of jsonfileParser not called
can you get your held to tell why it is not called.
Thank you.
This happens because you initialize the mocks after you create a JsonFileParser. Note that #Before method is executed after all the fields of your test class are initialized.
As a result, you pass null dependencies to the class. The invocation to the null FileValidator throws NullPointerException, but you swallow it in your catch block.
Generally it is advisable to verify the arguments you pass to your constructors and methods, to fail fast in case of an error. For example, Java comes with a handy Objects::requireNonNull method to verify that the passed parameters are non-null.
Similarly it's generally a bad practice to swallow every single exception. For instance, in your example, you expect IOException and JsonProcessingException to be thrown. It's better to catch these explicitly and let the program crash (or at least log a warning) for any other one.
Finally, mocks and spies are prone to overuse. Usually, it's enough to use fakes - dummy implementations of your interfaces. Depending on how much control you have over the code, you may also want to refactor it to avoid using a spy at all. Using one in a code you may freely change may signal an architectural problem.
Related
I want to create a Mock Library class that implements InvocationHandler interface from Java Reflection.
This is the template I have created:
import java.lang.reflect.*;
import java.util.*;
class MyMock implements InvocationHandler {
#Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// todo
}
public MyMock when(String method, Object[] args) {
// todo
}
public void thenReturn(Object val) {
// todo
}
}
The when and thenReturn methods are chained methods.
Then when method registers the given mock parameters.
thenReturn method registers the expected return values for the given mock parameters.
Also, I want to throw java.lang.IllegalArgumentException if the proxied interface calls methods or uses parameters that are not registered.
This is a sample interface:
interface CalcInterface {
int add(int a, int b);
String add(String a, String b);
String getValue();
}
Here we have two overloaded add methods.
This is a program to test the mock class I wanted to implement.
class TestApplication {
public static void main(String[] args) {
MyMock m = new MyMock();
CalcInterface ref = (CalcInterface) Proxy.newProxyInstance(MyMock.class.getClassLoader(), new Class[]{CalcInterface.class}, m);
m.when("add", new Object[]{1,2}).thenReturn(3);
m.when("add", new Object[]{"x","y"}).thenReturn("xy");
System.out.println(ref.add(1,2)); // prints 3
System.out.println(ref.add("x","y")); // prints "xy"
}
}
This is the code which I have implemented so far to check the methods in CalcInterface:
class MyMock implements InvocationHandler {
#Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
int n = args.length;
if(n == 2 && method.getName().equals("add")) {
Object o1 = args[0], o2 = args[1];
if((o1 instanceof String) && (o2 instanceof String)) {
String s1 = (String) o1, s2 = (String) o2;
return s1+ s2;
} else if((o1 instanceof Integer) && (o2 instanceof Integer)) {
int s1 = (Integer) o1, s2 = (Integer) o2;
return s1+ s2;
}
}
throw new IllegalArgumentException();
}
public MyMock when(String method, Object[] args) {
return this;
}
public void thenReturn(Object val) {
}
}
Here I am checking only for methods with the name add and having 2 arguments, with their type as String or Integer.
But I wanted to create this MyMock class in a general fashion, supporting different interfaces not just CalcInterface, and also supporting different methods not just the add method I implemented here.
You have to separate the builder logic from the object to build. The method when has to return something which remembers the arguments, so that the invocation of thenReturn still knows the context.
For example
public class MyMock implements InvocationHandler {
record Key(String name, List<?> arguments) {
Key { // stream().toList() creates an immutable list allowing null
arguments = arguments.stream().toList();
}
Key(String name, Object... arg) {
this(name, arg == null? List.of(): Arrays.stream(arg).toList());
}
}
final Map<Key, Function<Object[], Object>> rules = new HashMap<>();
#Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
var rule = rules.get(new Key(method.getName(), args));
if(rule == null) throw new IllegalStateException("No matching rule");
return rule.apply(args);
}
public record Rule(MyMock mock, Key key) {
public void thenReturn(Object val) {
var existing = mock.rules.putIfAbsent(key, arg -> val);
if(existing != null) throw new IllegalStateException("Rule already exist");
}
public void then(Function<Object[], Object> f) {
var existing = mock.rules.putIfAbsent(key, Objects.requireNonNull(f));
if(existing != null) throw new IllegalStateException("Rule already exist");
}
}
public Rule when(String method, Object... args) {
Key key = new Key(method, args);
if(rules.containsKey(key)) throw new IllegalStateException("Rule already exist");
return new Rule(this, key);
}
}
This is already capable of executing your example literally, but also supports something like
MyMock m = new MyMock();
CalcInterface ref = (CalcInterface) Proxy.newProxyInstance(
CalcInterface.class.getClassLoader(), new Class[]{CalcInterface.class}, m);
m.when("add", 1,2).thenReturn(3);
m.when("add", "x","y").thenReturn("xy");
AtomicInteger count = new AtomicInteger();
m.when("getValue").then(arg -> "getValue invoked " + count.incrementAndGet() + " times");
System.out.println(ref.add(1,2)); // prints 3
System.out.println(ref.add("x","y")); // prints "xy"
System.out.println(ref.getValue()); // prints getValue invoked 1 times
System.out.println(ref.getValue()); // prints getValue invoked 2 times
Note that when you want to add support for rules beyond simple value matching, a hash lookup will not work anymore. In that case you have to resort to a data structure you have to search linearly for a match.
The example above uses newer Java features like record classes but it shouldn’t be too hard to rewrite it for previous Java versions if required.
It’s also possible to redesign this code to use the real builder pattern, i.e. to use a builder to describe the configuration prior to creating the actual handler/mock instance. This allows the handler/mock to use an immutable state:
public class MyMock2 {
public static Builder builder() {
return new Builder();
}
public interface Rule {
Builder thenReturn(Object val);
Builder then(Function<Object[], Object> f);
}
public static class Builder {
final Map<Key, Function<Object[], Object>> rules = new HashMap<>();
public Rule when(String method, Object... args) {
Key key = new Key(method, args);
if(rules.containsKey(key))
throw new IllegalStateException("Rule already exist");
return new RuleImpl(this, key);
}
public <T> T build(Class<T> type) {
Map<Key, Function<Object[], Object>> rules = Map.copyOf(this.rules);
return type.cast(Proxy.newProxyInstance(type.getClassLoader(),
new Class[]{ type }, (proxy, method, args) -> {
var rule = rules.get(new Key(method.getName(), args));
if(rule == null) throw new IllegalStateException("No matching rule");
return rule.apply(args);
}));
}
}
record RuleImpl(MyMock2.Builder builder, Key key) implements Rule {
public Builder thenReturn(Object val) {
var existing = builder.rules.putIfAbsent(key, arg -> val);
if(existing != null) throw new IllegalStateException("Rule already exist");
return builder;
}
public Builder then(Function<Object[], Object> f) {
var existing = builder.rules.putIfAbsent(key, Objects.requireNonNull(f));
if(existing != null) throw new IllegalStateException("Rule already exist");
return builder;
}
}
record Key(String name, List<?> arguments) {
Key { // stream().toList() createns an immutable list allowing null
arguments = arguments.stream().toList();
}
Key(String name, Object... arg) {
this(name, arg == null? List.of(): Arrays.stream(arg).toList());
}
}
}
which can be used like
AtomicInteger count = new AtomicInteger();
CalcInterface ref = MyMock2.builder()
.when("add", 1,2).thenReturn(3)
.when("add", "x","y").thenReturn("xy")
.when("getValue")
.then(arg -> "getValue invoked " + count.incrementAndGet() + " times")
.build(CalcInterface.class);
System.out.println(ref.add(1,2)); // prints 3
System.out.println(ref.add("x","y")); // prints "xy"
System.out.println(ref.getValue()); // prints getValue invoked 1 times
System.out.println(ref.getValue()); // prints getValue invoked 2 times
InvocationHandler is used to invoke a method signature against an object instance (proxy) that's instantiated in memory (or a class, if you're tapping a static method), not to support methods that aren't in the supplied proxy's class at all, which is how you've implemented it here. That said, you can probably achieve what you're trying to do by holding the method signature you're trying to mock (as well as the value you want to return when the args match) in private variables.
This may work, with the proviso that I haven't done Java in a couple of years, so I may be a bit rusty:
class MyMock implements InvocationHandler {
private String methodName = null;
private Object[] supportedArgs = null;
private Object returnValue = null;
#Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// If we don't know when this mock is supposed to be used, it's useless
assert this.methodName != null: "when(method, args) hasn't been called against the mock yet!";
// Note that both args and supportedArgs will be null if the method signature has no params
if (method.getName().equals(this.methodName) && this.supportedArgs == args) {
return this.returnValue;
}
try {
return method.invoke(proxy, args);
}
catch (IllegalAccessException | InvocationTargetException innerException){
// The proxy didn't support the method either, so let's throw an IllegalArgumentException
throw new IllegalArgumentException("The supplied method signature isn't implemented in the proxy.");
}
}
public MyMock when(String method, Object[] args) {
this.methodName = method;
this.supportedArgs = args;
return this;
}
public void thenReturn(Object val) {
this.returnValue = val;
}
}
In the case of functions, when exceptions may only throw at their place of execution (based on my lack of knowledge this is uncertain for me at the moment), sometimes the culprit may rely on the way the lambda was created, an example may be:
// merge() will throw an exception if Function<S, Observable<T>> switchMap returns null.
public<S> void mergeSwitch(Observable<S> source, Function<S, Observable<T>> switchMap) {
final Mergeable<T> res = new Mergeable<>();
final Consumer<T> observer = creator(res, this); // Secondary subscription
setSource(
source, // Main subscription
new StateAwareObserver<S>() { // >>> implements Consumer<S>
Observable<T> switchRes = new Observable<>(); // Mediator
#Override
protected void onAdded() {
res.add(observer);
}
#Override
protected void onRemoved() {
res.remove(observer);
}
#Override
public void accept(S s) {
Observable<T> switchS = switchMap.apply(s);
if (switchRes != switchS) {
switchRes = switchS;
res.merge(switchS); //Where merge() throws if switchMap returns null
}
}
}
);
}
}
I've tried rethrowing the exception from the accept() method but it never gets pushed(pulled?) beyond its point of consumption, (where the consumer accepts "s"):
for (int i = observers.size() - 1; i >=0 ; i--) {
observers.get(i).accept(processed); // << -- StateAwareObserver<S> is stored in the *list*
}
-In this case I am using the word "beyond", but what I need is to throw the exception at its point of construction.-
In reality the real culprit may have been that the execution of the method: mergeSwitch(Observable<S> source, Function<S, Observable<T>> switchMap)
which created the Consumer<>, may have left something important out.
Or as is my case, the method executing mergeSwitch():
public<S> Forkable<S> forkSwitch(Function<T, Observable<S>> switchMap) {
Forkable<S> res = new Forkable<>();
res.mergeSwitch(this, switchMap);
return res;
}
In this case the issue was that the function was left with a null value.
public Holders.Forkable<List<Preset>> ledgerPresets = presetDao.forkSwitch(
presetDao -> ledgerId.forkSwitch(
aLong -> null
)
);
So if we take a look at the Consumer level, the real culprit would be:
#Override
public void accept(S s) {
int sVersion = source.getVersion();
if (sVersion != oVersion) {
Observable3<T> switchS = switchMap.apply(s); //< -- HERE
if (switchRes != switchS) {
switchRes = switchS;
res.merge(switchS);
}
oVersion = sVersion;
}
}
In this particular scenario, things can get out of hand easily since, the log may not stop at its nearest point of consumption, but the error may go all the way back to the root node.
Things can get even more complicated since the merge() method, which is the origin of the exception, may have been used from 3 different methods.
The ideal scenario would be that the exception would be thrown here:
public Holders.Forkable<List<Preset>> ledgerPresets = presetDao.forkSwitch(
presetDao -> ledgerId.forkSwitch( // <--- HERE
aLong -> null
)
);
But I haven't found a way to manage this particular exception rethrowing case yet.
EDIT:
In my previous answer I spent so much time trying to push the exception a stacktrace level, that I simply stopped trying to do it the easy way, you can ignore the craziness of the answer bellow the new one:
BEST ANSWER
public<S> Forkable<S> forkSwitch(Function<T, Observable<S>> switchMap) {
final IllegalStateException e = new IllegalStateException("switchMap must not be null");
final Forkable<S> res = new Forkable<>();
res.mergeSwitch(this,
t -> {
Observable<S> prev = switchMap.apply(t);
if (prev == null) {
throw e;
}
return prev;
}
);
return res;
}
OLD ANSWER
I tried searching for an answer but did not came up with an ideal one.
My solution has been capturing an int storing the top stacktrace and then infering the class name via the lambda.
public<S> Forkable2<S> forkSwitch(Function<T, Observable3<S>> switchMap) {
int lineNumber = Thread.currentThread().getStackTrace()[3].getLineNumber();
Forkable2<S> res = new Forkable2<>();
res.mergeSwitch(this,
t -> {
Observable3<S> prev = switchMap.apply(t);
if (prev == null) {
String lambdaS = switchMap.toString();
String prevLambdaS = lambdaS.substring(0, lambdaS.indexOf("$"));
throw new IllegalStateException("Switch map function must not return null!! at(" + prevLambdaS.substring(prevLambdaS.lastIndexOf(".") + 1) + ".java:"+ lineNumber + "), \n" +
"from: " + this);
}
return prev;
}
);
return res;
}
The reason why I find this solution to be lackluster is that the origin of the exception is actually not used, what we are really doing is intercepting the event before it actually happens, but imo this brings redundancy.
fortunately the exception is now more clear than before:
java.lang.IllegalStateException: Switch map function must not return
null!! at(PresetViewModel.java:66),
I believe one could encapsulate the entire process in a neat little object that creates the stacktrace before entering a lambda, and then creates an exception on command when required inside the lambda itself.
public<T, S> void toThrow(Function<T, S> throwableLambda) {
InferableThrow iT = new InferableThrow();
subscribe(
t -> {
S returned = throwableLambda.apply(t);
if (returned == null) {
throw iT.createExc(Class<? extends Exception>, Function<>, String message);
}
return returned;
}
);
}
Here is a helper object:
public final class InferableException<E extends RuntimeException> implements Supplier<E> {
private final Supplier<E> type;
public static<E extends RuntimeException> InferableException<E> create(
Function<String, E> type, Object lambdaObject, String message
) {
return new InferableException<>(type, lambdaObject, message);
}
private InferableException(Function<String, E> type, Object lambdaObject, String message) {
int lineNumber = Thread.currentThread().getStackTrace()[5].getLineNumber();
String lambdaS = lambdaObject.toString();
String prevLambdaS = lambdaS.substring(0, lambdaS.indexOf("$"));
String className = prevLambdaS.substring(prevLambdaS.lastIndexOf(".") + 1);
this.type = () -> type.apply(message +" at("+ className + ".java:" + lineNumber + ")");
}
#Override
public E get() {
return type.get();
}
}
Usage:
public<S> Forkable<S> forkSwitch(Function<T, Observable<S>> switchMap) {
final Supplier<IllegalStateException> e = InferableException.create( //<<-- construction
IllegalStateException::new,
switchMap,
"Switch map function must not return null!!"
);
Forkable<S> res = new Forkable<>();
res.mergeSwitch(this,
t -> {
Observable<S> prev = switchMap.apply(t);
if (prev == null) {
throw e.get(); //<<-- Throwing!!
}
return prev;
}
);
return res;
}
I have a legacy project with Spring version 3.0 (I can't use Retriable annotation from spring package).
I want to implement Retryable annotation to annotate my methods which execution should be retry on fail.
This is my class:
#Component
public final class RetryBpp implements BeanPostProcessor {
private final ConcurrentMap<String, ClassDefinition> map = new ConcurrentHashMap<>();
#Override
public Object postProcessBeforeInitialization(final Object bean, final String beanName) throws BeansException {
final Class<?> asClass = bean.getClass();
final Method[] methods = asClass.getMethods();
final List<Method> collect = Stream.of(methods)
.filter(method -> method.isAnnotationPresent(Repitable.class))
.collect(Collectors.toList());
if(!collect.isEmpty()){
this.map.put(beanName,new ClassDefinition(collect,asClass));
}
return bean;
}
#Override
public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
final ClassDefinition definition = this.map.get(beanName);
if(definition != null){
final Class beanClass = definition.asClass;
return Proxy.newProxyInstance(beanClass.getClassLoader(), beanClass.getInterfaces(), (proxy, method, args) -> {
if(definition.isMethodPresent(method)){
System.out.println("Present");
return this.retry(definition.originalMethod(method),bean,args);
} else{
return method.invoke(bean,args);
}
});
} else{
return bean;
}
}
private Object retry(final Method method,final Object originalBean,Object[] argc){
final Repitable repitable = method.getAnnotation(Repitable.class);
int attempts = repitable.attempts();
Throwable exc = null;
while(attempts!=0){
try{
return method.invoke(originalBean,argc);
}catch (final Throwable throwable){
exc = throwable;
attempts--;
this.sleep(repitable.delay(),repitable.timeUnit());
}
}
throw new RuntimeException(exc);
}
#SneakyThrows(InterruptedException.class)
private void sleep(final int time, final TimeUnit timeUnit){
timeUnit.sleep(time);
}
#AllArgsConstructor
private static final class ClassDefinition{
private final List<Method> methods;
private final Class asClass;
boolean isMethodPresent(final Method method){
return this.methods.stream().anyMatch(mthd->mthd.getName().equals(method.getName()));
}
Method originalMethod(final Method method){
return this.methods.stream().filter(mthd->mthd.getName().equals(method.getName())).findFirst().orElseThrow(NullPointerException::new);
}
}
}
And it work but I want to change two things
1)In retry method I want to keep last exception and throw when repeateCount = 0 for this I need to declare null ptr to exc, but I want all my fields to be final. Is it any possible way to rewrite my code?
2) In ClassDefinition I compare Method by name because original equals method of Method class compare by class, I can't do it because original class replaced by proxy, Is it possible to compare two Method's in different way?
For the first part: one option is to use a List of the Throwables. And then throw them something like:
final RuntimeException theError = new RuntimeException(list.get(list.size() - 1));
for (int i = 0; i < list.size() - 1; i++) {
theError.addSuppressed(list.get(i));
}
throw theError;
This also gives the benefit of providing all the failures.
For the latter part, this could be complicated and expensive depending on whether there's gonna be generics, var-args, etc. on some of the methods.
Since you're already using spring, you could try a combination of: AopUtils.getTargetClass and AopUtils.getMostSpecificMethod to get what you want (would need to refactor what you pass to some of the test methods).
Something simple to provide some more (note, not infallible) tests in the vein of what you're already running with though:
return methods.stream().anyMatch(mthd -> {
if (!method.getName().equals(mthd.getName())) {
return false;
}
if (!method.getReturnType().isAssignableFrom(mthd.getReturnType())) {
return false;
}
final Class<?>[] parameterTypes = method.getParameterTypes();
if (mthd.getParameterTypes().length != parameterTypes.length) {
return false;
}
for (int i = 0; i < parameterTypes.length; i++) {
if (!parameterTypes[i].equals(mthd.getParameterTypes()[i])) {
return false;
}
}
return true;
});
I'm sure there are some more checks you can do (Method.getExceptionTypes() to confirm they could override; Method.getDeclaringClass() and work up the tree from there; Method.getModifiers() to determine if it could be overridden, etc.), but that depends on how safe you need to be.
I am trying to get method regardless of what parameters that method takes (as of now there is no method overloading and there wouldn't be in future). The only possible solution that i could come up with was
private Method getMethod(Class<?> clas, String methodName) {
try {
Method[] methods = clas.getMethods();
for (Method method : methods) {
if (method.getName().equalsIgnoreCase(methodName)) {
return method;
}
}
} catch (SecurityException e) {
e.printStackTrace();
}
return null;
}
What i want to ask that is there a way to fetch a method regardless of its parameters ? I was looking at clas.getMethod ("methodName", parameters) and if i provide null in there it will try to fetch a method which has no parameters. Which wouldn't be no case.
Any ideas ?
EDIT
Thanks guys for input. In my case, i know that there would be only one method regardless of its case. The reason i am using ignoreCase is because the input will be coming from a developer (in other team) and he will be providing the name as a hard-coded string. So to keep things from spilling out of our hands, I am using a safe approach.
No. The way you've done it is the way to go. A method is identified by its signature and the signature includes the name and the parameter types.
Here is a solution that retrieves all methods with the specified class and method name regardless of the method's parameters:
public class Test
{
private class Foo
{
public void bar()
{
}
public void bar(String s)
{
}
public void goo()
{
}
}
private static Method[] getMethods(Class<?> clazz, String methodName)
{
List<Method> methods = new ArrayList<Method>();
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method declaredMethod: declaredMethods)
{
if (declaredMethod.getName().equals(methodName))
{
methods.add(declaredMethod);
}
}
return methods.toArray(new Method[methods.size()]);
}
public static void main(String[] args)
{
Method[] methods = getMethods(Foo.class, "bar");
System.out.println(Arrays.toString(methods));
}
}
This generates the following output:
[public void com.example.Test$Foo.bar(java.lang.String), public void com.example.Test$Foo.bar()]
You've done just fine. This is basically the same as the solution to a similar problem I dealt with four years ago, creating a means to create callback methods in Java. The constructors for my Callback class were:
public Callback(Class<?> clazz, String methodName, Object parentObj) {
// Find a method with the matching name
Method[] allMethods;
try { allMethods = clazz.getMethods(); }
catch(SecurityException se) { allMethods = new Method[0]; }
int count = 0;
Method single = null;
for(Method m : allMethods) {
if(m.getName().equals(methodName)) {
single = m;
count++;
}
// Can't have more than one instance
if(count > 1)
throw new IllegalArgumentException(clazz.getName()
+ " has more than one method named " + methodName);
}
if(count == 0) // No instances found
throw new IllegalArgumentException(clazz.getName()
+ " has no method named " + methodName);
this.parentObj = parentObj;
this.method = single;
this.parameters = single.getParameterTypes();
}
public Callback(
Class<?> clazz,
String methodName,
Object parentObj,
Class<?>...parameters)
{
try { this.method = clazz.getMethod(methodName, parameters); }
catch(NoSuchMethodException nsme) { nsme.printStackTrace(); }
catch(SecurityException se) { se.printStackTrace(); }
this.parentObj = parentObj;
this.parameters = parameters;
}
My Callback class isn't really useful any more in the era of Java 8, but at the time the only real means for a "callback" in java was anonymous interface implementations, which wasn't sufficient for my use-case.
As you can see in the first constructor, it throws an exception if it finds multiple methods with the same name.
Using java streams there is a really short method of finding a method, the first match, by its name only:
Stream.of(type.getMethods())
.filter((m) -> m.getName().equals(searchedName))
.findFirst()
.get();
I think this is a short and readable possibility in this case.
I'm attempting to make a system similar to https://github.com/ElgarL/TownyChat/blob/master/src/com/palmergames/bukkit/TownyChat/TownyChatFormatter.java
replacer.registerFormatReplacement(Pattern.quote("{worldname}"), new TownyChatReplacerCallable() {
#Override
public String call(String match, LocalTownyChatEvent event) throws Exception {
return String.format(ChatSettings.getWorldTag(), event.getEvent().getPlayer().getWorld().getName());
}
});
replacer.registerFormatReplacement(Pattern.quote("{town}"), new TownyChatReplacerCallable() {
#Override
public String call(String match, LocalTownyChatEvent event) throws Exception {
return event.getResident().hasTown() ? event.getResident().getTown().getName() : "";
}
});
and more.
Is there a way to use annotations to cut down on the amount of repeated code, avoiding reflection to call the call method, and only using it during registration, if at all?
I'm not adverse to the idea of creating an annotation pre processor as I was already planning on doing this to enable automatically generating documentation.
Let's assume you write a small Annotation
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
#interface PatternHandler {
String value();
}
And create a class like
class Callables {
#PatternHandler("foo")
public static final TownyChatReplacerCallable FOO = new TownyChatReplacerCallable() {
#Override
public String call(String match, String event) {
return "This is foo handler called with " + match + "," + event;
}
};
#PatternHandler("bar")
public static final TownyChatReplacerCallable BAR = new TownyChatReplacerCallable() {
#Override
public String call(String match, String event) {
return "This is foo handler called with " + match + "," + event;
}
};
}
Now you can take the whole class or even multiple classes that contain those static fields and pass it to some registry method that iterates reflectively over each field in that class and if it's an annotated callable registers that.
class AnnotationRegistry {
public static void register(String pattern, TownyChatReplacerCallable handler) {}
public static void register(Class<?> clazz) {
// only fields declared by this class, not inherited ones (static fields can't be inherited)
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
// must have that annotation
PatternHandler annotation = field.getAnnotation(PatternHandler.class);
if (annotation != null) {
// must be static
if (!Modifier.isStatic(field.getModifiers())) {
System.out.println("Field must be static:" + field.getName());
continue;
}
// get content of that field
try {
Object object = field.get(null);
// must be != null and a callable
if (object instanceof TownyChatReplacerCallable) {
register(annotation.value(), (TownyChatReplacerCallable) object);
} else {
System.out.println("Field must be instanceof TownyChatReplacerCallable:" + field.getName());
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
That would save you a bit code and would have no speed disadvantage at runtime since there is no need to use reflection to call those callables.
Full example here: http://ideone.com/m3PPcY
Besides using static fields, you can also use non static ones if you pass an instance of a class to the registry which would then be used like Object object = field.get(instance); instead of the null.
Furthermore, instead of fields the same approach would work with methods which would be less code to write:
#PatternHandler("foo")
public static String fooMethod(String match, String event) {
return "This is foo handler called with " + match + "," + event;
}
Registry would then look for all Methods. Then for example wrap them in
class MethodAdapter implements TownyChatReplacerCallable {
private final Method method;
public MethodAdapter(Method m) {
method = m;
}
#Override
public String call(String match, String event) {
try {
return (String) method.invoke(null, match, event);
} catch (Exception e) {
e.printStackTrace();
return "OMGZ";
}
}
}
and continue as usual. But beware: invoking a method reflectively is potentially slower than calling it directly via code - few percent only, nothing to worry about
Full example for methods: http://ideone.com/lMJsrl
You can try new Java 8 Lambda Expressions instead (http://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html).
replacer.registerFormatReplacement(Pattern.quote("{worldname}"), new TownyChatReplacerCallable() {
#Override
public String call(String match, LocalTownyChatEvent event) throws Exception {
return String.format(ChatSettings.getWorldTag(), event.getEvent().getPlayer().getWorld().getName());
}
});
Can be written as :
replacer.registerFormatReplacement(
Pattern.quote("{worldname}"),
(match, event) -> { return String.format(ChatSettings.getWorldTag(), event.getEvent().getPlayer().getWorld().getName()); }
});
You can also push it further with another interface, method, ... that wrap it