3 * Copyright (C) 2007 EDIT
4 * European Distributed Institute of Taxonomy
5 * http://www.e-taxonomy.eu
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.
11 package eu
.etaxonomy
.cdm
.strategy
.merge
;
13 import java
.lang
.annotation
.Annotation
;
14 import java
.lang
.reflect
.Field
;
15 import java
.lang
.reflect
.GenericDeclaration
;
16 import java
.lang
.reflect
.Method
;
17 import java
.lang
.reflect
.Type
;
18 import java
.util
.ArrayList
;
19 import java
.util
.Collection
;
20 import java
.util
.HashMap
;
21 import java
.util
.HashSet
;
22 import java
.util
.List
;
25 import java
.util
.UUID
;
27 import org
.apache
.log4j
.Logger
;
29 import sun
.reflect
.generics
.reflectiveObjects
.ParameterizedTypeImpl
;
30 import sun
.reflect
.generics
.reflectiveObjects
.TypeVariableImpl
;
31 import eu
.etaxonomy
.cdm
.common
.CdmUtils
;
32 import eu
.etaxonomy
.cdm
.model
.common
.CdmBase
;
33 import eu
.etaxonomy
.cdm
.model
.common
.ICdmBase
;
34 import eu
.etaxonomy
.cdm
.model
.common
.IRelated
;
35 import eu
.etaxonomy
.cdm
.model
.common
.RelationshipBase
;
36 import eu
.etaxonomy
.cdm
.strategy
.StrategyBase
;
43 public class DefaultMergeStrategy
extends StrategyBase
implements IMergeStrategy
{
44 private static final long serialVersionUID
= -8513956338156791995L;
45 private static final Logger logger
= Logger
.getLogger(DefaultMergeStrategy
.class);
46 final static UUID uuid
= UUID
.fromString("d85cd6c3-0147-452c-8fed-bbfb82f392f6");
48 public static DefaultMergeStrategy
NewInstance(Class
<?
extends CdmBase
> mergeClazz
){
49 return new DefaultMergeStrategy(mergeClazz
);
52 protected Map
<String
, MergeMode
> mergeModeMap
= new HashMap
<String
, MergeMode
>();
53 protected MergeMode defaultMergeMode
= MergeMode
.FIRST
;
54 protected MergeMode defaultCollectionMergeMode
= MergeMode
.ADD
;
56 protected Class
<?
extends CdmBase
> mergeClass
;
57 protected Map
<String
, Field
> mergeFields
;
59 protected DefaultMergeStrategy(Class
<?
extends CdmBase
> mergeClazz
) {
61 if (mergeClazz
== null){
62 throw new IllegalArgumentException("Merge class must not be null");
64 this.mergeClass
= mergeClazz
;
65 boolean includeStatic
= false;
66 boolean includeTransient
= false;
67 boolean makeAccessible
= true;
68 this.mergeFields
= CdmUtils
.getAllFields(mergeClass
, CdmBase
.class, includeStatic
, includeTransient
, makeAccessible
, true);
77 private void initMergeModeMap() {
78 for (Field field
: mergeFields
.values()){
79 for (Annotation annotation
: field
.getAnnotations()){
80 if (annotation
.annotationType() == Merge
.class){
81 MergeMode mergeMode
= ((Merge
)annotation
).value();
82 mergeModeMap
.put(field
.getName(), mergeMode
);
91 * @see eu.etaxonomy.cdm.strategy.StrategyBase#getUuid()
94 protected UUID
getUuid() {
101 * @return the merge class
103 public Class
<?
extends CdmBase
> getMergeClass() {
110 * @param mergeClazz the mergeClazz to set
112 public void setMergeClazz(Class
<?
extends CdmBase
> mergeClazz
) {
113 this.mergeClass
= mergeClazz
;
119 * @see eu.etaxonomy.cdm.strategy.merge.IMergeStragegy#getMergeMode(java.lang.String)
121 public MergeMode
getMergeMode(String propertyName
){
122 MergeMode result
= mergeModeMap
.get(propertyName
);
124 Field field
= mergeFields
.get(propertyName
);
125 if (isCollection(field
.getType())){
126 return defaultCollectionMergeMode
;
128 return defaultMergeMode
;
135 public void setMergeMode(String propertyName
, MergeMode mergeMode
) throws MergeException
{
136 if (mergeFields
.containsKey(propertyName
)){
137 checkIdentifier(propertyName
, mergeMode
);
138 mergeModeMap
.put(propertyName
, mergeMode
);
140 throw new MergeException("The class " + mergeClass
.getName() + " does not contain a field with name " + propertyName
);
145 * Tests if a property is an identifier property
146 * @param propertyName
148 * @throws MergeException
150 private void checkIdentifier(String propertyName
, MergeMode mergeMode
) throws MergeException
{
151 if (mergeMode
!= MergeMode
.FIRST
){
152 if ("id".equalsIgnoreCase(propertyName
) || "uuid".equalsIgnoreCase(propertyName
)){
153 throw new MergeException("Identifier must always have merge mode MergeMode.FIRST");
158 public <T
extends IMergable
> Set
<ICdmBase
> invoke(T mergeFirst
, T mergeSecond
) throws MergeException
{
159 return this.invoke(mergeFirst
, mergeSecond
, null);
163 * @see eu.etaxonomy.cdm.strategy.merge.IMergeStragegy#invoke(eu.etaxonomy.cdm.strategy.merge.IMergable, eu.etaxonomy.cdm.strategy.merge.IMergable)
165 public <T
extends IMergable
> Set
<ICdmBase
> invoke(T mergeFirst
, T mergeSecond
, Set
<ICdmBase
> clonedObjects
) throws MergeException
{
166 Set
<ICdmBase
> deleteSet
= new HashSet
<ICdmBase
>();
167 if (clonedObjects
== null){
168 clonedObjects
= new HashSet
<ICdmBase
>();
170 deleteSet
.add(mergeSecond
);
172 for (Field field
: mergeFields
.values()){
173 Class
<?
> fieldType
= field
.getType();
174 if (isIdentifier(field
)){
175 //do nothing (id and uuid stay with first object)
176 }else if (isPrimitive(fieldType
)){
177 mergePrimitiveField(mergeFirst
, mergeSecond
, field
);
178 }else if (fieldType
== String
.class ){
179 mergeStringField(mergeFirst
, mergeSecond
, field
);
180 }else if (isCollection(fieldType
)){
181 mergeCollectionField(mergeFirst
, mergeSecond
, field
, deleteSet
, clonedObjects
);
182 }else if(isUserType(fieldType
)){
183 mergeUserTypeField(mergeFirst
, mergeSecond
, field
);
184 }else if(isSingleCdmBaseObject(fieldType
)){
185 mergeSingleCdmBaseField(mergeFirst
, mergeSecond
, field
, deleteSet
);
186 }else if(fieldType
.isInterface()){
187 mergeInterfaceField(mergeFirst
, mergeSecond
, field
, deleteSet
);
188 }else if(fieldType
.isEnum()){
189 mergeEnumField(mergeFirst
, mergeSecond
, field
, deleteSet
);
191 throw new RuntimeException("Unknown Object type for merging: " + fieldType
);
195 } catch (Exception e
) {
196 throw new MergeException("Merge Exception in invoke", e
);
205 private <T
extends IMergable
> void mergeInterfaceField(T mergeFirst
, T mergeSecond
, Field field
, Set
<ICdmBase
> deleteSet
) throws Exception
{
206 String propertyName
= field
.getName();
207 MergeMode mergeMode
= this.getMergeMode(propertyName
);
208 if (mergeMode
!= MergeMode
.FIRST
){
209 mergeCdmBaseValue(mergeFirst
, mergeSecond
, field
, deleteSet
);
211 logger
.debug(propertyName
+ ": " + mergeMode
+ ", " + field
.getType().getName());
219 private <T
extends IMergable
> void mergeEnumField(T mergeFirst
, T mergeSecond
, Field field
, Set
<ICdmBase
> deleteSet
) throws Exception
{
220 String propertyName
= field
.getName();
221 MergeMode mergeMode
= this.getMergeMode(propertyName
);
222 if (mergeMode
!= MergeMode
.FIRST
){
223 mergeCdmBaseValue(mergeFirst
, mergeSecond
, field
, deleteSet
);
225 logger
.debug(propertyName
+ ": " + mergeMode
+ ", " + field
.getType().getName());
233 private <T
extends IMergable
> void mergeSingleCdmBaseField(T mergeFirst
, T mergeSecond
, Field field
, Set
<ICdmBase
> deleteSet
) throws Exception
{
234 String propertyName
= field
.getName();
235 MergeMode mergeMode
= this.getMergeMode(propertyName
);
236 if (mergeMode
!= MergeMode
.FIRST
){
237 mergeCdmBaseValue(mergeFirst
, mergeSecond
, field
, deleteSet
);
239 logger
.debug(propertyName
+ ": " + mergeMode
+ ", " + field
.getType().getName());
243 private <T
extends IMergable
> void mergeCdmBaseValue(T mergeFirst
, T mergeSecond
, Field field
, Set
<ICdmBase
> deleteSet
) throws Exception
{
245 Object value
= getMergeValue(mergeFirst
, mergeSecond
, field
);
246 if (value
instanceof ICdmBase
|| value
== null){
247 field
.set(mergeFirst
, (ICdmBase
)value
);
249 throw new MergeException("Merged value must be of type CdmBase but is not: " + value
.getClass());
252 throw new MergeException("Not supported mode");
260 private <T
extends IMergable
> void mergeUserTypeField(T mergeFirst
, T mergeSecond
, Field field
) throws Exception
{
261 String propertyName
= field
.getName();
262 Class
<?
> fieldType
= field
.getType();
263 MergeMode mergeMode
= this.getMergeMode(propertyName
);
264 if (mergeMode
== MergeMode
.MERGE
){
265 Method mergeMethod
= getMergeMethod(fieldType
);
266 Object firstObject
= field
.get(mergeFirst
);
267 if (firstObject
== null){
268 firstObject
= fieldType
.newInstance();
270 Object secondObject
= field
.get(mergeSecond
);
271 mergeMethod
.invoke(firstObject
, secondObject
);
272 }else if (mergeMode
!= MergeMode
.FIRST
){
273 Object value
= getMergeValue(mergeFirst
, mergeSecond
, field
);
274 field
.set(mergeFirst
, value
);
276 logger
.debug(propertyName
+ ": " + mergeMode
+ ", " + fieldType
.getName());
281 * @throws NoSuchMethodException
282 * @throws SecurityException
284 private Method
getMergeMethod(Class
<?
> fieldType
) throws SecurityException
, NoSuchMethodException
{
285 Method mergeMethod
= fieldType
.getDeclaredMethod("merge", fieldType
);
295 private <T
extends IMergable
> void mergeCollectionField(T mergeFirst
, T mergeSecond
, Field field
, Set
<ICdmBase
> deleteSet
, Set
<ICdmBase
> clonedObjects
) throws Exception
{
296 String propertyName
= field
.getName();
297 Class
<?
> fieldType
= field
.getType();
298 MergeMode mergeMode
= this.getMergeMode(propertyName
);
299 if (mergeMode
!= MergeMode
.FIRST
){
300 mergeCollectionFieldNoFirst(mergeFirst
, mergeSecond
, field
, mergeMode
, deleteSet
, clonedObjects
);
302 logger
.debug(propertyName
+ ": " + mergeMode
+ ", " + fieldType
.getName());
306 private <T
extends IMergable
> void mergeCollectionFieldNoFirst(T mergeFirst
, T mergeSecond
, Field field
, MergeMode mergeMode
, Set
<ICdmBase
> deleteSet
, Set
<ICdmBase
> clonedObjects
) throws Exception
{
307 Class
<?
> fieldType
= field
.getType();
308 if (mergeMode
== MergeMode
.ADD
|| mergeMode
== MergeMode
.ADD_CLONE
){
310 Method addMethod
= getAddMethod(field
);
311 Method removeMethod
= getAddMethod(field
, true);
313 if (Set
.class.isAssignableFrom(fieldType
) || List
.class.isAssignableFrom(fieldType
)){
314 Collection
<ICdmBase
> secondCollection
= (Collection
<ICdmBase
>)field
.get(mergeSecond
);
315 List
<ICdmBase
> removeList
= new ArrayList
<ICdmBase
>();
316 if(secondCollection
!= null) {
317 for (ICdmBase obj
: secondCollection
){
319 if (mergeMode
== MergeMode
.ADD
){
321 }else if(mergeMode
== MergeMode
.ADD_CLONE
){
322 Method cloneMethod
= obj
.getClass().getDeclaredMethod("clone");
323 objectToAdd
= cloneMethod
.invoke(obj
);
324 clonedObjects
.add(obj
);
326 throw new MergeException("Unknown collection merge mode: " + mergeMode
);
328 addMethod
.invoke(mergeFirst
, objectToAdd
);
332 for (ICdmBase removeObj
: removeList
){
333 //removeMethod.invoke(mergeSecond, removeObj);
334 if ((removeObj
instanceof CdmBase
)&& mergeMode
== MergeMode
.ADD_CLONE
) {
335 deleteSet
.add(removeObj
);
339 throw new MergeException("Merge for collections other than sets and lists not yet implemented");
341 }else if (mergeMode
== MergeMode
.RELATION
){
342 if (Set
.class.isAssignableFrom(fieldType
) || List
.class.isAssignableFrom(fieldType
)){
343 Collection
<RelationshipBase
<?
,?
,?
>> secondCollection
= (Collection
<RelationshipBase
<?
,?
,?
>>)field
.get(mergeSecond
);
344 List
<ICdmBase
> removeList
= new ArrayList
<ICdmBase
>();
345 for (RelationshipBase
<?
,?
,?
> relation
: secondCollection
){
346 Method relatedFromMethod
= RelationshipBase
.class.getDeclaredMethod("getRelatedFrom");
347 relatedFromMethod
.setAccessible(true);
348 Object relatedFrom
= relatedFromMethod
.invoke(relation
);
350 Method relatedToMethod
= RelationshipBase
.class.getDeclaredMethod("getRelatedTo");
351 relatedToMethod
.setAccessible(true);
352 Object relatedTo
= relatedToMethod
.invoke(relation
);
354 if (relatedFrom
.equals(mergeSecond
)){
355 Method setRelatedMethod
= RelationshipBase
.class.getDeclaredMethod("setRelatedFrom", IRelated
.class);
356 setRelatedMethod
.setAccessible(true);
357 setRelatedMethod
.invoke(relation
, mergeFirst
);
359 if (relatedTo
.equals(mergeSecond
)){
360 Method setRelatedMethod
= RelationshipBase
.class.getDeclaredMethod("setRelatedTo", IRelated
.class);
361 setRelatedMethod
.setAccessible(true);
362 setRelatedMethod
.invoke(relation
, mergeFirst
);
364 ((IRelated
)mergeFirst
).addRelationship(relation
);
365 removeList
.add(relation
);
367 for (ICdmBase removeObj
: removeList
){
368 //removeMethod.invoke(mergeSecond, removeObj);
369 if (removeObj
instanceof CdmBase
){
370 deleteSet
.add(removeObj
);
374 throw new MergeException("Merge for collections other than sets and lists not yet implemented");
377 throw new MergeException("Other merge modes for collections not yet implemented");
382 private Method
getAddMethod(Field field
) throws MergeException
{
383 return getAddMethod(field
, false);
386 public static Method
getAddMethod(Field field
, boolean remove
) throws MergeException
{
388 Class parameterClass
= getCollectionType(field
);
389 String fieldName
= field
.getName();
390 String firstCapital
= fieldName
.substring(0, 1).toUpperCase();
391 String rest
= fieldName
.substring(1);
392 String prefix
= remove?
"remove": "add";
393 String methodName
= prefix
+ firstCapital
+ rest
;
394 boolean endsWithS
= parameterClass
.getSimpleName().endsWith("s");
395 if (! endsWithS
&& ! fieldName
.equals("media")){
396 methodName
= methodName
.substring(0, methodName
.length() -1); //remove 's' at end
398 Class
<?
> methodClass
= field
.getDeclaringClass();
400 result
= methodClass
.getMethod(methodName
, parameterClass
);
401 }catch (NoSuchMethodException e1
) {
403 result
= methodClass
.getDeclaredMethod(methodName
, parameterClass
);
404 result
.setAccessible(true);
405 } catch (NoSuchMethodException e
) {
406 logger
.warn(methodName
);
407 throw new IllegalArgumentException("Default adding method for collection field ("+field
.getName()+") does not exist");
409 } catch (SecurityException e
) {
419 private <T
extends IMergable
> void mergePrimitiveField(T mergeFirst
, T mergeSecond
, Field field
) throws Exception
{
420 String propertyName
= field
.getName();
421 Class
<?
> fieldType
= field
.getType();
422 MergeMode mergeMode
= this.getMergeMode(propertyName
);
423 if (mergeMode
!= MergeMode
.FIRST
){
424 Object value
= getMergeValue(mergeFirst
, mergeSecond
, field
);
425 field
.set(mergeFirst
, value
);
427 logger
.debug(propertyName
+ ": " + mergeMode
+ ", " + fieldType
.getName());
435 private <T
extends IMergable
> void mergeStringField(T mergeFirst
, T mergeSecond
, Field field
) throws Exception
{
436 String propertyName
= field
.getName();
437 Class
<?
> fieldType
= field
.getType();
438 MergeMode mergeMode
= this.getMergeMode(propertyName
);
439 if (mergeMode
!= MergeMode
.FIRST
){
440 Object value
= getMergeValue(mergeFirst
, mergeSecond
, field
);
441 field
.set(mergeFirst
, value
);
443 logger
.debug(propertyName
+ ": " + mergeMode
+ ", " + fieldType
.getName());
451 private boolean isIdentifier(Field field
) {
452 Class
<?
> fieldType
= field
.getType();
453 if ("id".equals(field
.getName()) && fieldType
== int.class ){
455 }else if ("uuid".equals(field
.getName()) && fieldType
== UUID
.class ){
470 protected <T
extends IMergable
> Object
getMergeValue(T mergeFirst
, T mergeSecond
,
471 Field field
) throws Exception
{
472 MergeMode mergeMode
= this.getMergeMode(field
.getName());
474 if (mergeMode
== MergeMode
.FIRST
){
475 return field
.get(mergeFirst
);
476 }else if (mergeMode
== MergeMode
.SECOND
){
477 return field
.get(mergeSecond
);
478 }else if (mergeMode
== MergeMode
.NULL
){
480 }else if (mergeMode
== MergeMode
.CONCAT
){
481 return ((String
)field
.get(mergeFirst
) + (String
)field
.get(mergeSecond
));
482 }else if (mergeMode
== MergeMode
.AND
){
483 return ((Boolean
)field
.get(mergeFirst
) && (Boolean
)field
.get(mergeSecond
));
484 }else if (mergeMode
== MergeMode
.OR
){
485 return ((Boolean
)field
.get(mergeFirst
) || (Boolean
)field
.get(mergeSecond
));
487 throw new IllegalStateException("Unknown MergeMode");
489 } catch (IllegalArgumentException e
) {
490 throw new Exception(e
);
495 private static Class
getCollectionType(Field field
) throws MergeException
{
496 Type genericType
= (ParameterizedTypeImpl
)field
.getGenericType();
497 if (genericType
instanceof ParameterizedTypeImpl
){
498 ParameterizedTypeImpl paraType
= (ParameterizedTypeImpl
)genericType
;
499 Class
<?
> rawType
= paraType
.getRawType();
500 Type
[] arguments
= paraType
.getActualTypeArguments();
502 if (arguments
.length
== 1){
503 Class collectionClass
;
504 if (arguments
[0] instanceof Class
){
505 collectionClass
= (Class
)arguments
[0];
506 }else if(arguments
[0] instanceof TypeVariableImpl
){
507 TypeVariableImpl typeVariable
= (TypeVariableImpl
)arguments
[0];
508 GenericDeclaration genericDeclaration
= typeVariable
.getGenericDeclaration();
509 collectionClass
= (Class
)genericDeclaration
;
511 throw new MergeException("Collection with other types than TypeVariableImpl are not yet supported");
513 return collectionClass
;
515 throw new MergeException("Collection with multiple types not supported");
518 throw new MergeException("Collection has no generic type of type ParameterizedTypeImpl. Unsupport case.");