Project

General

Profile

Download (16.4 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.util.ArrayList;
18
import java.util.Collection;
19
import java.util.HashMap;
20
import java.util.List;
21
import java.util.Map;
22
import java.util.Set;
23
import java.util.UUID;
24

    
25
import org.apache.log4j.Logger;
26

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

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

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

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

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

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

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

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

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

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

    
295

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

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

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

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

    
473
	
474
}
(2-2/10)