View Javadoc

1   package cz.cuni.amis.pogamut.ut2004.communication.messages.custom;
2   
3   import java.lang.reflect.Constructor;
4   import java.lang.reflect.Field;
5   import java.util.ArrayList;
6   import java.util.Collection;
7   import java.util.HashMap;
8   import java.util.List;
9   import java.util.Map;
10  
11  import cz.cuni.amis.pogamut.base3d.worldview.object.Location;
12  import cz.cuni.amis.pogamut.unreal.communication.messages.UnrealId;
13  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.ControlMessage;
14  import cz.cuni.amis.utils.ClassUtils;
15  import cz.cuni.amis.utils.Tuple3;
16  
17  /**
18   * Reads definition of {@link ICustomControlMessage} implementation interpreting {@link ControlMessageType}, {@link ControlMessageField} and {@link ControlMessageSimType}.
19   * and provides {@link ControlMessageMapper#deserialize(ControlMessage)} method for auto-mapping of {@link ControlMessage} onto custom {@link ICustomControlMessage}.
20   * 
21   * @author Jimmy
22   *
23   * @param <T>
24   */
25  public class ControlMessageMapper<T extends ICustomControlMessage> {
26  	
27  	public static interface ControlMessageGetter<T> {
28  		
29  		public T get(ControlMessage msg);
30  		
31  	}
32  	
33  	@SuppressWarnings("unchecked")
34  	public static final ControlMessageGetter<Integer>[] getterIntegers = 
35  		new ControlMessageGetter[] {
36  			// 0
37  			null,
38  			// 1
39  			new ControlMessageGetter<Integer>() {
40  				@Override
41  				public Integer get(ControlMessage msg) {
42  					return msg.getPI1();
43  				}
44  			},
45  			// 2
46  			new ControlMessageGetter<Integer>() {
47  				@Override
48  				public Integer get(ControlMessage msg) {
49  					return msg.getPI2();
50  				}
51  			},
52  			// 3
53  			new ControlMessageGetter<Integer>() {
54  				@Override
55  				public Integer get(ControlMessage msg) {
56  					return msg.getPI3();
57  				}
58  			},
59  		};
60  	
61  	@SuppressWarnings("unchecked")
62  	public static final ControlMessageGetter<Double>[] getterDoubles = 
63  		new ControlMessageGetter[] {
64  			// 0
65  			null,
66  			// 1
67  			new ControlMessageGetter<Double>() {
68  				@Override
69  				public Double get(ControlMessage msg) {
70  					return msg.getPF1();
71  				}
72  			},
73  			// 2
74  			new ControlMessageGetter<Double>() {
75  				@Override
76  				public Double get(ControlMessage msg) {
77  					return msg.getPF2();
78  				}
79  			},
80  			// 3
81  			new ControlMessageGetter<Double>() {
82  				@Override
83  				public Double get(ControlMessage msg) {
84  					return msg.getPF3();
85  				}
86  			},
87  		};
88  	
89  	@SuppressWarnings("unchecked")
90  	public static final ControlMessageGetter<Double>[] getterStrings = 
91  		new ControlMessageGetter[] {
92  			// 0
93  			null,
94  			// 1
95  			new ControlMessageGetter<String>() {
96  				@Override
97  				public String get(ControlMessage msg) {
98  					return msg.getPS1();
99  				}
100 			},
101 			// 2
102 			new ControlMessageGetter<String>() {
103 				@Override
104 				public String get(ControlMessage msg) {
105 					return msg.getPS2();
106 				}
107 			},
108 			// 3
109 			new ControlMessageGetter<String>() {
110 				@Override
111 				public String get(ControlMessage msg) {
112 					return msg.getPS3();
113 				}
114 			},
115 		};
116 	
117 	@SuppressWarnings("unchecked")
118 	public static final ControlMessageGetter<Double>[] getterBooleans = 
119 		new ControlMessageGetter[] {
120 			// 0
121 			null,
122 			// 1
123 			new ControlMessageGetter<Boolean>() {
124 				@Override
125 				public Boolean get(ControlMessage msg) {
126 					return msg.isPB1();
127 				}
128 			},
129 			// 2
130 			new ControlMessageGetter<Boolean>() {
131 				@Override
132 				public Boolean get(ControlMessage msg) {
133 					return msg.isPB2();
134 				}
135 			},
136 			// 3
137 			new ControlMessageGetter<Boolean>() {
138 				@Override
139 				public Boolean get(ControlMessage msg) {
140 					return msg.isPB3();
141 				}
142 			},
143 		};
144 	
145 	private Class<T> descriptor;
146 	
147 	private String type;
148 	
149 	private Constructor<T> constructor;
150 	
151 	@SuppressWarnings("rawtypes")
152 	private List<Tuple3<Field, ControlMessageGetter, ControlMessageTypeMapper>> fields = new ArrayList<Tuple3<Field, ControlMessageGetter, ControlMessageTypeMapper>>();
153 	
154 	private Field simTimeField;
155 	
156 	@SuppressWarnings("rawtypes")
157 	public ControlMessageMapper(Class<T> customControlMessageClass) {
158 		Map<Integer, Field> integers = new HashMap<Integer, Field>();
159 		Map<Integer, Field> doubles = new HashMap<Integer, Field>();
160 		Map<Integer, Field> strings = new HashMap<Integer, Field>();
161         Map<Integer, Field> booleans = new HashMap<Integer, Field>();
162 		
163 		this.descriptor = customControlMessageClass;
164 		
165 		if (!customControlMessageClass.isAnnotationPresent(ControlMessageType.class)) {
166 			throw new RuntimeException("Cannot create ControlMessageDeserializer for " + customControlMessageClass + " as it is not annotated with ControlMessageType!");
167 		}
168 		this.type = customControlMessageClass.getAnnotation(ControlMessageType.class).type();
169 		
170 		try {
171 			constructor = customControlMessageClass.getConstructor();
172 		} catch (Exception e) {
173 			throw new RuntimeException("Cannot create ControlMessageDeserializer as its parameter-less contructor is unavailable (either non-existent or Java security related).", e);
174 		}
175 		if (constructor == null) {
176 			throw new RuntimeException("Cannot create ControlMessageDeserializer as its parameter-less contructor is unavailable (either non-existent or Java security related).");
177 		}
178 		
179 		Collection<Class> classes = ClassUtils.getSubclasses(customControlMessageClass);
180 		for (Class cls : classes) {
181 			// SANITY-CHECKS
182 			if (cls.isAnnotation()) continue;
183 			if (cls.isInterface()) continue;
184 			
185 			// PROBE FIELDS
186 			for (Field field : cls.getDeclaredFields()) {
187 				
188 				if (field.isAnnotationPresent(ControlMessageSimType.class)) {
189 					if (!field.getType().equals(long.class)) {
190 						throw new RuntimeException("Cannot create ControlMessageDeserializer as its @ControlMessageSimType field " + field.getDeclaringClass() + "." + field.getName() + " is not of type 'long' but '" + field.getType() + "', invalid.");
191 					}
192 					if (simTimeField != null) {
193 						throw new RuntimeException("Cannot create ControlMessageDeserializer as its @ControlMessageSimType field declared twice, first " + simTimeField.getDeclaringClass() + "." + simTimeField.getName() + ", second " + field.getDeclaringClass() + "." + field.getDeclaringClass() + "." + field.getName() + ".");
194 					}
195 					simTimeField = field;
196 					continue;
197 				}
198 				
199 				if (!field.isAnnotationPresent(ControlMessageField.class)) continue;
200 				
201 				ControlMessageField info = field.getAnnotation(ControlMessageField.class);
202 				
203 				if (info.index() < 0 || info.index() > 3) {
204 					throw new RuntimeException("Cannot create CustomMessageDeserializer for " + customControlMessageClass + " as field " + field.getDeclaringClass() + "." + field.getName() + " contains annotation ControlMessageParam(index=" + info.index() + "), unsupported. 1 <= index <= 3.");
205 				}
206 				
207 				Map<Integer, Field> map;
208 				
209 				if (field.getType() == Integer.class) {
210 					map = integers;
211 				} else
212 				if (field.getType() == Double.class) {
213 					map = doubles;
214 				} else
215 				if (field.getType() == String.class) {
216 					map = strings;
217 				} else
218 				if (field.getType() == Boolean.class) {
219 					map = booleans;
220 				} else
221 				if (field.getType() == UnrealId.class) {
222 					map = strings;
223 				} else 
224 				if (field.getType() == Location.class) {
225 					map = strings;
226 				} else {					
227 					throw new RuntimeException("Cannot create CustomMessageDeserializer for " + customControlMessageClass + " as field " + field.getDeclaringClass() + "." + field.getName() + " is of invalid type " + field.getType() + ", only Integer, Double, String, Boolean, UnrealId, Location is supported.");
228 				}
229 				
230 				if (map.containsKey(info.index())) {
231 					throw new RuntimeException("Cannot create CustomMessageDeserializer for " + customControlMessageClass + " as field " + field.getDeclaringClass() + "." + field.getName() + " is referencing index " + info.index() + " that has already been defined/taken by field " + map.get(info.index()).getName() + ".");
232 				}
233 				
234 				map.put(info.index(), field);
235 				
236 				if (field.getType() == Integer.class) {
237 					if (info.index() >= getterIntegers.length) {
238 						throw new RuntimeException("Cannot create CustomMessageDeserializer for " + customControlMessageClass + " as field " + field.getDeclaringClass() + "." + field.getName() + " has unexpected index " + info.index() + " for ControlMessage Integer field, I do not have ControlMessageGetter for that!");
239 					}
240 					fields.add(
241 						new Tuple3<Field, ControlMessageGetter, ControlMessageTypeMapper>(
242 							field,
243 							getterIntegers[info.index()],
244 							ControlMessageTypeMapper.DIRECT_MAPPER
245 						)
246 					);	
247 				} else
248 				if (field.getType() == Double.class) {
249 					if (info.index() >= getterDoubles.length) {
250 						throw new RuntimeException("Cannot create CustomMessageDeserializer for " + customControlMessageClass + " as field " + field.getDeclaringClass() + "." + field.getName() + " has unexpected index " + info.index() + " for ControlMessage Double field, I do not have ControlMessageGetter for that!");
251 					}
252 					fields.add(
253 						new Tuple3<Field, ControlMessageGetter, ControlMessageTypeMapper>(
254 							field,
255 							getterDoubles[info.index()],
256 							ControlMessageTypeMapper.DIRECT_MAPPER
257 						)
258 					);	
259 				} else 
260 				if (field.getType() == String.class) {
261 					if (info.index() >= getterStrings.length) {
262 						throw new RuntimeException("Cannot create CustomMessageDeserializer for " + customControlMessageClass + " as field " + field.getDeclaringClass() + "." + field.getName() + " has unexpected index " + info.index() + " for ControlMessage String field, I do not have ControlMessageGetter for that!");
263 					}
264 					fields.add(
265 						new Tuple3<Field, ControlMessageGetter, ControlMessageTypeMapper>(
266 							field,
267 							getterStrings[info.index()],
268 							ControlMessageTypeMapper.DIRECT_MAPPER
269 						)
270 					);	
271 				} else
272 				if (field.getType() == Boolean.class) {
273 					if (info.index() >= getterBooleans.length) {
274 						throw new RuntimeException("Cannot create CustomMessageDeserializer for " + customControlMessageClass + " as field " + field.getDeclaringClass() + "." + field.getName() + " has unexpected index " + info.index() + " for ControlMessage Boolean field, I do not have ControlMessageGetter for that!");
275 					}
276 					fields.add(
277 						new Tuple3<Field, ControlMessageGetter, ControlMessageTypeMapper>(
278 							field,
279 							getterBooleans[info.index()],
280 							ControlMessageTypeMapper.DIRECT_MAPPER
281 						)
282 					);	
283 				} else 
284 				if (field.getType() == UnrealId.class) {
285 					if (info.index() >= getterStrings.length) {
286 						throw new RuntimeException("Cannot create CustomMessageDeserializer for " + customControlMessageClass + " as field " + field.getDeclaringClass() + "." + field.getName() + " has unexpected index " + info.index() + " for ControlMessage String field, I do not have ControlMessageGetter for that!");
287 					}
288 					fields.add(
289 						new Tuple3<Field, ControlMessageGetter, ControlMessageTypeMapper>(
290 							field,
291 							getterStrings[info.index()],
292 							ControlMessageTypeMapper.STRING_2_UNREAL_ID_MAPPER
293 						)
294 					);	
295 				} else 
296 				if (field.getType() == Location.class) {
297 					if (info.index() >= getterStrings.length) {
298 						throw new RuntimeException("Cannot create CustomMessageDeserializer for " + customControlMessageClass + " as field " + field.getDeclaringClass() + "." + field.getName() + " has unexpected index " + info.index() + " for ControlMessage String field, I do not have ControlMessageGetter for that!");
299 					}
300 					fields.add(
301 						new Tuple3<Field, ControlMessageGetter, ControlMessageTypeMapper>(
302 							field,
303 							getterStrings[info.index()],
304 							ControlMessageTypeMapper.STRING_2_LOCATION_MAPPER
305 						)
306 					);	
307 				} else {
308 					throw new RuntimeException("Cannot create CustomMessageDeserializer for " + customControlMessageClass + " as field " + field.getDeclaringClass() + "." + field.getName() + " is of invalid type " + field.getType() + ", only Integer, Double, String, Boolean, UnrealId, Location is supported.");
309 				}
310 			}
311 		}
312 		
313 		if (simTimeField == null) {
314 			throw new RuntimeException("Cannot create CustomMessageDeserializer for " + customControlMessageClass + " as no long-typed field was annotated with @CustomMessageSimTime.");
315 		}
316 	}
317 	
318 	public Class<T> getDescriptor() {
319 		return descriptor;
320 	}
321 	
322 	public String getType() {
323 		return type;
324 	}
325 
326 	@SuppressWarnings("rawtypes")
327 	public T deserialize(ControlMessage message) {
328 		T result;
329 		
330 		try {
331 			result = constructor.newInstance();
332 		} catch (Exception e) {
333 			throw new RuntimeException("Failed to instantiate new " + descriptor + ".", e);
334 		}
335 		
336 		for (Tuple3<Field, ControlMessageGetter, ControlMessageTypeMapper> field : fields) {
337 			Object baseValue = field.getSecond().get(message);
338 			Object value = field.getThird().map(baseValue);
339 			try {
340 				field.getFirst().setAccessible(true);
341 				field.getFirst().set(result, value);
342 			} catch (Exception e) {
343 				throw new RuntimeException("Failed to set " + descriptor + "." + field.getFirst().getName() + " with value '" + value + "' mapped from base value '" + baseValue + "'." , e);
344 			}
345 			
346 		}
347 		
348 		try {
349 			simTimeField.setAccessible(true);
350 			simTimeField.set(result, message.getSimTime());
351 		} catch (Exception e) {
352 			throw new RuntimeException("Failed to set " + descriptor + "." + simTimeField.getName() + " with value '" + message.getSimTime() + "'", e);
353 		}
354 		
355 		return result;
356 	}
357 
358 }