View Javadoc

1   /**
2    * 
3    */
4   package org.ximtec.igesture.core.composite;
5   
6   import java.lang.reflect.InvocationTargetException;
7   import java.lang.reflect.Method;
8   import java.text.ParseException;
9   import java.text.SimpleDateFormat;
10  import java.util.ArrayList;
11  import java.util.Calendar;
12  import java.util.Date;
13  import java.util.HashMap;
14  import java.util.HashSet;
15  import java.util.Iterator;
16  import java.util.List;
17  import java.util.Map;
18  import java.util.Set;
19  
20  import org.sigtec.ink.Note;
21  import org.ximtec.igesture.core.DefaultDataObject;
22  import org.ximtec.igesture.core.Gesture;
23  import org.ximtec.igesture.core.GestureSample;
24  import org.ximtec.igesture.core.GestureSample3D;
25  import org.ximtec.igesture.io.GestureDevice;
26  import org.ximtec.igesture.io.IDeviceManager;
27  import org.ximtec.igesture.io.IUser;
28  import org.ximtec.igesture.util.MultiValueMap;
29  import org.ximtec.igesture.util.additions3d.RecordedGesture3D;
30  
31  /**
32   * @author Bjorn Puype, bpuype@gmail.com
33   *
34   */
35  public abstract class DefaultConstraint extends DefaultDataObject implements Constraint {
36  	
37  	protected Map<String, String> DEFAULT_CONFIGURATION = new HashMap<String, String>();
38  	protected Map<String, String> setterMapping = new HashMap<String, String>();
39  	
40  	protected List<DefaultConstraintEntry> gestures;
41  	public static final String PROPERTY_GESTURES = "gestures";
42  	
43  	protected SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss.SSS");
44  	
45  	protected Calendar gestureTime;
46  	protected Calendar processingTime;
47  	
48  	public enum Config{
49  		GESTURE_TIME
50  	}
51  	
52  	private static final String GESTURE_TIME = "00:00:10.000";
53  	
54  	public DefaultConstraint()
55  	{
56  		DEFAULT_CONFIGURATION.put(Config.GESTURE_TIME.name(), GESTURE_TIME);
57  		setterMapping.put(Config.GESTURE_TIME.name(), "setGestureTime");
58  		gestures = new ArrayList<DefaultConstraintEntry>();
59  		df.setLenient(false);
60  		try {
61  //			df.parse("00:00:10.000");
62  //			gestureTime = df.getCalendar();
63  //			df.parse("00:00:02.000");
64  //			processingTime = df.getCalendar();
65  			Date d = df.parse(GESTURE_TIME);
66  			gestureTime = Calendar.getInstance();
67  			gestureTime.setTime(d);
68  			d = df.parse("00:00:02.000");
69  			processingTime = Calendar.getInstance();
70  			processingTime.setTime(d);
71  		} catch (ParseException e) {
72  			e.printStackTrace();
73  		}
74  		
75  	}
76  	
77  	/* (non-Javadoc)
78  	 * @see org.ximtec.igesture.core.composite.Constraint#addGestureClass(java.lang.String)
79  	 */
80  	@Override
81  	public void addGestureClass(String gestureClass) {
82  		DefaultConstraintEntry entry = new DefaultConstraintEntry(gestureClass); 
83  		gestures.add(entry);
84  		propertyChangeSupport.fireIndexedPropertyChange(PROPERTY_GESTURES, gestures.indexOf(entry), null, entry);
85  	}
86  
87  	/* (non-Javadoc)
88  	 * @see org.ximtec.igesture.core.composite.Constraint#addGestureClass(java.lang.String, int)
89  	 */
90  	@Override
91  	public void addGestureClass(String gestureClass, int user) {
92  		DefaultConstraintEntry entry = new DefaultConstraintEntry(gestureClass, user);
93  		gestures.add(entry);
94  		propertyChangeSupport.fireIndexedPropertyChange(PROPERTY_GESTURES, gestures.indexOf(entry), null, entry);
95  	}
96  
97  	/* (non-Javadoc)
98  	 * @see org.ximtec.igesture.core.composite.Constraint#addGestureClass(java.lang.String, java.lang.String, java.util.Set<String>)
99  	 */
100 	@Override
101 	public void addGestureClass(String gestureClass, String deviceType, Set<String> devices) {
102 		DefaultConstraintEntry entry = new DefaultConstraintEntry(gestureClass, deviceType, devices); 
103 		gestures.add(entry);
104 		propertyChangeSupport.fireIndexedPropertyChange(PROPERTY_GESTURES, gestures.indexOf(entry), null, entry);
105 	}
106 
107 	/* (non-Javadoc)
108 	 * @see org.ximtec.igesture.core.composite.Constraint#addGestureClass(java.lang.String, int, java.lang.String, java.util.Set<String>)
109 	 */
110 	@Override
111 	public void addGestureClass(String gestureClass, int user, String deviceType, Set<String> devices) {
112 		DefaultConstraintEntry entry = new DefaultConstraintEntry(gestureClass, deviceType, devices);
113 		gestures.add(entry);
114 		propertyChangeSupport.fireIndexedPropertyChange(PROPERTY_GESTURES, gestures.indexOf(entry), null, entry);
115 	}
116 
117 	/* (non-Javadoc)
118 	 * @see org.ximtec.igesture.core.composite.Constraint#getGestureClasses()
119 	 */
120 	@Override
121 	public List<String> getGestureClasses() {
122 		List<String> list = new ArrayList<String>();
123 		for(DefaultConstraintEntry entry : gestures)
124 			list.add(entry.getGesture());
125 		return list;
126 	}
127 	
128 	/* (non-Javadoc)
129 	 * @see org.ximtec.igesture.core.composite.Constraint#getDistinctGestureClasses()
130 	 */
131 	@Override
132 	public Set<String> getDistinctGestureClasses() {
133 		Set<String> set = new HashSet<String>();
134 		set.addAll(getGestureClasses());
135 		return set;
136 	}
137 	
138 	/* (non-Javadoc)
139 	 * @see org.ximtec.igesture.core.composite.Constraint#getNumberOfGestures()
140 	 */
141 	@Override
142 	public int getNumberOfGestures() {
143 		return gestures.size();
144 	}
145 
146 	public void setGestureTime(String time) throws ParseException
147 	{
148 		Date d = df.parse(time);
149 		gestureTime = Calendar.getInstance();
150 		gestureTime.setTime(d);
151 	}
152 	
153 	public Calendar getGestureTime()
154 	{
155 		return gestureTime;
156 	}
157 	
158 	/* (non-Javadoc)
159 	 * @see org.ximtec.igesture.core.composite.Constraint#generatePatterns(java.util.Map)
160 	 */
161 	@Override
162 	public abstract Set<String> generatePatterns(Map<String, String> charMapping);
163 
164 	/* (non-Javadoc)
165 	 * @see org.ximtec.igesture.core.composite.Constraint#validateConditions(java.util.List)
166 	 */
167 	@Override
168 	public boolean validateConditions(List<Gesture<?>> gestures, IDeviceManager manager)
169 	{
170 		boolean conditionsValid = true;
171 		Map<Gesture<?>,GestureDevice<?,?>> map = new HashMap<Gesture<?>,GestureDevice<?,?>>();
172 		
173 		/* device checks */
174 		boolean found = true;
175 		for(Gesture<?> gesture : gestures)
176 		{
177 			//get the gesture class of the gesture
178 			String gestureClass = gesture.getName();
179 			//get all entries that are of the same gesture class
180 			List<DefaultConstraintEntry> entries = new ArrayList<DefaultConstraintEntry>();
181 			for(DefaultConstraintEntry entry : this.gestures)
182 			{
183 				if(entry.getGesture().equals(gestureClass))
184 					entries.add(entry);
185 			}
186 			//check if the gesture is performed by an allowed device
187 			for (Iterator<DefaultConstraintEntry> iterator = entries.iterator(); iterator.hasNext();) {
188 				
189 				DefaultConstraintEntry entry = iterator.next();
190 				GestureDevice<?,?> device = gesture.getSource();
191 				
192 				// if no device type specified, continue with next entry
193 				if(entry.getDeviceType() == null)
194 					continue;
195 				// check if device type matches
196 				if(entry.getDeviceType().equals(device.getDeviceClass()))
197 				{
198 					// if specific devices were specified...
199 					if(entry.getDevices() != null && !entry.getDevices().isEmpty())
200 					{
201 						// ... check if the gesture was created by a specified device (based on ID)
202 						if(entry.getDevices().contains(device.getDeviceID()))
203 							found = true;
204 						else
205 							found = false;
206 					}
207 					else
208 						found = true;						
209 				}
210 				
211 				if(found == true)
212 				{
213 					map.put(gesture, device);
214 					break;
215 				}
216 			}
217 			
218 			if(found == false)
219 			{
220 				conditionsValid = false;
221 				break;
222 			}
223 		}
224 		
225 		/* user check */
226 		if(conditionsValid && manager!= null) // if previous conditions hold
227 		{
228 			MultiValueMap<Integer, String> defined = new MultiValueMap<Integer, String>();
229 			MultiValueMap<IUser, String> recognised = new MultiValueMap<IUser, String>();
230 			
231 			//preprocess defined gestures
232 			for (Iterator<DefaultConstraintEntry> iterator = this.gestures.iterator(); iterator.hasNext();) {
233 				DefaultConstraintEntry entry = iterator.next();
234 				if(entry.getUser() != -1)
235 					defined.add(entry.getUser(), entry.getGesture());				
236 			}
237 			
238 			//preprocess recognised gestures
239 			for (Iterator<Gesture<?>> iterator = gestures.iterator(); iterator.hasNext();) {
240 				Gesture<?> gesture = iterator.next();
241 				recognised.add(manager.getAssociatedUser(gesture.getSource()), gesture.getName());
242 			}
243 			
244 			//compare gestures
245 			Set<Integer> definedKeys = defined.keySet();
246 			Set<IUser> recognisedKeys = recognised.keySet();
247 			
248 //			int matches = 0;
249 			
250 			//check if for every defined (user,gestures) combination, there is an equivalent in the recognised set
251 			for (Iterator<Integer> iterator = definedKeys.iterator(); iterator.hasNext();) {
252 				Integer integer = iterator.next();
253 				
254 				// put all users that perform at least the required gestures (those that were defined)
255 				Map<IUser, Integer> fittingKeys = new HashMap<IUser, Integer>();
256 				
257 				List<String> values = defined.getValues(integer);
258 				for(Iterator<IUser> iter = recognisedKeys.iterator(); iter.hasNext();)
259 				{
260 					IUser user = iter.next();
261 					List<String> list = recognised.getValues(user);
262 					if(list.containsAll(values))
263 					{
264 						fittingKeys.put(user, list.size());
265 					}
266 				}
267 				
268 				// if not match found...
269 				if(fittingKeys.isEmpty())
270 				{
271 					conditionsValid = false; //... condition invalid
272 					break;
273 				}
274 				else
275 				{
276 					// ...use the first found shortest fit
277 					IUser shortestFittingKey = null;
278 					int min = Integer.MAX_VALUE;
279 					for(Iterator<IUser> iter = fittingKeys.keySet().iterator(); iter.hasNext();)
280 					{
281 						IUser user = iter.next();
282 						if(fittingKeys.get(user).intValue() < min)
283 						{
284 							min = fittingKeys.get(user).intValue();
285 							shortestFittingKey = user;
286 						}
287 					}
288 //					matches += 1;
289 					recognised.removeKey(shortestFittingKey);
290 				}
291 				
292 			}
293 //			// defining the user that has to perform a gesture is optional
294 //			// this means that in most cases there will only be a partial mapping from the recognised gestures 
295 //			// to the defined ones (so !recognisedKeys.isEmpty() is not usable)
296 //			// so it is only necessary to have for each defined gesture a mapping in the recognised gestures
297 //			if(matches != definedKeys.size())
298 //				conditionsValid = false;
299 		}
300 		else if(manager == null)
301 		{
302 			conditionsValid = false;
303 		}
304 		
305 		return conditionsValid;
306 	}
307 
308 	/* (non-Javadoc)
309 	 * @see org.ximtec.igesture.core.composite.Constraint#determineTimeWindows()
310 	 */
311 	@Override
312 	public abstract Map<String, Calendar> determineTimeWindows();
313 	
314 	/**
315 	 * Get the start or end timestamp of a gesture.
316 	 * @param gesture	the gesture from which to get the timestamp
317 	 * @param start		true to get the start timestamp, false to get the end timestamp
318 	 * @return	timestamp
319 	 */
320 	protected long getTimeStamp(Gesture<?> gesture, boolean start)
321 	{
322 		long timestamp = 0;
323 		if(gesture instanceof GestureSample)
324 		{
325 			Note note = ((GestureSample)gesture).getGesture();
326 			if(start)
327 				timestamp = note.getStartPoint().getTimestamp();
328 			else
329 				timestamp = note.getEndPoint().getTimestamp();
330 		}
331 		else if(gesture instanceof GestureSample3D)
332 		{
333 			RecordedGesture3D record = ((GestureSample3D)gesture).getGesture();
334 			if(start)
335 				timestamp = record.getStartPoint().getTimeStamp();
336 			else
337 				timestamp = record.getEndPoint().getTimeStamp();
338 		}
339 		else
340 			;//TODO error
341 		
342 		return timestamp;
343 	}
344 	
345 	public String toString()
346 	{
347 		return DefaultConstraint.class.getSimpleName();
348 	}
349 	
350 	/* (non-Javadoc)
351 	 * @see org.ximtec.igesture.core.composite.Constraint#getParameters()
352 	 */
353 	@Override
354 	public Map<String, String> getParameters() {
355 		return DEFAULT_CONFIGURATION;
356 	}
357 	
358 	/* (non-Javadoc)
359 	 * @see org.ximtec.igesture.core.composite.Constraint#getParameter(Java.lang.String property)
360 	 */
361 	@Override
362 	public String getParameter(String property)
363 	{
364 		return DEFAULT_CONFIGURATION.get(property);
365 	}
366 	
367 	/* (non-Javadoc)
368 	 * @see org.ximtec.igesture.core.composite.Constraint#setParameter(Java.lang.String property, Java.lang.String)
369 	 */
370 	@Override
371 	public void setParameter(String property, String value)
372 	{
373 		DEFAULT_CONFIGURATION.put(property, value);
374 		Class<?> clazz = this.getClass();
375 		try {
376 			Method m = clazz.getMethod(setterMapping.get(property), String.class);//no getDeclaredMethod because setGestureTime in DefaultConstraint
377 			m.invoke(this, value);
378 		} catch (SecurityException e) {
379 			e.printStackTrace();
380 		} catch (NoSuchMethodException e) {
381 			e.printStackTrace();
382 		} catch (IllegalArgumentException e) {
383 			e.printStackTrace();
384 		} catch (IllegalAccessException e) {
385 			e.printStackTrace();
386 		} catch (InvocationTargetException e) {
387 			e.printStackTrace();
388 		}
389 	}
390 	
391 	/* (non-Javadoc)
392 	 * @see org.ximtec.igesture.core.composite.Constraint#removeAllGestureClasses()
393 	 */
394 	@Override
395 	public void removeAllGestureClasses() {
396 		DefaultConstraintEntry entry = gestures.get(0);
397 		gestures.clear();
398 		propertyChangeSupport.fireIndexedPropertyChange(PROPERTY_GESTURES, 0, entry, null);
399 	}
400 
401 	/* (non-Javadoc)
402 	 * @see org.ximtec.igesture.core.composite.Constraint#removeGestureClass(org.ximtec.igesture.core.composite.DefaultConstraintEntry entry)
403 	 */
404 	@Override
405 	public void removeGestureClass(DefaultConstraintEntry entry)
406 	{
407 		gestures.remove(entry);
408 		propertyChangeSupport.fireIndexedPropertyChange(PROPERTY_GESTURES, gestures.indexOf(entry), entry, null);
409 	}
410 	
411 	/* (non-Javadoc)
412 	 * @see org.ximtec.igesture.core.composite.Constraint#getGestureEntries()
413 	 */
414 	@Override
415 	public List<DefaultConstraintEntry> getGestureEntries()
416 	{
417 		return gestures;
418 	}
419 	
420 }