io.timeandspace.collect.Equivalence Maven / Gradle / Ivy
Show all versions of smoothie-map Show documentation
/*
* Copyright (C) The SmoothieMap Authors
*
* 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 io.timeandspace.collect;
import com.google.auto.value.AutoValue;
import io.timeandspace.collect.map.ObjObjMap;
import org.jetbrains.annotations.Nullable;
import java.util.Map;
/**
* A strategy for determining whether two instances are considered equivalent.
*
* This class is inspired and very similar to
*
* Guava's {@code Equivalence}, with one notable difference: this {@code Equivalence} forces
* the actual implementation to override {@link #equals(Object)} and {@link #hashCode()}. Notice
* these are {@code Equivalence}'s own equals and hashCode, not the strategy
* {@link #equivalent(Object, Object)} and {@link #hash(Object)} methods. It is needed because,
* for example, {@link ObjCollection}'s equality depends on {@code Equivalence} equality.
*
*
In most cases, when {@code Equivalence} is stateless, you can extend
* {@link StatelessEquivalence} not to bother with implementing these methods. See examples
* in the documentation to {@linkplain #identity() identity} and
* {@linkplain #caseInsensitive() case insensitive} equivalences.
*
* @param type of objects compared by this equivalence
*/
public abstract class Equivalence {
/**
* Returns the default, built-in equivalence in Java, driven by {@link Object#equals(Object)}
* and {@link Object#hashCode()} methods.
*
* @param type of objects, needed to compare. {@link Object#equals} could be applied to
* object of any type, so there aren't any constraints over the generic type parameter.
* @return the built-in Java equality
*/
@SuppressWarnings("unchecked")
public static Equivalence defaultEquality() {
return (Equivalence) DEFAULT_EQUALITY;
}
/**
* Returns the equivalence that uses {@code ==} to compare objects and
* {@link System#identityHashCode(Object)} to compute the hash code.
* {@link Equivalence#nullableEquivalent} returns {@code true} if {@code a == b}, including
* in the case when {@code a} and {@code b} are both {@code null}.
*
* This equivalence could be implemented as follows:
*
* final class Identity extends StatelessEquivalence<Object> {
* static final Identity INSTANCE = new Identity();
* private Identity() {}
* @Override
* public boolean equivalent(Object a, Object b) {
* return a == b;
* }
* @Override
* public int hash(Object t) {
* return System.identityHashCode(t);
* }
* }
*
*
* @param type of objects, needed to compare. Identity check could be applied to objects
* of any type, so there aren't any constraints over the generic type parameter.
* @return the identity equivalence
*/
@SuppressWarnings("unchecked")
public static Equivalence identity() {
return (Equivalence) IDENTITY;
}
/**
* Returns the equivalence that compares {@link CharSequence}s by their contents.
*
* This equivalence could be implemented as follows (actual implementation, of cause,
* is more efficient and doesn't allocate garbage objects):
*
* final class CharSequenceEquivalence extends StatelessEquivalence<CharSequence> {
* static final CharSequenceEquivalence INSTANCE = new CharSequenceEquivalence();
* private CharSequenceEquivalence() {}
* @Override
* public boolean equivalent(CharSequence a, CharSequence b) {
* return a.toString().equals(b.toString());
* }
* @Override
* public int hash(CharSequence cs) {
* return cs.toString().hashCode();
* }
* }
*
* @return the {@link CharSequence} equivalence
*/
@SuppressWarnings("unused") // Public API; TODO use in tests
public static Equivalence charSequence() {
return CHAR_SEQUENCE;
}
/**
* Returns the {@link String} equivalence that uses {@link String#equalsIgnoreCase} to compare
* strings.
*
* This equivalence could be implemented as follows:
*
* final class CaseInsensitive extends StatelessEquivalence<String> {
* static final CaseInsensitive INSTANCE = new CaseInsensitive();
* private CaseInsensitive() {}
* @Override
* public boolean equivalent(String a, String b) {
* return a.equalsIgnoreCase(b);
* }
* @Override
* public int hash(String s) {
* return s.toLowerCase().hashCode();
* }
* }
*
*
* @return the case-insensitive {@link String} equivalence
*/
public static Equivalence caseInsensitive() {
return CASE_INSENSITIVE;
}
/**
* Returns a {@link java.util.Map.Entry} equivalence for the given key and value equivalences.
*
* @param keyEquivalence the entry key equivalence
* @param valueEquivalence the entry value equivalence
* @param the entry key type
* @param the entry value type
* @return a {@link java.util.Map.Entry} equivalence for the given key and value equivalences
*/
@SuppressWarnings("unused") // Public API; TODO use in tests
public static Equivalence> entryEquivalence(
Equivalence keyEquivalence, Equivalence valueEquivalence) {
if (keyEquivalence.equals(defaultEquality()) && valueEquivalence.equals(defaultEquality()))
return defaultEquality();
return new AutoValue_Equivalence_EntryEquivalence<>(keyEquivalence, valueEquivalence);
}
@AutoValue
static abstract class EntryEquivalence extends Equivalence> {
abstract Equivalence keyEquivalence();
abstract Equivalence valueEquivalence();
@Override
public boolean equivalent(Map.Entry a, Map.Entry b) {
//noinspection ObjectEquality: identity comparison is intended
boolean entriesIdentical = a == b;
return entriesIdentical ||
(keyEquivalence().nullableEquivalent(a.getKey(), b.getKey()) &&
valueEquivalence().nullableEquivalent(a.getValue(), b.getValue()));
}
@Override
public int hash(Map.Entry entry) {
return keyEquivalence().nullableHash(entry.getKey()) ^
valueEquivalence().nullableHash(entry.getValue());
}
}
private static final Equivalence