View Javadoc
1   /*
2    * Copyright (C) 2012-2024 RRiBbit.org
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.rribbit.execution;
17  
18  import org.rribbit.ListenerObject;
19  import org.rribbit.Response;
20  import org.slf4j.Logger;
21  import org.slf4j.LoggerFactory;
22  
23  import java.lang.reflect.InvocationTargetException;
24  import java.lang.reflect.Method;
25  import java.util.ArrayList;
26  import java.util.Collection;
27  
28  /**
29   * This {@link ListenerObjectExecutor} provides a blueprint for the execution of a {@link Collection} of {@link ListenerObject}s and the processing of their results. The
30   * only thing left to implementation subclasses is to actually use the methods provided in this class to actually do the execution. The implementations can decide on, for example, sequential
31   * or multi-threaded execution of the different {@link ListenerObject}s.
32   * <p />
33   * Please note that this class uses the java method {@link Method}.invoke() and this method does NOT work with varargs. So, if you want to call a listener method that accepts varargs, you MUST
34   * explicitly pass an array where the varargs are expected, even if that array is empty, otherwise, the method will NOT match and will NOT be executed.
35   *
36   * @author G.J. Schouten
37   *
38   */
39  public abstract class AbstractListenerObjectExecutor implements ListenerObjectExecutor {
40  
41  	private static final Logger log = LoggerFactory.getLogger(AbstractListenerObjectExecutor.class);
42  
43  	@Override
44  	public <T> Response<T> executeListeners(Collection<ListenerObject> listenerObjects, Object... parameters) {
45  
46  		Collection<T> results = new ArrayList<>();
47  		Collection<Throwable> throwables = new ArrayList<>();
48  
49  		log.debug("Executing ListenerObjects");
50  		for(ExecutionResult executionResult : this.doExecuteListeners(listenerObjects, parameters)) {
51  			if(executionResult != null && !(executionResult instanceof VoidResult)) {
52  				if(executionResult instanceof ThrowableResult) {
53  					throwables.add(((ThrowableResult) executionResult).getThrowable());
54  				} else {
55  					results.add((T) (((ObjectResult) executionResult).getResult()));
56  				}
57  			}
58  		}
59  		return new Response<>(results, throwables);
60  	}
61  
62  	/**
63  	 * This method executes a single {@link ListenerObject} and returns the appropriate {@link ExecutionResult}. It should be used by subclasses when excuting the {@link ListenerObject}s.
64  	 *
65  	 * @param listenerObject
66  	 * @param parameters
67  	 * @return the {@link ExecutionResult} of the execution of the {@link ListenerObject}, or null if the {@link ListenerObject} did not match the parameters
68  	 */
69  	protected ExecutionResult executeSingleListenerObject(ListenerObject listenerObject, Object... parameters) {
70  
71  		try {
72  			Object returnValue = listenerObject.getMethod().invoke(listenerObject.getTarget(), parameters);
73  			if(listenerObject.getMethod().getReturnType().equals(void.class)) { //Nothing to return
74  				log.debug("ListenerObject '{}' successfully executed, return value was void", listenerObject);
75  				return new VoidResult();
76  			} else {
77  				log.debug("ListenerObject '{}' successfully executed, return value was object", listenerObject);
78  				return new ObjectResult(returnValue);
79  			}
80  		} catch(InvocationTargetException e) {
81  			log.debug("Underlying method of ListenerObject '{}' threw Throwable", listenerObject);
82  			//Caused by the underlying method throwing a Throwable. Rethrowing...
83  			return new ThrowableResult(e.getCause());
84  		} catch(Exception e) {
85  			//Note: In Java 18, the JVM sometimes throws an InvocationTargetException instead of an IllegalArgumentException
86  			//if the parameters don't match the Method signature, thereby ending up in the above catch clause instead of this
87  			//one. This seems to be fixed in Java 21, but if it occurs again, the solution is to return null from the
88  			//above catch clause if the cause of the InvocationTargetException is actually mismatching parameters, in order
89  			//to match the handling of that scenario in this catch clause.
90  
91  			log.trace("Method of ListenerObject '" + listenerObject + "' did not match parameters, ignoring", e);
92  			//Probably caused by parameters not matching Method signature. Ignoring...
93  			return null;
94  		}
95  	}
96  
97  	/**
98  	 * This method should call {@link #executeSingleListenerObject(ListenerObject, Object...)} on each {@link ListenerObject}, accumulate the results, and return. Typical implementations
99  	 * do this either sequentially, or multi-threaded.
100 	 *
101 	 * @param listenerObjects
102 	 * @param parameters
103 	 * @return a {@link Collection} with the {@link ExecutionResult}s of the given {@link ListenerObject}s or an empty {@link Collection} if there are no results, never returns null
104 	 */
105 	protected abstract Collection<ExecutionResult> doExecuteListeners(Collection<ListenerObject> listenerObjects, Object... parameters);
106 
107 	/**
108 	 * This class represents the outcome of the execution of a {@link ListenerObject}.
109 	 *
110 	 * @author G.J. Schouten
111 	 *
112 	 */
113 	protected abstract static class ExecutionResult {}
114 
115 	/**
116 	 * This {@link ExecutionResult} represents a successful invocation, but no result (method return type was 'void').
117 	 *
118 	 * @author G.J. Schouten
119 	 *
120 	 */
121 	protected static class VoidResult extends ExecutionResult {}
122 
123 	/**
124 	 * This {@link ExecutionResult} represents an unsuccessful invocation, where the method threw a {@link Throwable}.
125 	 *
126 	 * @author G.J. Schouten
127 	 *
128 	 */
129 	protected static class ThrowableResult extends ExecutionResult {
130 
131 		private Throwable throwable;
132 
133 		public ThrowableResult(Throwable throwable) {
134 			this.throwable = throwable;
135 		}
136 
137 		public Throwable getThrowable() {
138 			return throwable;
139 		}
140 	}
141 
142 	/**
143 	 * This {@link ExecutionResult} represents a successful invocation, along with its result.
144 	 *
145 	 * @author G.J. Schouten
146 	 */
147 	protected static class ObjectResult extends ExecutionResult {
148 
149 		private Object result;
150 
151 		public ObjectResult(Object result) {
152 			this.result = result;
153 		}
154 
155 		public Object getResult() {
156 			return result;
157 		}
158 	}
159 }