Project

General

Profile

Download (15.9 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
			return false;
124
		}
125
		matching.deleteTemporaryMatchers(); //just in case they are not yet deleted during last invoke
126
		if (! matchClass.isAssignableFrom(matchFirst.getClass()) ){
127
			throw new MatchException("Match object are of different type than the match class (" + matchClass + ") this match strategy was created with");
128
		}else if (matchClass != matchFirst.getClass()){
129
			initializeSubclass(matchFirst.getClass());
130
		}
131
		try {
132
			result = invokeChecked(matchFirst, matchSecond, result);
133
		}catch (MatchException e) {
134
			throw e;
135
		}finally{
136
			matching.deleteTemporaryMatchers();
137
		}
138
		return result;
139
	}
140

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

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

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

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

    
291

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

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

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

    
457
	
458
}
(2-2/10)