/*
* 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.deltaspike.testcontrol.impl.mock;
import org.apache.deltaspike.core.util.ReflectionUtils;
import javax.enterprise.util.Nonbinding;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Comparator;
//class from OWB
public class BeanCacheKey
{
private static final Comparator<Annotation> ANNOTATION_COMPARATOR = new AnnotationComparator();
private final Type type;
private final Annotation qualifier;
private final Annotation qualifiers[];
private final int hashCode;
public BeanCacheKey(Type type, Annotation... qualifiers)
{
this.type = type;
final int length = qualifiers != null ? qualifiers.length : 0;
if (length == 0)
{
qualifier = null;
this.qualifiers = null;
}
else if (length == 1)
{
qualifier = qualifiers[0];
this.qualifiers = null;
}
else
{
qualifier = null;
// to save array creations, we only create an array, if we have more than one annotation
this.qualifiers = new Annotation[length];
System.arraycopy(qualifiers, 0, this.qualifiers, 0, length);
Arrays.sort(this.qualifiers, ANNOTATION_COMPARATOR);
}
// this class is directly used in ConcurrentHashMap.get() so simply init the hasCode here
hashCode = computeHashCode();
}
@Override
public boolean equals(Object o)
{
if (this == o)
{
return true;
}
if (o == null || getClass() != o.getClass())
{
return false;
}
BeanCacheKey cacheKey = (BeanCacheKey) o;
if (!type.equals(cacheKey.type))
{
return false;
}
if (qualifier != null ? !qualifierEquals(qualifier, cacheKey.qualifier) : cacheKey.qualifier != null)
{
return false;
}
if (!qualifierArrayEquals(qualifiers, cacheKey.qualifiers))
{
return false;
}
return true;
}
private boolean qualifierArrayEquals(Annotation[] qualifiers1, Annotation[] qualifiers2)
{
if (qualifiers1 == qualifiers2)
{
return true;
}
else if (qualifiers1 == null || qualifiers2 == null)
{
return false;
}
if (qualifiers1.length != qualifiers2.length)
{
return false;
}
for (int i = 0; i < qualifiers1.length; i++)
{
Annotation a1 = qualifiers1[i];
Annotation a2 = qualifiers2[i];
if (a1 == null ? a2 != null : !qualifierEquals(a1, a2))
{
return false;
}
}
return true;
}
@Override
public int hashCode()
{
return hashCode;
}
/**
* Compute the HashCode. This should be called only in the constructor.
*/
private int computeHashCode()
{
int computedHashCode = 31 * ReflectionUtils.calculateHashCodeOfType(type);
if (qualifier != null)
{
computedHashCode = 31 * computedHashCode + getQualifierHashCode(qualifier);
}
if (qualifiers != null)
{
for (int i = 0; i < qualifiers.length; i++)
{
computedHashCode = 31 * computedHashCode + getQualifierHashCode(qualifiers[i]);
}
}
return computedHashCode;
}
/**
* Calculate the hashCode() of a qualifier, which ignores {@link javax.enterprise.util.Nonbinding} members.
*/
private int getQualifierHashCode(Annotation a)
{
return ReflectionUtils.calculateHashCodeOfAnnotation(a, true);
}
/**
* Implements the equals() method for qualifiers, which ignores {@link javax.enterprise.util.Nonbinding} members.
*/
private boolean qualifierEquals(Annotation qualifier1, Annotation qualifier2)
{
return ANNOTATION_COMPARATOR.compare(qualifier1, qualifier2) == 0;
}
/**
* for debugging ...
*/
@Override
public String toString()
{
return "BeanCacheKey{" + "type=" + type + ", qualifiers="
+ (qualifiers == null ? qualifier : Arrays.asList(qualifiers)) + ", hashCode=" + hashCode + '}';
}
/**
* to keep the annotations ordered.
*/
private static class AnnotationComparator implements Comparator<Annotation>
{
// Notice: Sorting is a bit costly, but the use of this code is very rar.
@Override
public int compare(Annotation annotation1, Annotation annotation2)
{
final Class<? extends Annotation> type1 = annotation1.annotationType();
final Class<? extends Annotation> type2 = annotation2.annotationType();
final int temp = type1.getName().compareTo(type2.getName());
if (temp != 0)
{
return temp;
}
final Method[] member1 = type1.getDeclaredMethods();
final Method[] member2 = type2.getDeclaredMethods();
// TBD: the order of the list of members seems to be deterministic
int i = 0;
int j = 0;
final int length1 = member1.length;
final int length2 = member2.length;
// find next nonbinding
for (;; i++, j++)
{
while (i < length1 && member1[i].isAnnotationPresent(Nonbinding.class))
{
i++;
}
while (j < length2 && member2[j].isAnnotationPresent(Nonbinding.class))
{
j++;
}
if (i >= length1 && j >= length2)
{ // both ended
return 0;
}
else if (i >= length1)
{ // #1 ended
return 1;
}
else if (j >= length2)
{ // #2 ended
return -1;
}
else
{ // not ended
int c = member1[i].getName().compareTo(member2[j].getName());
if (c != 0)
{
return c;
}
final Object value1 = ReflectionUtils.invokeMethod(annotation1, member1[i], Object.class, true);
final Object value2 = ReflectionUtils.invokeMethod(annotation2, member2[j], Object.class, true);
assert value1.getClass().equals(value2.getClass());
if (value1 instanceof Comparable)
{
c = ((Comparable)value1).compareTo(value2);
if (c != 0)
{
return c;
}
}
else if (value1.getClass().isArray())
{
c = value1.getClass().getComponentType().getName()
.compareTo(value2.getClass().getComponentType().getName());
if (c != 0)
{
return c;
}
final int length = Array.getLength(value1);
c = length - Array.getLength(value2);
if (c != 0)
{
return c;
}
for (int k = 0; k < length; k++)
{
c = ((Comparable)Array.get(value1, k)).compareTo(Array.get(value2, k));
if (c != 0)
{
return c;
}
}
}
else if (value1 instanceof Class)
{
c = ((Class)value1).getName().compareTo(((Class) value2).getName());
if (c != 0)
{
return c;
}
}
else
{
// valid types for members are only Comparable, Arrays, or Class
assert false;
}
}
}
}
}
}