package org.springmodules.prevayler.id; import java.lang.reflect.Field; import java.util.Collection; import java.util.IdentityHashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import org.springmodules.prevayler.id.support.PrevaylerIdMergeException; import org.springmodules.prevayler.support.PrevaylerConfigurationException; import org.springmodules.prevayler.system.PrevalenceInfo; /** * {@link IdMerger} default implementation. * * @author Sergio Bossa */ public class DefaultIdMerger implements IdMerger { private static final long serialVersionUID = 476105208506333743L; private static final ThreadLocal localIdentityMap = new ThreadLocal(); private PrevalenceInfo prevalenceInfo; public DefaultIdMerger(PrevalenceInfo info) { this.prevalenceInfo = info; } public DefaultIdMerger() { } public void setPrevalenceInfo(PrevalenceInfo info) { this.prevalenceInfo = info; } public void merge(Object source, Object destination) { Class sourceClass = source.getClass(); Class destinationClass = destination.getClass(); if (! sourceClass.equals(destinationClass)) { throw new IllegalArgumentException("Source and destination objects are not of the same class!"); } else if (source == destination) { return; } else { this.localIdentityMap.set(new IdentityHashMap()); this.doMerge(source, destination); } } private void doMerge(Object sourceValue, Object destinationValue) { if (sourceValue != null && destinationValue != null) { if (sourceValue.getClass().isArray()) { this.traverseArray((Object[]) sourceValue, (Object[]) destinationValue); } else if (Collection.class.isAssignableFrom(sourceValue.getClass())) { this.traverseCollection((Collection) sourceValue, (Collection) destinationValue); } else if (Map.class.isAssignableFrom(sourceValue.getClass())) { this.traverseMap((Map) sourceValue, (Map) destinationValue); } else if (this.prevalenceInfo.getPrevalentClass(sourceValue.getClass()) != null) { this.mergePrevalentObjectIdentifiers(sourceValue, destinationValue); } } } private void traverseArray(Object[] sourceArray, Object[] destinationArray) { for (int i = 0; i < sourceArray.length; i++) { this.doMerge(sourceArray[i], destinationArray[i]); } } private void traverseCollection(Collection sourceCollection, Collection destinationCollection) { Iterator sIt = sourceCollection.iterator(); Iterator dIt = destinationCollection.iterator(); while (sIt.hasNext()) { this.doMerge(sIt.next(), dIt.next()); } } private void traverseMap(Map sourceMap, Map destinationMap) { Iterator sIt = sourceMap.entrySet().iterator(); Iterator dIt = destinationMap.entrySet().iterator(); while (sIt.hasNext()) { Map.Entry sourceEntry = (Entry) sIt.next(); Map.Entry destinationEntry = (Entry) dIt.next(); this.doMerge(sourceEntry.getKey(), destinationEntry.getKey()); this.doMerge(sourceEntry.getValue(), destinationEntry.getValue()); } } private void mergePrevalentObjectIdentifiers(Object sourceEntity, Object destinationEntity) { try { Class sourcePrevalentClass = this.prevalenceInfo.getPrevalentClass(sourceEntity.getClass()); Class destinationPrevalentClass = this.prevalenceInfo.getPrevalentClass(destinationEntity.getClass()); // If entities have prevalent classes: if (sourcePrevalentClass != null && destinationPrevalentClass != null) { if (sourcePrevalentClass.equals(destinationPrevalentClass)) { // Copy the identifier from source to destination: Field sourceIdField = this.prevalenceInfo.getIdResolutionStrategy().resolveId(sourceEntity); Field destinationIdField = this.prevalenceInfo.getIdResolutionStrategy().resolveId(destinationEntity); destinationIdField.set(destinationEntity, sourceIdField.get(sourceEntity)); // Cascade the merge: this.cascadeMerge(sourceEntity, destinationEntity); } else { throw new IllegalStateException("Classes don't match!"); } } else { throw new PrevaylerConfigurationException("Object with no configured prevalent class!"); } } catch (IllegalAccessException actual) { throw new IllegalStateException("Cannot access id value!", actual); } } private void cascadeMerge(Object source, Object destination) { // We are traversing the source object graph, and we'll have to put in an identity map the source object. IdentityHashMap localIdentityMap = (IdentityHashMap) DefaultIdMerger.localIdentityMap.get(); if (! localIdentityMap.containsKey(source)) { localIdentityMap.put(source, new Integer(1)); this.doCascadeMerge(source, destination); } } private void doCascadeMerge(Object source, Object destination) { Class currentClass = source.getClass(); while (currentClass != null) { try { Field[] fields = currentClass.getDeclaredFields(); for (int counter = 0; counter < fields.length; counter++) { Field currentField = fields[counter]; currentField.setAccessible(true); Object sourceValue = currentField.get(source); Object destinationValue = currentField.get(destination); this.doMerge(sourceValue, destinationValue); } currentClass = currentClass.getSuperclass(); } catch(IllegalAccessException ex) { throw new PrevaylerIdMergeException(ex.getMessage(), ex); } } } }