blob: 4b9c271de3153f941fb7a4a6e0753ba1e2471bf5 [file] [log] [blame]
/*
* Copyright (c) 2011, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// James Sutherland - initial API and implementation
package org.eclipse.persistence.internal.identitymaps;
import java.io.*;
import java.util.Arrays;
import org.eclipse.persistence.internal.helper.*;
/**
* Defines a wrapper for a primary key (Id) to use as a key in the cache.
*
* @since EclipseLink 2.1
* @author James Sutherland
*/
public class CacheId implements Serializable, Comparable<CacheId> {
public static final CacheId EMPTY = new CacheId(new Object[0]);
private static final CacheIdComparator COMPARATOR = new CacheIdComparator();
/** The primary key values. */
protected Object[] primaryKey;
/** Cached hashcode. */
protected int hash;
/** Indicates whether at least one element of primaryKey is array. */
protected boolean hasArray;
public CacheId() {
}
public CacheId(Object[] primaryKey) {
this.primaryKey = primaryKey;
this.hash = computeHash(primaryKey);
}
public Object[] getPrimaryKey() {
return primaryKey;
}
public void setPrimaryKey(Object[] primaryKey) {
this.primaryKey = primaryKey;
this.hash = computeHash(primaryKey);
}
/**
* Append the value to the end of the primary key values.
*/
public void add(Object value) {
Object[] array = new Object[this.primaryKey.length + 1];
System.arraycopy(this.primaryKey, 0, array, 0, this.primaryKey.length);
array[this.primaryKey.length] = value;
setPrimaryKey(array);
}
/**
* Set the value in the primary key values.
*/
public void set(int index, Object value) {
this.primaryKey[index] = value;
setPrimaryKey(this.primaryKey);
}
/**
* Pre-compute the hash to optimize hash calls.
*/
protected int computeHash(Object[] primaryKey) {
int result = 1;
for (Object value : primaryKey) {
if (value != null) {
//bug5709489, gf bug 1193: fix to handle array hashcodes properly
if (value.getClass().isArray()) {
result = computeArrayHashCode(result, value);
this.hasArray = true;
} else {
result = 31 * result + value.hashCode();
}
} else {
result = 31 * result;
}
}
return result;
}
/**
* Compute the hashcode for supported array types.
*/
private int computeArrayHashCode(int result, Object obj) {
if (obj.getClass() == ClassConstants.APBYTE) {
for (byte element : (byte[])obj) {
result = 31 * result + element;
}
} else if (obj.getClass() == ClassConstants.APCHAR) {
for (char element : (char[])obj) {
result = 31 * result + element;
}
} else {
for (Object element : (Object[])obj) {
result = 31 * result + (element == null ? 0 : element.hashCode());
}
}
return result;
}
/**
* Return the precomputed hashcode.
*/
@Override
public int hashCode() {
return this.hash;
}
/**
* Determine if the receiver is equal to anObject.
* If anObject is a CacheKey, do further comparison, otherwise, return false.
* @see CacheKey#equals(CacheKey)
*/
@Override
public boolean equals(Object object) {
try {
return equals((CacheId)object);
} catch (ClassCastException incorrectType) {
return false;
}
}
/**
* Determine if the receiver is equal to key.
* Use an index compare, because it is much faster than enumerations.
*/
public boolean equals(CacheId id) {
if (this == id) {
return true;
}
if (this.hash != id.hash) {
return false;
}
if (this.hasArray != id.hasArray) {
return false;
}
// PERF: Using direct variable access.
int size = this.primaryKey.length;
Object[] otherKey = id.primaryKey;
if (size == otherKey.length) {
for (int index = 0; index < size; index++) {
Object value = this.primaryKey[index];
Object otherValue = otherKey[index];
if (value == null) {
if (otherValue != null) {
return false;
} else {
continue;
}
} else if (otherValue == null) {
return false;
}
if (this.hasArray) {
Class<?> valueClass = value.getClass();
if (valueClass.isArray()) {
Class<?> otherClass = otherValue.getClass();
//gf bug 1193: fix array comparison logic to exit if they don't match, and continue the loop if they do
if (((valueClass == ClassConstants.APBYTE) && (otherClass == ClassConstants.APBYTE)) ) {
if (!Helper.compareByteArrays((byte[])value, (byte[])otherValue)){
return false;
}
} else if (((valueClass == ClassConstants.APCHAR) && (otherClass == ClassConstants.APCHAR)) ) {
if (!Helper.compareCharArrays((char[])value, (char[])otherValue)){
return false;
}
} else {
if (!Helper.compareArrays((Object[])value, (Object[])otherValue)) {
return false;
}
}
} else {
if (!(value.equals(otherValue))) {
return false;
}
}
} else {
if (!(value.equals(otherValue))) {
return false;
}
}
}
return true;
}
return false;
}
/**
* Determine if the receiver is greater or less than the key.
*/
@Override
public int compareTo(CacheId otherId) {
return COMPARATOR.compare(this, otherId);
}
public boolean hasArray() {
return this.hasArray;
}
@Override
public String toString() {
return "[" + Arrays.asList(this.primaryKey) + ": " + this.hash + "]";
}
}