Project

General

Profile

Download (16.5 KB) Statistics
| Branch: | Tag: | Revision:
1
// $Id$
2
/**
3
* Copyright (C) 2007 EDIT
4
* European Distributed Institute of Taxonomy 
5
* http://www.e-taxonomy.eu
6
* 
7
* The contents of this file are subject to the Mozilla Public License Version 1.1
8
* See LICENSE.TXT at the top of this package for the full license terms.
9
*/
10

    
11
package eu.etaxonomy.cdm.strategy.match;
12

    
13
import java.lang.annotation.Annotation;
14
import java.lang.reflect.Field;
15
import java.lang.reflect.GenericDeclaration;
16
import java.lang.reflect.Type;
17
import java.net.URI;
18
import java.util.ArrayList;
19
import java.util.Collection;
20
import java.util.HashMap;
21
import java.util.List;
22
import java.util.Map;
23
import java.util.Set;
24
import java.util.UUID;
25

    
26
import org.apache.log4j.Logger;
27

    
28
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;
29
import sun.reflect.generics.reflectiveObjects.TypeVariableImpl;
30
import eu.etaxonomy.cdm.common.CdmUtils;
31
import eu.etaxonomy.cdm.common.DoubleResult;
32
import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
33
import eu.etaxonomy.cdm.model.common.CdmBase;
34
import eu.etaxonomy.cdm.strategy.StrategyBase;
35
import eu.etaxonomy.cdm.strategy.match.Match.ReplaceMode;
36

    
37
/**
38
 * @author a.mueller
39
 * @created 06.08.2009
40
 * @version 1.0
41
 */
