Skip to content

Method: findAllImplementedInterfacesOf(Class)

1: /*
2: * *************************************************************************************************************************************************************
3: *
4: * TheseFoolishThings: Miscellaneous utilities
5: * http://tidalwave.it/projects/thesefoolishthings
6: *
7: * Copyright (C) 2009 - 2025 by Tidalwave s.a.s. (http://tidalwave.it)
8: *
9: * *************************************************************************************************************************************************************
10: *
11: * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
12: * You may obtain a copy of the License at
13: *
14: * http://www.apache.org/licenses/LICENSE-2.0
15: *
16: * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
17: * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
18: *
19: * *************************************************************************************************************************************************************
20: *
21: * git clone https://bitbucket.org/tidalwave/thesefoolishthings-src
22: * git clone https://github.com/tidalwave-it/thesefoolishthings-src
23: *
24: * *************************************************************************************************************************************************************
25: */
26: package it.tidalwave.role.spi;
27:
28: import java.lang.reflect.InvocationTargetException;
29: import jakarta.annotation.Nonnull;
30: import java.util.ArrayList;
31: import java.util.Collection;
32: import java.util.HashSet;
33: import java.util.List;
34: import java.util.Map;
35: import java.util.Optional;
36: import java.util.Set;
37: import java.util.SortedSet;
38: import java.util.TreeSet;
39: import it.tidalwave.util.ContextManager;
40: import it.tidalwave.util.annotation.VisibleForTesting;
41: import it.tidalwave.util.impl.MultiMap;
42: import it.tidalwave.util.impl.OwnerAndRole;
43: import it.tidalwave.dci.annotation.DciRole;
44: import lombok.extern.slf4j.Slf4j;
45: import static java.util.Comparator.*;
46: import static it.tidalwave.util.ShortNames.*;
47:
48: /***************************************************************************************************************************************************************
49: *
50: * A basic implementation of a {@link SystemRoleFactory}. This class must be specialized to:
51: *
52: * <ol>
53: * <li>discover roles (see {@link #scan(java.util.Collection)}</li>
54: * <li>associate roles to a datum (see {@link #findDatumTypesForRole(java.lang.Class)}</li>
55: * <li>associate roles to contexts (see {@link #findContextTypeForRole(java.lang.Class)}</li>
56: * <li>eventually retrieve beans to inject in created roles (see {@link #getBean(java.lang.Class)}</li>
57: * </ol>
58: *
59: * Specializations might use annotations or configuration files to accomplish these tasks.
60: *
61: * @author Fabrizio Giudici
62: *
63: **************************************************************************************************************************************************************/
64: @Slf4j
65: public abstract class SystemRoleFactorySupport implements SystemRoleFactory
66: {
67: @VisibleForTesting final MultiMap<OwnerAndRole, Class<?>> roleMapByOwnerAndRole = new MultiMap<>();
68:
69: // FIXME: use ConcurrentHashMap
70: @VisibleForTesting final Set<OwnerAndRole> alreadyScanned = new HashSet<>();
71:
72: /***********************************************************************************************************************************************************
73: * {@inheritDoc}
74: **********************************************************************************************************************************************************/
75: @Override @Nonnull
76: public synchronized <T> List<T> findRoles (@Nonnull final Object datum, @Nonnull final Class<? extends T> roleType)
77: {
78: log.trace("findRoles({}, {})", shortId(datum), shortName(roleType));
79: final Class<?> datumType = findTypeOf(datum);
80: final List<T> roles = new ArrayList<>();
81: final var roleImplementationTypes = findRoleImplementationsFor(datumType, roleType);
82:
83: outer: for (final var roleImplementationType : roleImplementationTypes)
84: {
85: for (final var constructor : roleImplementationType.getDeclaredConstructors())
86: {
87: log.trace(">>>> trying constructor {}", constructor);
88: final var parameterTypes = constructor.getParameterTypes();
89: Optional<?> context = Optional.empty();
90: final var contextType = findContextTypeForRole(roleImplementationType);
91:
92: if (contextType.isPresent())
93: {
94: // With DI frameworks such as Spring it's better to avoid eager initializations of references
95: final var contextManager = ContextManager.getInstance();
96: log.trace(">>>> contexts: {}", shortIds(contextManager.getContexts()));
97: context = contextManager.findContextOfType(contextType.get());
98:
99: if (context.isEmpty())
100: {
101: log.trace(">>>> role {} discarded, can't find context: {}", shortName(roleImplementationType), shortName(contextType.get()));
102: continue outer;
103: }
104: }
105:
106: try
107: {
108: final var params = getParameterValues(parameterTypes, datumType, datum, contextType, context);
109: roles.add(roleType.cast(constructor.newInstance(params)));
110: break;
111: }
112: catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e)
113: {
114: log.error("Could not instantiate role of type " + roleImplementationType, e);
115: }
116: }
117: }
118:
119: if (log.isTraceEnabled())
120: {
121: log.trace(">>>> findRoles() returning: {}", shortIds(roles));
122: }
123:
124: return roles;
125: }
126:
127: /***********************************************************************************************************************************************************
128: * Prepare the constructor parameters out of the given expected types. Parameters will be eventually made of the given datum, context, and other objects
129: * returned by {@link #getBean(java.lang.Class)}.
130: * @param parameterTypes the expected types
131: * @param datumClass the type of the datum
132: * @param datum the datum
133: * @param contextClass the type of the context
134: * @param context the context
135: **********************************************************************************************************************************************************/
136: @Nonnull
137: private Object[] getParameterValues (@Nonnull final Class<?>[] parameterTypes,
138: @Nonnull final Class<?> datumClass,
139: @Nonnull final Object datum,
140: @Nonnull final Optional<Class<?>> contextClass,
141: @Nonnull final Optional<?> context)
142: {
143: final var values = new ArrayList<>();
144:
145: for (final var parameterType : parameterTypes)
146: {
147: if (parameterType.isAssignableFrom(datumClass))
148: {
149: values.add(datum);
150: }
151: else if (contextClass.isPresent() && parameterType.isAssignableFrom(contextClass.get()))
152: {
153: values.add(context.orElse(null));
154: }
155: else // generic injection
156: {
157: // FIXME: it's injecting null, but perhaps should it throw exception?
158: values.add(getBean(parameterType).orElse(null));
159: }
160: }
161:
162: log.trace(">>>> constructor parameters: {}", values);
163: return values.toArray();
164: }
165:
166: /***********************************************************************************************************************************************************
167: * Finds the role implementations for the given owner type and role type. This method might discover new implementations that weren't found during the
168: * initial scan, since the initial scan can't go down in a hierarchy; that is, given a Base class or interface with some associated roles, it can't
169: * associate those roles to subclasses (or implementations) of Base. Now we can navigate up the hierarchy and complete the picture. Each new discovered role
170: * is added into the map, so the next time scanning will be faster.
171: * @param datumType the type of the datum
172: * @param roleType the type of the role to find
173: * @return the types of role implementations
174: **********************************************************************************************************************************************************/
175: @Nonnull
176: @VisibleForTesting synchronized <T> Set<Class<? extends T>> findRoleImplementationsFor (@Nonnull final Class<?> datumType, @Nonnull final Class<T> roleType)
177: {
178: final var datumAndRole = new OwnerAndRole(datumType, roleType);
179:
180: if (!alreadyScanned.contains(datumAndRole))
181: {
182: alreadyScanned.add(datumAndRole);
183: final var before = new HashSet<>(roleMapByOwnerAndRole.getValues(datumAndRole));
184:
185: for (final var superDatumAndRole : datumAndRole.getSuper())
186: {
187: roleMapByOwnerAndRole.addAll(datumAndRole, roleMapByOwnerAndRole.getValues(superDatumAndRole));
188: }
189:
190: final var after = new HashSet<>(roleMapByOwnerAndRole.getValues(datumAndRole));
191: logChanges(datumAndRole, before, after);
192: }
193:
194: return (Set<Class<? extends T>>)(Set)roleMapByOwnerAndRole.getValues(datumAndRole);
195: }
196:
197: /***********************************************************************************************************************************************************
198: * Scans all the given role implementation classes and builds a map of roles by owner class.
199: * @param roleImplementationTypes the types of role implementations to scan
200: **********************************************************************************************************************************************************/
201: protected synchronized void scan (@Nonnull final Collection<Class<?>> roleImplementationTypes)
202: {
203: log.debug("scan({})", shortNames(roleImplementationTypes));
204:
205: for (final var roleImplementationType : roleImplementationTypes)
206: {
207: for (final var datumType : findDatumTypesForRole(roleImplementationType))
208: {
209: for (final var roleType : findAllImplementedInterfacesOf(roleImplementationType))
210: {
211: if (!"org.springframework.beans.factory.aspectj.ConfigurableObject".equals(roleType.getName()))
212: {
213: roleMapByOwnerAndRole.add(new OwnerAndRole(datumType, roleType), roleImplementationType);
214: }
215: }
216: }
217: }
218:
219: logRoles();
220: }
221:
222: /***********************************************************************************************************************************************************
223: * Finds all the interfaces implemented by a given class, including those eventually implemented by superclasses and interfaces that are indirectly
224: * implemented (e.g. C implements I1, I1 extends I2).
225: * @param clazz the class to inspect
226: * @return the implemented interfaces
227: **********************************************************************************************************************************************************/
228: @Nonnull
229: @VisibleForTesting static SortedSet<Class<?>> findAllImplementedInterfacesOf (@Nonnull final Class<?> clazz)
230: {
231: final SortedSet<Class<?>> interfaces = new TreeSet<>(comparing(Class::getName));
232: interfaces.addAll(List.of(clazz.getInterfaces()));
233:
234:• for (final var interface_ : interfaces)
235: {
236: interfaces.addAll(findAllImplementedInterfacesOf(interface_));
237: }
238:
239:• if (clazz.getSuperclass() != null)
240: {
241: interfaces.addAll(findAllImplementedInterfacesOf(clazz.getSuperclass()));
242: }
243:
244: return interfaces;
245: }
246:
247: /***********************************************************************************************************************************************************
248: * Retrieves an extra bean.
249: * @param <T> the static type of the bean
250: * @param beanType the dynamic type of the bean
251: * @return the bean
252: **********************************************************************************************************************************************************/
253: @Nonnull
254: protected <T> Optional<T> getBean (@Nonnull final Class<T> beanType)
255: {
256: return Optional.empty();
257: }
258:
259: /***********************************************************************************************************************************************************
260: * Returns the type of the context associated to the given role implementation type.
261: * @param roleImplementationType the role type
262: * @return the context type
263: **********************************************************************************************************************************************************/
264: @Nonnull
265: protected Optional<Class<?>> findContextTypeForRole (@Nonnull final Class<?> roleImplementationType)
266: {
267: final var contextClass = roleImplementationType.getAnnotation(DciRole.class).context();
268: return (contextClass == DciRole.NoContext.class) ? Optional.empty() : Optional.of(contextClass);
269: }
270:
271: /***********************************************************************************************************************************************************
272: * Returns the valid datum types for the given role implementation type.
273: * @param roleImplementationType the role type
274: * @return the datum types
275: **********************************************************************************************************************************************************/
276: @Nonnull
277: protected Class<?>[] findDatumTypesForRole (@Nonnull final Class<?> roleImplementationType)
278: {
279: return roleImplementationType.getAnnotation(DciRole.class).datumType();
280: }
281:
282: /***********************************************************************************************************************************************************
283: *
284: **********************************************************************************************************************************************************/
285: private void logChanges (@Nonnull final OwnerAndRole ownerAndRole, @Nonnull final Set<Class<?>> before, @Nonnull final Set<Class<?>> after)
286: {
287: after.removeAll(before);
288:
289: if (!after.isEmpty())
290: {
291: log.debug(">>>>>>> added implementations: {} -> {}", ownerAndRole, shortNames(after));
292:
293: if (log.isTraceEnabled()) // yes, trace
294: {
295: logRoles();
296: }
297: }
298: }
299:
300: /***********************************************************************************************************************************************************
301: *
302: **********************************************************************************************************************************************************/
303: public void logRoles()
304: {
305: log.debug("Configured roles:");
306: final var entries = new ArrayList<>(roleMapByOwnerAndRole.entrySet());
307: entries.sort(comparing((Map.Entry<OwnerAndRole, Set<Class<?>>> e) -> e.getKey().getOwnerClass().getName())
308: .thenComparing(e -> e.getKey().getRoleClass().getName()));
309:
310: for (final var entry : entries)
311: {
312: log.debug(">>>> {}: {} -> {}", shortName(entry.getKey().getOwnerClass()), shortName(entry.getKey().getRoleClass()), shortNames(entry.getValue()));
313: }
314: }
315:
316: /***********************************************************************************************************************************************************
317: * Returns the type of object, taking care of mocks created by Mockito, for which the implemented interface is returned.
318: * @param object the object
319: * @return the object type
320: **********************************************************************************************************************************************************/
321: @Nonnull
322: @VisibleForTesting static <T> Class<T> findTypeOf (@Nonnull final T object)
323: {
324: var ownerClass = object.getClass();
325:
326: if (ownerClass.toString().contains("MockitoMock"))
327: {
328: ownerClass = ownerClass.getInterfaces()[0]; // 1st is the original class, 2nd is CGLIB proxy
329:
330: if (log.isTraceEnabled())
331: {
332: log.trace(">>>> owner is a mock {} implementing {}", shortName(ownerClass), shortNames(List.of(ownerClass.getInterfaces())));
333: log.trace(">>>> owner class replaced with {}", shortName(ownerClass));
334: }
335: }
336:
337: return (Class<T>)ownerClass;
338: }
339: }