/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.metamodel.util;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A base object type with conveniently implemented base methods like hashCode()
* and equals(). Subclasses should implement the {@link #decorateIdentity(List)}
* method to have {@link #equals(Object)} and {@link #hashCode()} automatically
* implemented.
*/
public abstract class BaseObject {
private static final Logger logger = LoggerFactory
.getLogger(BaseObject.class);
@Override
public String toString() {
// overridden version of toString() method that uses identity hash code
// (to prevent hashCode() recursion due to logging!)
return getClass().getName() + "@"
+ Integer.toHexString(System.identityHashCode(this));
}
/**
* {@inheritDoc}
*/
@Override
public final int hashCode() {
logger.debug("{}.hashCode()", this);
int hashCode = -1;
List<Object> list = new ArrayList<Object>();
decorateIdentity(list);
if (list.isEmpty()) {
list.add(toString());
}
hashCode -= list.size();
for (Object obj : list) {
hashCode += hashCode(obj);
}
return hashCode;
}
private static final int hashCode(Object obj) {
if (obj == null) {
logger.debug("obj is null, returning constant");
return -17;
}
if (obj.getClass().isArray()) {
logger.debug("obj is an array, returning a sum");
int length = Array.getLength(obj);
int hashCode = 4324;
for (int i = 0; i < length; i++) {
Object o = Array.get(obj, i);
hashCode += hashCode(o);
}
return hashCode;
}
logger.debug("obj is a regular object, returning hashCode");
return obj.hashCode();
}
/**
* Override this method if the equals method should support different
* subtypes. For example, if different subtypes of Number should be
* supported, implement this method with:
*
* <code>
* obj instanceof Number
* </code>
*
* and make sure that the decorateIdentity(...) method will always return a
* comparable list of identity-objects.
*
* @param obj
* @return true if the provided object's class is accepted for equals
* comparison
*/
protected boolean classEquals(BaseObject obj) {
return getClass() == obj.getClass();
}
/**
* {@inheritDoc}
*/
@Override
public final boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (obj == this) {
return true;
}
if (obj instanceof BaseObject) {
BaseObject that = (BaseObject) obj;
if (classEquals(that)) {
List<Object> list1 = new ArrayList<Object>();
List<Object> list2 = new ArrayList<Object>();
decorateIdentity(list1);
that.decorateIdentity(list2);
if (list1.size() != list2.size()) {
throw new IllegalStateException(
"Two instances of the same class ("
+ getClass().getName()
+ ") returned different size decorated identity lists");
}
if (list1.isEmpty()) {
assert list2.isEmpty();
list1.add(toString());
list2.add(that.toString());
}
EqualsBuilder eb = new EqualsBuilder();
Iterator<Object> it1 = list1.iterator();
Iterator<Object> it2 = list2.iterator();
while (it1.hasNext()) {
assert it2.hasNext();
Object next1 = it1.next();
Object next2 = it2.next();
eb.append(next1, next2);
}
assert !it2.hasNext();
return eb.isEquals();
}
}
return false;
}
/**
* Subclasses should implement this method and add all fields to the list
* that are to be included in equals(...) and hashCode() evaluation
*
* @param identifiers
*/
protected abstract void decorateIdentity(List<Object> identifiers);
}