42
public class DefaultMatchStrategy extends StrategyBase implements IMatchStrategy {
43
	private static final long serialVersionUID = 5045874493910155162L;
44
	private static final Logger logger = Logger.getLogger(DefaultMatchStrategy.class);
45

    
46
	final static UUID uuid = UUID.fromString("69467b70-07ec-43a6-b779-3ec8d013837b");
47
	
48
	public static DefaultMatchStrategy NewInstance(Class<? extends IMatchable> matchClazz){
49
		return new DefaultMatchStrategy(matchClazz);
50
	}
51
	
52
//	protected Map<String, MatchMode> matchModeMap = new HashMap<String, MatchMode>();
53
	protected MatchMode defaultMatchMode = MatchMode.EQUAL;
54
	protected MatchMode defaultCollectionMatchMode = MatchMode.IGNORE;
55
	protected MatchMode defaultMatchMatchMode = MatchMode.MATCH;
56
	
57
	protected Class<? extends IMatchable> matchClass;
58
	protected Map<String, Field> matchFields;
59
	protected Matching matching = new Matching();
60
	
61
	protected DefaultMatchStrategy(Class<? extends IMatchable> matchClazz) {
62
		super();
63
		if (matchClazz == null){
64
			throw new IllegalArgumentException("Match class must not be null");
65
		}
66
		this.matchClass = matchClazz;
67
		initMapping();
68
	}
69
	
70
	/**
71
	 * @return the merge class
72
	 */
73
	public Class<? extends IMatchable> getMatchClass() {
74
		return matchClass;
75
	}
76

    
77
	/* (non-Javadoc)
78
	 * @see eu.etaxonomy.cdm.strategy.StrategyBase#getUuid()
79
	 */
80
	@Override
81
	protected UUID getUuid() {
82
		return uuid;
83
	}
84
	
85
	/* (non-Javadoc)
86
	 * @see eu.etaxonomy.cdm.strategy.match.IMatchStrategy#getMatching()
87
	 */
88
	public Matching getMatching() {
89
		return matching;
90
	}
91

    
92
	/* (non-Javadoc)
93
	 * @see eu.etaxonomy.cdm.strategy.match.IMatchStrategy#setMatchMode(java.lang.String, eu.etaxonomy.cdm.strategy.match.MatchMode)
94
	 */
95
	public void setMatchMode(String propertyName, MatchMode matchMode)
96
			throws MatchException {
97
		if (matchFields.containsKey(propertyName)){
98
			FieldMatcher fieldMatcher = FieldMatcher.NewInstance(matchFields.get(propertyName), matchMode);
99
			matching.setFieldMatcher(fieldMatcher);
100
		}else{
101
			throw new MatchException("The class " + matchClass.getName() + " does not contain a field named " + propertyName);
102
		}
103
	}
104
	
105
	/* (non-Javadoc)
106
	 * @see eu.etaxonomy.cdm.strategy.match.IMatchStrategy#getMatchMode(java.lang.String)
107
	 */
108
	public MatchMode getMatchMode(String propertyName) {
109
		FieldMatcher fieldMatcher = matching.getFieldMatcher(propertyName);
110
		return fieldMatcher == null ? defaultMatchMode : fieldMatcher.getMatchMode();
111
	}
112

    
113
	/* (non-Javadoc)
114
	 * @see eu.etaxonomy.cdm.strategy.match.IMatchStrategy#invoke(eu.etaxonomy.cdm.strategy.match.IMatchable, eu.etaxonomy.cdm.strategy.match.IMatchable)
115
	 */
116
	public <T extends IMatchable> boolean invoke(T matchFirst, T matchSecond)
117
			throws MatchException {
118
		boolean result = true;
119
		if (matchFirst == null || matchSecond == null){
120
			return false;
121
		}else if (matchFirst == matchSecond){
122
			return true;
123
		}else if (matchFirst.getClass() != matchSecond.getClass()){
124
			matchFirst = (T) HibernateProxyHelper.deproxy(matchFirst);
125
			matchSecond = (T) HibernateProxyHelper.deproxy(matchSecond);
126
			if (matchFirst.getClass() != matchSecond.getClass()){
127
				return false;
128
			}
129
		}
130
		matching.deleteTemporaryMatchers(); //just in case they are not yet deleted during last invoke
131
		if (! matchClass.isAssignableFrom(matchFirst.getClass()) ){
132
			throw new MatchException("Match object are of different type than the match class (" + matchClass + ") this match strategy was created with");
133
		}else if (matchClass != matchFirst.getClass()){
134
			initializeSubclass(matchFirst.getClass());
135
		}
136
		try {
137
			result = invokeChecked(matchFirst, matchSecond, result);
138
		}catch (MatchException e) {
139
			throw e;
140
		}finally{
141
			matching.deleteTemporaryMatchers();
142
		}
143
		return result;
144
	}
145

    
146
	/**
147
	 * @param class1
148
	 */
149
	private void initializeSubclass(Class<? extends IMatchable> instanceClass) {
150
		Map<String, Field> subClassFields = CdmUtils.getAllFields(instanceClass, matchClass, false, false, true, false);
151
		for (Field field: subClassFields.values()){
152
			initField(field, true);
153
		}
154
	}
155

    
156
	/**
157
	 * @param <T>
158
	 * @param matchFirst
159
	 * @param matchSecond
160
	 * @param result
161
	 * @return
162
	 * @throws MatchException
163
	 */
164
	private <T extends IMatchable> boolean invokeChecked(T matchFirst, T matchSecond,
165
			boolean result) throws MatchException {
166
		//matchFirst != matchSecond != null
167
		try {
168
			Map<String, List<MatchMode>> replaceMatchers = new HashMap<String, List<MatchMode>>();
169
			for (CacheMatcher cacheMatcher: matching.getCacheMatchers()){
170
				Field protectedField = cacheMatcher.getProtectedField(matching);
171
				boolean protected1 = protectedField.getBoolean(matchFirst);
172
				boolean protected2 = protectedField.getBoolean(matchFirst);
173
				if (protected1 != protected2){
174
					return false;
175
				}else if (protected1 == false){
176
					//ignore
177
				}else{
178
					String cache1 = (String)cacheMatcher.getField().get(matchFirst);
179
					String cache2 = (String)cacheMatcher.getField().get(matchSecond);
180
					result = cacheMatcher.getMatchMode().matches(cache1, cache2, null);
181
					if (result == false){
182
						return false;
183
					}
184
					List<DoubleResult<String, MatchMode>> replacementModes = cacheMatcher.getReplaceMatchModes(matching);
185
					for (DoubleResult<String, MatchMode> replacementMode: replacementModes ){
186
						String propertyName = replacementMode.getFirstResult();
187
						List<MatchMode> replaceMatcherList = replaceMatchers.get(propertyName);
188
						if (replaceMatcherList == null){
189
							replaceMatcherList = new ArrayList<MatchMode>();
190
							replaceMatchers.put(propertyName, replaceMatcherList);
191
						}
192
						replaceMatcherList.add(replacementMode.getSecondResult());
193
					}
194
				}
195
			}
196
			for (FieldMatcher fieldMatcher : matching.getFieldMatchers(true)){
197
				Field field = fieldMatcher.getField();
198
				List<MatchMode> replaceModeList = replaceMatchers.get(fieldMatcher.getPropertyName());
199
				if (replaceModeList == null){
200
					replaceModeList = new ArrayList<MatchMode>();
201
				}
202
				Class<?> fieldType = field.getType();
203
				logger.debug(field.getName() + ": ");
204
				if (isPrimitive(fieldType)){
205
					result &= matchPrimitiveField(matchFirst, matchSecond, fieldMatcher, replaceModeList);
206
				}else if (fieldType == String.class ){
207
					result &= matchPrimitiveField(matchFirst, matchSecond, fieldMatcher, replaceModeList);
208
				}else if(isUserType(fieldType)){
209
					result &= matchPrimitiveField(matchFirst, matchSecond, fieldMatcher, replaceModeList);
210
				}else if(fieldType == UUID.class){
211
					//result &= matchPrimitiveField(matchFirst, matchSecond, fieldMatcher, replaceModeList);
212
				}else if(fieldType == URI.class){
213
					result &= matchPrimitiveField(matchFirst, matchSecond, fieldMatcher, replaceModeList);
214
				}else if(isSingleCdmBaseObject(fieldType)){
215
					result &= matchPrimitiveField(matchFirst, matchSecond, fieldMatcher, replaceModeList);
216
				}else if (isCollection(fieldType)){
217
					result &= matchCollectionField(matchFirst, matchSecond, fieldMatcher, replaceModeList);
218
				}else if(fieldType.isInterface()){
219
					result &= matchPrimitiveField(matchFirst, matchSecond, fieldMatcher, replaceModeList);
220
				}else if(fieldType.isEnum()){
221
					result &= matchPrimitiveField(matchFirst, matchSecond, fieldMatcher, replaceModeList);
222
				}else{
223
					throw new RuntimeException("Unknown Object type for matching: " + fieldType);
224
				}
225
//				if (result == false){
226
//					return result;
227
//				}
228
			}
229
		} catch (Exception e) {
230
			throw new MatchException("Match Exception in invoke", e);
231
		}
232
		return result;
233
	}
234

    
235
		
236
	/**
237
	 * @throws Exception 
238
	 * 
239
	 */
240
	private <T extends IMatchable> boolean matchPrimitiveField(T matchFirst, T matchSecond, FieldMatcher fieldMatcher, List<MatchMode> replaceModeList) throws Exception {
241
		Field field = fieldMatcher.getField();
242
		Object value1 = checkEmpty(field.get(matchFirst));
243
		Object value2 = checkEmpty(field.get(matchSecond));
244
		IMatchStrategy matchStrategy = fieldMatcher.getMatchStrategy();
245
		boolean result = fieldMatcher.getMatchMode().matches(value1, value2, matchStrategy);
246
		for (MatchMode replaceMode : replaceModeList){
247
			if (result == true){
248
				break; 
249
			}
250
			result |= replaceMode.matches(value1, value2, null);
251
		}
252
		logger.debug(fieldMatcher.getMatchMode() + ", " + field.getType().getName()+ ": " + result);
253
		return result;
254
	}
255

    
256
	/**
257
	 * @throws Exception 
258
	 * 
259
	 */
260
	private <T extends IMatchable> boolean matchCollectionField(T matchFirst, T matchSecond, FieldMatcher fieldMatcher, List<MatchMode> replaceModeList) throws Exception {
261
		boolean result;
262
		Field field = fieldMatcher.getField();
263
		Collection value1 = (Collection)field.get(matchFirst);
264
		Collection value2 = (Collection)field.get(matchSecond);
265
		MatchMode matchMode = fieldMatcher.getMatchMode();
266
		Class<?> fieldType = fieldMatcher.getField().getType();
267
		IMatchStrategy matchStrategy = fieldMatcher.getMatchStrategy();
268
		if (matchMode.isMatch()){
269
			Class collectionType = getTypeOfSet(field);
270
			if (! IMatchable.class.isAssignableFrom(collectionType)){
271
				//TODO
272
				return fieldMatcher.getMatchMode().matches(value1, value2, matchStrategy);	
273
			}
274
			if (matchStrategy == null){
275
				matchStrategy = DefaultMatchStrategy.NewInstance(collectionType);
276
			}
277
			if (Set.class.isAssignableFrom(fieldType)){
278
				result = matchSet(value1, value2, matchStrategy);
279
			}else if (List.class.isAssignableFrom(fieldType)){
280
				result = matchList(value1, value2, matchStrategy);
281
			}else{
282
				throw new MatchException("Collection type not yet supported: " + fieldType);
283
			}
284
		}else{
285
			result = fieldMatcher.getMatchMode().matches(value1, value2, matchStrategy);
286
		}
287
		//cache replace modes
288
		for (MatchMode replaceMode : replaceModeList){
289
			if (result == true){
290
				break; 
291
			}
292
			result |= replaceMode.matches(value1, value2, null);
293
		}
294
		logger.debug(fieldMatcher.getMatchMode() + ", " + field.getType().getName()+ ": " + result);
295
		return result;
296
	}
297

    
298

    
299
	/**
300
	 * @param object
301
	 * @return
302
	 */
303
	private Object checkEmpty(Object object) {
304
		if (object instanceof String){
305
			if (CdmUtils.isEmpty((String)object)){
306
				return null;
307
			}
308
		}
309
		return HibernateProxyHelper.deproxy(object);
310
	}
311

    
312
	/**
313
	 * @param value1
314
	 * @param matchStrategy
315
	 * @return
316
	 * @throws MatchException
317
	 */
318
	private boolean matchSet(Collection value1, Collection value2, IMatchStrategy matchStrategy)
319
			throws MatchException {
320
		boolean result;
321
		Set<IMatchable> set1 = (Set<IMatchable>)value1;
322
		Set<IMatchable> set2 = (Set<IMatchable>)value2;
323
		if (set1.size()!= set2.size()){
324
			return false;
325
		}
326
		result = true;
327
		for (IMatchable setItem1: set1){
328
			boolean matches = false;
329
			for (IMatchable setItem2: set2){
330
				matches |= matchStrategy.invoke(setItem1, setItem2);
331
			}
332
			if (matches == false){
333
				return false;
334
			}
335
		}
336
		return result;
337
	}
338
	
339
	/**
340
	 * @param value1
341
	 * @param matchStrategy
342
	 * @return
343
	 * @throws MatchException
344
	 */
345
	private boolean matchList(Collection value1, Collection value2, IMatchStrategy matchStrategy)
346
			throws MatchException {
347
		boolean result;
348
		List<IMatchable> list1 = (List<IMatchable>)value1;
349
		List<IMatchable> list2 = (List<IMatchable>)value2;
350
		if(list1 == null && list2 == null) {
351
			return true;
352
		}
353
		
354
		if ((list1 != null && list2 == null) || (list1 == null && list2 != null) || (list1.size()!= list2.size())){
355
			return false;
356
		}
357
		
358
		result = true;
359
		for (int i = 0; i < list1.size(); i++){
360
			IMatchable listObject1 = list1.get(i);
361
			IMatchable listObject2 = list2.get(i);
362
			if (! matchStrategy.invoke(listObject1, listObject2)){
363
				return false;
364
			}
365
		}
366
		return result;
367
	}
368
	
369
	private Class getTypeOfSet(Field field) throws MatchException{
370
		Type genericType = (ParameterizedTypeImpl)field.getGenericType();
371
		if (genericType instanceof ParameterizedTypeImpl){
372
			ParameterizedTypeImpl paraType = (ParameterizedTypeImpl)genericType;
373
			paraType.getRawType();
374
			Type[] arguments = paraType.getActualTypeArguments();
375
			if (arguments.length == 1){
376
				Class collectionClass;
377
				try {
378
					if (arguments[0] instanceof Class){
379
						return (Class)arguments[0];
380
					}else if(arguments[0] instanceof TypeVariableImpl){
381
						TypeVariableImpl typeVariable = (TypeVariableImpl)arguments[0];
382
						GenericDeclaration genericDeclaration = typeVariable.getGenericDeclaration();
383
						return (Class)genericDeclaration;
384
					}else{
385
						logger.warn("Unknown Type");
386
						throw new MatchException("");
387
					}
388
				} catch (Exception e) {
389
					logger.warn(e.getMessage());
390
					throw new MatchException("");
391
				}
392
			}else{
393
				logger.warn("Length of arguments <> 1");
394
				throw new MatchException("");
395
			}
396
		}else{
397
			logger.warn("Not a generic type of type ParameterizedTypeImpl");
398
			throw new MatchException("Collection type could not be determined. Generic type is not of type ParamterizedTypeImpl");
399
		}
400
	}
401
	
402
	
403
	/**
404
	 * 
405
	 */
406
	private void initMapping() {
407
		boolean includeStatic = false;
408
		boolean includeTransient = false;
409
		boolean makeAccessible = true;
410
		matchFields = CdmUtils.getAllFields(matchClass, CdmBase.class, includeStatic, includeTransient, makeAccessible, true);
411
		for (Field field: matchFields.values()){
412
			initField(field, false);
413
		}
414
	}
415

    
416
	/**
417
	 * Initializes the matching for a single field
418
	 * @param field
419
	 */
420
	private void initField(Field field, boolean temporary) {
421
		MatchMode matchMode = null;
422
		IMatchStrategy matchStrategy = null;
423
		for (Annotation annotation : field.getAnnotations()){
424
			if (annotation.annotationType() == Match.class){
425
				Match match = ((Match)annotation);
426
				matchMode = match.value();
427
				if (matchMode == MatchMode.CACHE){
428
					ReplaceMode replaceMode = match.cacheReplaceMode();
429
					String[] cachePropertyReplaces = match.cacheReplacedProperties();
430
					MatchMode replaceMatchMode = match.replaceMatchMode();
431
					matching.addCacheMatcher(CacheMatcher.NewInstance(field, replaceMode, cachePropertyReplaces, replaceMatchMode));
432
					
433
				}else{
434
					matching.setFieldMatcher(FieldMatcher.NewInstance(field, matchMode), temporary);
435
				}
436
			}
437
		}
438
		Class fieldType = field.getType();
439
		if (matchMode == null){
440
			if (isCollection(fieldType)){
441
				matchMode = defaultCollectionMatchMode;
442
				matching.setFieldMatcher(FieldMatcher.NewInstance(field, matchMode), temporary);
443
			}else if (fieldType.isInterface()){
444
				//TODO could be handled more sophisticated
445
				matchMode = defaultMatchMatchMode;
446
				matching.setFieldMatcher(FieldMatcher.NewInstance(field, matchMode, matchStrategy), temporary);
447
			}else if (isSingleCdmBaseObject(fieldType)){
448
				if (IMatchable.class.isAssignableFrom(fieldType)){
449
					matchMode = defaultMatchMatchMode;
450
					if (matchStrategy == null){
451
						if (fieldType == this.matchClass){
452
							matchStrategy = this;
453
						}else{
454
							matchStrategy = DefaultMatchStrategy.NewInstance(fieldType);
455
						}
456
					}
457
					matching.setFieldMatcher(FieldMatcher.NewInstance(field, matchMode, matchStrategy), temporary);
458
				}else{
459
					matchMode = defaultMatchMode;
460
					matching.setFieldMatcher(FieldMatcher.NewInstance(field, matchMode), temporary);
461
				}
462
			}else{
463
				matchMode = defaultMatchMode;
464
				matching.setFieldMatcher(FieldMatcher.NewInstance(field, matchMode), temporary);
465
			}	
466
		}
467
	}
468

    
469
	/* (non-Javadoc)
470
	 * @see eu.etaxonomy.cdm.strategy.match.IMatchStrategy#getMatchFields()
471
	 */
472
	public Set<String> getMatchFieldPropertyNames() {
473
		return matchFields.keySet();
474
	}
475

    
476
	
477
}
(2-2/10)