View Javadoc
1   package org.oxerr.spring.cache.redis.scored.score.resolver.annotated;
2   
3   import java.io.IOException;
4   import java.lang.annotation.Annotation;
5   import java.lang.reflect.AnnotatedElement;
6   import java.lang.reflect.Field;
7   import java.lang.reflect.InvocationTargetException;
8   import java.lang.reflect.Method;
9   import java.util.HashMap;
10  import java.util.List;
11  import java.util.Map;
12  import java.util.Objects;
13  import java.util.Optional;
14  
15  import org.apache.commons.lang3.reflect.FieldUtils;
16  import org.apache.commons.lang3.reflect.MethodUtils;
17  import org.oxerr.spring.cache.redis.scored.score.resolver.ScoreResolver;
18  import org.oxerr.spring.cache.redis.scored.score.resolver.ScoreUtils;
19  import org.springframework.core.Ordered;
20  import org.springframework.core.annotation.Order;
21  import org.springframework.lang.NonNull;
22  import org.springframework.lang.Nullable;
23  
24  public class AnnotatedScoreResolver implements ScoreResolver {
25  
26  	private static final long serialVersionUID = 2021082001L;
27  
28  	private transient Map<Class<?>, AnnotatedElementWrapper<? extends AnnotatedElement>> annotatedElements;
29  
30  	private Class<? extends Annotation> annotationType;
31  
32  	public AnnotatedScoreResolver(Class<? extends Annotation> annotationType) {
33  		this.annotationType = annotationType;
34  		this.annotatedElements = new HashMap<>();
35  	}
36  
37  	@Override
38  	public Optional<Double> resolveScore(@Nullable Object value) {
39  		if (value == null) {
40  			return Optional.empty();
41  		} else {
42  			return getScore(value);
43  		}
44  	}
45  
46  	private Optional<Double> getScore(@NonNull Object value) {
47  		Optional<Double> score = Optional.empty();
48  
49  		try {
50  			score = this.getScoreInternal(value);
51  		} catch (IllegalAccessException | InvocationTargetException e) {
52  			throw new IllegalArgumentException(e);
53  		}
54  
55  		return score;
56  	}
57  
58  	private Optional<Double> getScoreInternal(@NonNull Object value)
59  			throws IllegalAccessException, InvocationTargetException {
60  		final Class<?> valueType = value.getClass();
61  		final Object version = this.getAnnotatedElement(valueType).readValue(value);
62  		return Optional.ofNullable(extractScore(version));
63  	}
64  
65  	private AnnotatedElementWrapper<? extends AnnotatedElement> getAnnotatedElement(Class<?> valueType) {
66  		AnnotatedElementWrapper<? extends AnnotatedElement> annotatedElement = this.annotatedElements.get(valueType);
67  		if (annotatedElement == null) {
68  			annotatedElement = resolveAnnotatedElement(valueType);
69  			this.annotatedElements.put(valueType, annotatedElement);
70  		}
71  		return annotatedElement;
72  	}
73  
74  	private AnnotatedElementWrapper<? extends AnnotatedElement> resolveAnnotatedElement(Class<?> valueType) {
75  		final AnnotatedElementWrapper<? extends AnnotatedElement> annotatedElement;
76  
77  		final List<Method> methods = MethodUtils.getMethodsListWithAnnotation(valueType, annotationType, true, true);
78  
79  		if (!methods.isEmpty()) {
80  			final Method method;
81  			if (methods.size() == 1) {
82  				method = methods.get(0);
83  			} else {
84  				method = methods.stream()
85  					.map(OrderedAnnotatedElement::new)
86  					.min((a, b) -> Integer.compare(a.getOrder(), b.getOrder()))
87  					.map(OrderedAnnotatedElement::getAnnotatedElement)
88  					.orElseThrow(IllegalArgumentException::new);
89  			}
90  			method.setAccessible(true);
91  			annotatedElement = AnnotatedElementWrapper.of(method, AnnotatedElementWrapper.Type.METHOD);
92  		} else {
93  			final List<Field> fields = FieldUtils.getFieldsListWithAnnotation(valueType, annotationType);
94  			if (!fields.isEmpty()) {
95  				final Field field;
96  				if (fields.size() == 1) {
97  					field = fields.get(0);
98  				} else {
99  					field = fields.stream()
100 						.map(OrderedAnnotatedElement::new)
101 						.min((a, b) -> Integer.compare(a.getOrder(), b.getOrder()))
102 						.map(OrderedAnnotatedElement::getAnnotatedElement)
103 						.orElseThrow(IllegalArgumentException::new);
104 				}
105 				field.setAccessible(true);
106 				annotatedElement = AnnotatedElementWrapper.of(field, AnnotatedElementWrapper.Type.FIELD);
107 			} else {
108 				annotatedElement = AnnotatedElementWrapper.empty();
109 			}
110 		}
111 
112 		return annotatedElement;
113 	}
114 
115 	private static class AnnotatedElementWrapper<T extends AnnotatedElement> {
116 
117 		/**
118 		 * Common instance for {@code empty()}.
119 		 */
120 		private static final AnnotatedElementWrapper<?> EMPTY = new AnnotatedElementWrapper<>();
121 
122 		private final T annotatedElement;
123 
124 		public enum Type {
125 			METHOD,
126 			FIELD;
127 		}
128 
129 		private final Type type;
130 
131 		private AnnotatedElementWrapper() {
132 			this.annotatedElement = null;
133 			this.type = null;
134 		}
135 
136 		public static <T extends AnnotatedElement> AnnotatedElementWrapper<T> empty() {
137 			@SuppressWarnings("unchecked")
138 			AnnotatedElementWrapper<T> t = (AnnotatedElementWrapper<T>) EMPTY;
139 			return t;
140 		}
141 
142 		private AnnotatedElementWrapper(T annotatedElement, Type type) {
143 			this.annotatedElement = Objects.requireNonNull(annotatedElement);
144 			this.type = Objects.requireNonNull(type);
145 		}
146 
147 		public static <T extends AnnotatedElement> AnnotatedElementWrapper<T> of(T value, Type type) {
148 			return new AnnotatedElementWrapper<>(value, type);
149 		}
150 
151 		private boolean isPresent() {
152 			return this.annotatedElement != null;
153 		}
154 
155 		public Object readValue(Object object) throws IllegalAccessException, InvocationTargetException {
156 			Object value;
157 			if (isPresent()) {
158 				if (this.type == Type.METHOD) {
159 					value = ((Method) this.annotatedElement).invoke(object);
160 				} else {
161 					value = ((Field) this.annotatedElement).get(object);
162 				}
163 			} else {
164 				value = null;
165 			}
166 			return value;
167 		}
168 
169 	}
170 
171 	private class OrderedAnnotatedElement<T extends AnnotatedElement> implements Ordered {
172 
173 		private final T annotatedElement;
174 		private final int order;
175 
176 		public OrderedAnnotatedElement(T annotatedElement) {
177 			this.annotatedElement = annotatedElement;
178 			this.order = Optional.ofNullable(annotatedElement.getAnnotation(Order.class))
179 				.map(Order::value)
180 				.orElse(Ordered.LOWEST_PRECEDENCE);
181 		}
182 
183 		public T getAnnotatedElement() {
184 			return annotatedElement;
185 		}
186 
187 		@Override
188 		public int getOrder() {
189 			return order;
190 		}
191 
192 	}
193 
194 	protected Double extractScore(Object version) {
195 		return ScoreUtils.extractScore(version);
196 	}
197 
198 	private void writeObject(java.io.ObjectOutputStream out) throws IOException {
199 		out.writeUTF(this.annotationType.getName());
200 	}
201 
202 	@SuppressWarnings("unchecked")
203 	private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
204 		this.annotationType = (Class<? extends Annotation>) Class.forName(in.readUTF());
205 		this.annotatedElements = new HashMap<>();
206 	}
207 
208 }