package org.apache.ode.bpel.obj.migrate; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Stack; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * compare two object for equality. default strategy: * for collections and maps, compare their contents * for POJO, * if any custom equality comparator can handle it, then use it; * if it has an equals() method defined, use it. * or compare their non-transient accessible fields by reflection. * @author fangzhen * */ public class DeepEqualityHelper{ private static final Logger __log = LoggerFactory.getLogger(ObjectTraverser.class); public boolean logFalseThrough = false; private Stack<String> st = new Stack<String>(); private List<EqualityComparator> comparators = new LinkedList<EqualityComparator>(); private Stack<Long> ongoing = new Stack<Long>(); private Map<Long, Boolean> cache = new HashMap<Long, Boolean>(); private final List<String> IGNORE_FIELDS = new ArrayList<String>(); { // due to ODE-1023, we set all document element to null in compiled process models. Thus we need to exclude them // from the comparison because the were still set in old CBP files. IGNORE_FIELDS.add("protected org.w3c.dom.Element com.ibm.wsdl.AbstractWSDLElement.docEl"); } public boolean deepEquals(Object obj1, Object obj2){ // __log.debug("comparing Objects: " + obj1 + " and " + obj2); //will cause too much log Boolean c = cachedRes(obj1, obj2); if (c != null) { return c; } Long h12 = hash(obj1, obj2); if (ongoing.contains(h12)) { return true; } ongoing.push(h12); boolean n; if (isMap(obj1)){ n = visitMap(obj1, obj2); }else if (isSet(obj1)){ n = visitSet(obj1, obj2); }else if (isCollection(obj1)){ n = visitCollection(obj1, obj2); }else if (isArray(obj1)){ n = visitArray(obj1, obj2); }else{ n = visitPojo(obj1, obj2); } cacheRes(obj1, obj2, n); ongoing.pop(); return n; } private void cacheRes(Object obj1, Object obj2, Boolean n) { cache.put(hash(obj1, obj2), n); } private Boolean cachedRes(Object obj1, Object obj2) { return cache.get(hash(obj1, obj2)); } public Boolean visitMap(Object obj, Object other) { if (obj == other) return true; if (other == null) { if (!logFalseThrough){ __log.debug("Unequal in Map: Object2 is null. " + st); } return false; } Map m1 = (Map)obj; Map m2 = null; try{ m2 = (Map)other; }catch (ClassCastException e){ if (!logFalseThrough){ __log.debug("Unequal in Map: Object2 is not a map, it's a" + other.getClass() + "\n" + st); } return false; } if (m1.size() != m2.size()) { if (!logFalseThrough){ __log.debug("Unequal in Map: size mismatch. " + st + "\n size: " + m1.size() + " and " + m2.size()); } return false; } Set ks1 = m1.keySet(); Set ks2 = m2.keySet(); for (Object k1 : ks1){ st.push(k1.toString()); Object k2 = contains(ks2, k1); if (k2 == null){ if (!logFalseThrough){ __log.debug("Unequal in Map: cant find key. " + st + "\n missing key: " + k1); } st.pop(); return false; } Object o1 = m1.get(k1); Object o2 = m2.get(k2); if (o1 == null){ if (!(o2 == null)){ if (!logFalseThrough){ __log.debug("Unequal in Map: mismatch, one is null" + st + "\n When dealing with " + o1 + " and " + o2); } st.pop(); return false; } } st.pop(); st.push(k1.toString() + ":" + o1.getClass().getSimpleName()); Boolean e = deepEquals(o1, o2); if (!e) { st.pop(); return false; } st.pop(); } return true; } @SuppressWarnings("rawtypes") public Boolean visitSet(Object obj, Object other){ if (obj == other) return true; if (other == null) { if (!logFalseThrough){ __log.debug("Unequal in Set, Object2 is null. " + st); } return false; } Collection c1 = (Collection)obj; Collection c2 = null; try { c2 = (Collection)other; }catch(ClassCastException e){ if (!logFalseThrough){ __log.debug("Unequal in Set: Object2 is not a Set, it's a" + other.getClass() + "\n" + st); } return false; } if (c1.size() != c2.size()) { if (!logFalseThrough){ __log.debug("Unequal in Set: size mismatch. " + st + "\n. sizes are " + c1.size() + " and " + c2.size()); } return false; } Iterator i1 = c1.iterator(); while (i1.hasNext()){ Object o1 = i1.next(); st.push(":" + o1.getClass().getSimpleName()); if (contains(c2, o1) == null) { if (!logFalseThrough){ __log.debug("Unequal in Set: Object mismatch. " + st + "\n" + "can't find " + o1); } st.pop(); return false; } st.pop(); } return true; } private Object contains(Collection c, Object t1) { Iterator itor = c.iterator(); Object t2; logFalseThrough = true; while (itor.hasNext()){ t2 = itor.next(); if (deepEquals(t1, t2)) { logFalseThrough = false; return t2; } } logFalseThrough = false; return null; } @SuppressWarnings("rawtypes") public Boolean visitCollection(Object obj, Object other) { if (obj == other) return true; if (other == null) { if (!logFalseThrough){ __log.debug("Unequal in Collection, Object2 is null. " + st); } return false; } Collection c = (Collection)obj; Collection c2 = null; try { c2 = (Collection)other; }catch(ClassCastException e){ if (!logFalseThrough){ __log.debug("Unequal in Collection: Object2 is not a Collection, it's a" + other.getClass() + "\n" + st); } return false; } if (c.size() != c2.size()) { if (!logFalseThrough){ __log.debug("Unequal in Collection: size mismatch. " + st + "\n. sizes are " + c.size() + " and " + c2.size()); } return false; } Iterator i1 = c.iterator(); Iterator i2 = c2.iterator(); while (i1.hasNext()){ Object o1 = i1.next(); Object o2 = i2.next(); st.push(":" + o1.getClass().getSimpleName()); Boolean e = deepEquals(o1, o2); if (!e) { st.pop(); return false; } st.pop(); } return true; } public Boolean visitArray(Object obj, Object other) { throw new UnsupportedOperationException(); } public Boolean visitPojo(Object obj, Object other) { EqualityComparator customComp = getCustomComparator(obj); if (customComp != null){ return customComp.objectsEqual(obj, other); } if (obj == other) return true; if (other == null) { if (!logFalseThrough){ __log.debug("Unequal in POJO: Object2 is null." + st); } return false; } if (obj.getClass() != other.getClass()) { if(!logFalseThrough){ __log.debug("Unequal in POJO: type mistach. " + st + "\nmismatched types are: " + obj.getClass().getSimpleName() + " and " + other.getClass().getSimpleName()); } return false; } try{ obj.getClass().getDeclaredMethod("equals", Object.class); boolean e = obj.equals(other); if (!e){ if (!logFalseThrough){ __log.debug("Unequal in POJO: not equal by equals() method" + st); } } return e; }catch (NoSuchMethodException e){ return equalityByReflection(obj, other); } } private EqualityComparator getCustomComparator(Object obj) { for (EqualityComparator c : comparators){ if (c.canHanle(obj)){ return c; } } return null; } public Boolean equalityByReflection(Object obj, Object other) { List<Field> fields = MigUtils.getAllFields(obj.getClass()); List<Field> fields2 = MigUtils.getAllFields(other.getClass()); if (!fields.equals(fields2)){ if (!logFalseThrough){ __log.debug("Unequal: getFields() of two Object do not match " + st); } return false; } for (Field f : fields){ f.setAccessible(true); if (((Modifier.TRANSIENT | Modifier.STATIC) & f.getModifiers()) != 0){ continue; //skip transient fields } try { Object v2 = f.get(other); Object v1 = f.get(obj); if (v1 == null && v2 == null){ continue; } if (IGNORE_FIELDS.contains(f.toString())) { __log.debug("Ignoring field " + f.getName() + " with values " + v1 + " and " + v2); continue; } st.push(f.getName()+ ":" + f.getType().getSimpleName()); if (v1 == null || v2 == null){ if (!logFalseThrough){ __log.debug("Unequal: one field is null" + st + ".\n When dealing with " + v1 + " and " + v2); } st.pop(); return false; } Boolean res = deepEquals(v1, v2); if (!res){ st.pop(); return false; } st.pop(); } catch (Exception e) { //should not get here e.printStackTrace(); } } return true; } /** * determine if obj is collections that order doesn't matter. * @param obj * @return */ protected boolean isSet(Object obj) { return obj instanceof Set; } protected boolean isArray(Object old) { return old.getClass().isArray(); } /** * determine if obj is collections that order does matter. * @param obj * @return */ protected boolean isCollection(Object old) { return (old instanceof Collection) && !isSet(old); } protected boolean isMap(Object old) { return old instanceof Map; } private Long hash(Object obj1, Object obj2) { int h1 = System.identityHashCode(obj1); int h2 = System.identityHashCode(obj2); return ((long)h1) << 32 | h2; } public void addCustomComparator(EqualityComparator oe){ comparators.add(0, oe); oe.setDeepEquality(this); } public Stack<String> getSt() { return st; } }