| 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; |
| } |
| } |