/*
* Copyright (C) 2015 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jboss.errai.jpa.sync.client.shared;
import java.util.Collection;
import java.util.IdentityHashMap;
import java.util.Iterator;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.ManagedType;
import javax.persistence.metamodel.Metamodel;
import org.jboss.errai.common.client.api.Assert;
public class EntityComparator {
private final Metamodel metamodel;
private final JpaAttributeAccessor attributeAccessor;
public EntityComparator(Metamodel metamodel, JpaAttributeAccessor attributeAccessor) {
this.metamodel = Assert.notNull(metamodel);
this.attributeAccessor = Assert.notNull(attributeAccessor);
}
/**
* Compares two JPA Managed Type instances to see if they are the same.
*
* @param lhs
* @param rhs
* @return
*/
public <X> boolean isDifferent(X lhs, X rhs) {
return isDifferent(lhs, rhs, metamodel, new IdentityHashMap<Object, Object>());
}
/**
* Private recursive subroutine of {@link #isDifferent(Object, Object)}.
*
* @param lhs
* @param rhs
* @param encountered
* @return
*/
private <X> boolean isDifferent(X lhs, X rhs, Metamodel metamodel, IdentityHashMap<Object, Object> encountered) {
if (lhs == null && rhs == null) return false;
if (lhs == null || rhs == null) return true;
if (encountered.get(lhs) == rhs) {
// we're already in the middle of comparing lhs to rhs, so pretend they're equal for now.
// if they're not really equal, the truth will come out once the stack has unwound.
return false;
}
encountered.put(lhs, rhs);
// XXX probably need to pass in the actual entity class rather than this cast
// (because dynamic proxies will fool it)
@SuppressWarnings("unchecked")
ManagedType<X> jpaType = metamodel.managedType((Class<X>) lhs.getClass());
for (Attribute<? super X, ?> attr : jpaType.getAttributes()) {
Object lhsVal = attributeAccessor.get(attr, lhs);
Object rhsVal = attributeAccessor.get(attr, rhs);
if (lhsVal == null && rhsVal == null) continue;
if (lhsVal == null || rhsVal == null) return true;
assert (lhsVal != null);
assert (rhsVal != null);
switch (attr.getPersistentAttributeType()) {
case BASIC:
case ELEMENT_COLLECTION:
if (!lhsVal.equals(rhsVal)) return true;
break;
case EMBEDDED:
case MANY_TO_ONE:
case ONE_TO_ONE:
if (isDifferent(lhsVal, rhsVal, metamodel, encountered)) return true;
break;
case MANY_TO_MANY:
case ONE_TO_MANY:
Collection<?> lhsCollection = (Collection<?>) lhsVal;
Collection<?> rhsCollection = (Collection<?>) rhsVal;
if (lhsCollection.size() != rhsCollection.size()) return true;
Iterator<?> lhsIt = lhsCollection.iterator();
Iterator<?> rhsIt = rhsCollection.iterator();
while (lhsIt.hasNext()) {
// FIXME this will not work for unordered collections (eg. bags, sets). Needs tests!
if (isDifferent(lhsIt.next(), rhsIt.next(), metamodel, encountered)) return true;
}
break;
default:
throw new RuntimeException("Unknown JPA attribute type: " + attr.getPersistentAttributeType());
}
}
return false;
}
}